操作系统开发:文件读取寻址问题、解决方案及实验
问题
记录下自己学习自制操作系统时写MBR遇到的文件读取问题及解决方案。
简单来说就是LBA和CHS地址读取方式没分清。
环境
bochs
虚拟机nasm
汇编
现象
照着文档或教程写的代码,扇区号啥的也没错,但文件无法读取。
经调试器检查,存储器状态接口与读入接口皆为0x0
。
代码示例
这是LBA读文件的代码。
修改自从零开发操作系统
load_section:
; eax Section to read from
; bx Memory address to write to
; cx Number of sections to read
; backup eax & cx
mov esi, eax
mov di, cx
; write arguments
; Number of sections
mov dx, 0x1f2
mov al, cl
out dx, al
mov eax, esi
; Section (LBA) addr
; 7~0
mov dx, 0x1f3
out dx, al
; 15~8
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al
; 23~16
shr eax, cl
mov dx, 0x1f5
out dx, al
; 27~24
shr eax, cl
and al, 0x0f
; 7~4==1110 => LBA mode
or al, 0xe0
mov dx, 0x1f6
out dx, al
; write command
mov dx, 0x1f7
mov al, 0x20
out dx, al
; wait until disk ready
.wait_disk:
nop
in al, dx
; 4th digit==1 => ready
; 7th digit==1 => busy
; if (not (4th==1 and 7th!=1)) then wait
and al, 0x88
cmp al, 0x08
jnz .wait_disk
; calc read times
; di = cx = sections to read
mov ax, di
; 1 word (2 bytes) per time per section, one section 512 bytes
; total 256 times per section
mov dx, 256
; sections to read * time to read per section
; mul dx => prod=dx*ax, dx=prod.high16, ax=prod.low16
mul dx
; high16 ignored, cx=low16 for loop times
mov cx, ax
mov dx, 0x1f0
.read_loop:
in ax, dx
mov [bx], ax
add bx, 2
loop .read_loop
ret
这是CHS读文件的代码。
修改自《自己动手写操作系统》
BPB_SecPerTrk equ 18
BS_DrvNum equ 0
ReadSector:
; ax Section to read from
; cl Number of sectors to read
; bx Destination
; 1.44MB = 2 * 80 * 18 * 512
; (One head for each side)
; (Side * TracksPerSide * SectorsPerTrack * BytesPerSector)
; SecToRead / SecPerTrk -> [q]uotient, [r]emainder
; CyldNum = Q >> 1, HeadNum = Q & 1, StartSec = R + 1
push bp
mov bp, sp
sub esp, 2
mov byte [bp-2], cl
push bx
mov bl, BPB_SecPerTrk
div bl
inc ah
mov cl, ah
shr al, 1
mov ch, al
and dh, 1
pop bx
mov dl, BS_DrvNum
.GoOnReading:
; int 0x13, ah=2 for reading to buffer
; al number of sectors to read
; ch cylinder number
; cl first section number
; dh header number
; dl drive number (0 for drive A)
; es:bx buffer
mov ah, 2
mov al, byte [bp-2]
int 0x13
jc .GoOnReading
add esp, 2
pop bp
ret
问题原因
我的问题出在代码与存储设备格式不匹配。
简称 | 全称 | 中文 | 适用设备 | 读取方式(不多赘述,具体请查找文档教程) |
---|---|---|---|---|
LBA | Logical Block Addressing |
逻辑块 寻址 | 多用于硬盘 | 通过向设备写入地址 与命令 ,并检查硬盘可用性,即可读取 |
CHS | Cylinder Head Sector |
柱面-读头-扇区 寻址 | 多用于软盘 | 通过柱面 、读头 、扇区 的三维坐标,调用系统中断,即可读取 |
通过bochsrc
中的floppya
、floppyb
指定软盘,ata[0-3][-master/-slave]
指定硬盘。boot
参数指定启动设备。
例:
# 软盘
floppya: 1_44="img/floppy.img", status=inserted
boot: floppy
# 硬盘
ata0-master: path="img/hd.img", type=disk, mode=flat
boot: disk
解决方案
LBA寻址一般给硬盘使用,CHS一般给软盘使用。
根据存储设备选择读取方式。
实验验证
点击展开
工具
bochs
虚拟机nasm
汇编bximage
虚拟盘制作,bochs
自带dd
虚拟盘写入,大多类unix
(linux
和mac
)自带
系统建议使用Linux
或Mac
,对于Windows
用户bochsrc.bxrc
与一些命令可能需更改,本文不多赘述。
代码
模拟mbr
将操作系统
载入内存并执行的过程
os.asm
%include "boot.inc"
section MBR vstart=OS_BASE_ADDR
mov ax, 0xb800
mov gs, ax
; print msg
mov byte [gs:0x00], 'O'
mov byte [gs:0x01], 0x02
mov byte [gs:0x02], 'S'
mov byte [gs:0x03], 0x02
jmp $
载入后会打印绿色OS
字样。
mbr_lba.asm
%include "boot.inc"
StackBase equ 0x7c00
; load loader.asm into memory
section MBR vstart=StackBase
; init
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, StackBase
; video
mov ax, 0xb800
mov gs, ax
; display clear
mov ax, 0x0600
mov bx, 0x0700
mov cx, 0x0000
; dl=80, dh=25
mov dx, 0x184f
int 0x10
; print msg
mov byte [gs:0x00], 'M'
mov byte [gs:0x01], 0x04
mov byte [gs:0x02], 'B'
mov byte [gs:0x03], 0x04
mov byte [gs:0x04], 'R'
mov byte [gs:0x05], 0x04
mov byte [gs:0x06], ' '
mov byte [gs:0x07], 0x04
; load loader
mov eax, OS_START_SEC
mov bx, OS_BASE_ADDR
mov cx, 1
call load_section
jmp OS_BASE_ADDR
load_section:
; eax Section to read from
; bx Memory address to write to
; cx Number of sections to read
; backup eax & cx
mov esi, eax
mov di, cx
; write arguments
; Number of sections
mov dx, 0x1f2
mov al, cl
out dx, al
mov eax, esi
; Section (LBA) addr
; 7~0
mov dx, 0x1f3
out dx, al
; 15~8
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al
; 23~16
shr eax, cl
mov dx, 0x1f5
out dx, al
; 27~24
shr eax, cl
and al, 0x0f
; 7~4==1110 => LBA mode
or al, 0xe0
mov dx, 0x1f6
out dx, al
; write command
mov dx, 0x1f7
mov al, 0x20
out dx, al
; wait until disk ready
.wait_disk:
nop
in al, dx
; 4th digit==1 => ready
; 7th digit==1 => busy
; if (not (4th==1 and 7th!=1)) then wait
and al, 0x88
cmp al, 0x08
jnz .wait_disk
; calc read times
; di = cx = sections to read
mov ax, di
; 1 word (2 bytes) per time per section, one section 512 bytes
; total 256 times per section
mov dx, 256
; sections to read * time to read per section
; mul dx => prod=dx*ax, dx=prod.high16, ax=prod.low16
mul dx
; high16 ignored, cx=low16 for loop times
mov cx, ax
mov dx, 0x1f0
.read_loop:
in ax, dx
mov [bx], ax
add bx, 2
loop .read_loop
ret
times 510 - ($ - $$) db 0x0
dw 0xaa55
mbr_chs.asm
%include "boot.inc"
StackBase equ 0x7c00
; load os.asm into memory
section MBR vstart=StackBase
; init
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
; video
mov ax, 0xb800
mov gs, ax
; display clear
mov ax, 0x0600
mov bx, 0x0700
mov cx, 0x0000
; dl=80, dh=25
mov dx, 0x184f
int 0x10
; print msg
mov byte [gs:0x00], 'M'
mov byte [gs:0x01], 0x04
mov byte [gs:0x02], 'B'
mov byte [gs:0x03], 0x04
mov byte [gs:0x04], 'R'
mov byte [gs:0x05], 0x04
; load os
mov eax, OS_START_SEC
mov bx, OS_BASE_ADDR
mov cx, OS_SEC_CNT
call ReadSector
jmp OS_BASE_ADDR
ReadSector:
; ax Section to read from
; cl Number of sectors to read
; bx Destination
; 1.44MB = 2 * 80 * 18 * 512
; (One head for each side)
; (Side * TracksPerSide * SectorsPerTrack * BytesPerSector)
; SecToRead / SecPerTrk -> [q]uotient, [r]emainder
; CyldNum = Q >> 1, HeadNum = Q & 1, StartSec = R + 1
push bp
mov bp, sp
sub esp, 2
mov byte [bp-2], cl
push bx
mov bl, BPB_SecPerTrk
div bl
inc ah
mov cl, ah
shr al, 1
mov ch, al
and dh, 1
pop bx
mov dl, BS_DrvNum
.GoOnReading:
; int 0x13, ah=2 for reading to buffer
; al number of sectors to read
; ch cylinder number
; cl first section number
; dh header number
; dl drive number (0 for drive A)
; es:bx buffer
mov ah, 2
mov al, byte [bp-2]
int 0x13
jc .GoOnReading
add esp, 2
pop bp
ret
times 510 - ($ - $$) db 0x0
dw 0xaa55
这两个文件前面先清屏输出红色的MBR
文字,后读入os。若os.asm
载入成功,则前两个字符应被绿色OS
字覆盖。
创建include
目录(文件夹),并写入
include/boot.inc
OS_BASE_ADDR equ 0x900 ; os将被载入的内存地址
OS_START_SEC equ 2 ; os被存储的起始扇区
OS_SEC_CNT equ 1 ; os存储占用扇区数
BPB_SecPerTrk equ 18 ; 存储设备单磁道扇区数
BS_DrvNum equ 0 ; 存储设备号
存储一些mbr和os共用公用的数据,方便修改。后两行仅mbr_chs.asm
使用。
bochsrc_floppy.bxrc
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest.bin
keyboard: keymap=/usr/local/share/bochs/keymaps/sdl2-pc-us.map
floppya: 1_44="os_chs.img", status=inserted
boot: floppy
log: /dev/null
mouse: enabled=0
megs: 32
display_library: sdl2
bochsrc_disk.bxrc
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest.bin
keyboard: keymap=/usr/local/share/bochs/keymaps/sdl2-pc-us.map
ata0-master: type=disk, path="os_lba.img", mode=flat
boot: disk
log: /dev/null
mouse: enabled=0
megs: 32
display_library: sdl2
bochs
的启动配置文件。
准备
制作软盘chs
版本。
nasm -I include/ -f bin mbr_chs.asm -o mbr_chs.bin
nasm -I include/ -f bin os.asm -o os.bin
bximage -func=create -fd=1.44M -q os_chs.img
dd if=mbr_chs.bin of=os_chs.img bs=512 count=1 conv=notrunc
dd if=os.bin of=os_chs.img bs=512 count=1 conv=notrunc seek=2
制作硬盘lba
版本。
nasm -I include/ -f bin mbr_lba.asm -o mbr_lba.bin
nasm -I include/ -f bin os.asm -o os.bin
bximage -func=create -hd=10M -q os_lba.img
dd if=mbr_lba.bin of=os_lba.img bs=512 count=1 conv=notrunc
dd if=os.bin of=os_lba.img bs=512 count=1 conv=notrunc seek=2
实验
先试试软盘
bochs -f bochsrc_floppy.bxrc
然后硬盘
bochs -f bochsrc_disk.bxrc
现象
不出意外的话,两次都应该出现OSR
字样,原因在代码区有解释。
有兴趣可以将不同的mbr
与不同的虚拟存储设备相结合。读取方式不匹配预期现象为显示MBR
字样,说明OS
加载失败。
有错误望指正。