系统将setup程序从软盘加载至内存0x90200地址后,首先会获取并保存磁盘参数信息,接着加载system模块。完成这些操作后,控制权将转移至setup程序执行,至此 bootsect 阶段结束。
三、bootsect.s第三阶段详解
3.1 输出消息
# get cursor mov $0x03,%ah mov $0x00,%bh int $0x10 # print msg3 mov $0x13,%ah mov $0x01,%al mov $0x00,%bh mov $0x07,%bl mov $msg3_len,%cx mov $msg3,%bp int $0x10 ... msg3: .ascii "Stage3:\r\n\t(1) Get disk drive parameters..\r\n" msg3_len = . - msg33.2 获取磁盘参数并保存
3.2.1 获取磁盘参数
# get disk drive paramters mov $0x08,%ah mov $0x00,%dl int $0x13通过BIOS中断0x13的08号功能获取磁盘驱动器参数,具体使用方法及返回值说明如下:
AH = 08h DL = drive (bit 7 set for hard disk) ES:DI = 0000h:0000h to guard against BIOS bugs Return: CF set on error AH = status (07h) (see #00234) CF clear if successful AH = 00h AL = 00h on at least some BIOSes BL = drive type (AT/PS2 floppies only) (see #00242) CH = low eight bits of maximum cylinder number CL = maximum sector number (bits 5-0) high two bits of maximum cylinder number (bits 7-6) DH = maximum head number DL = number of drives ES:DI -> drive parameter table (floppies only)当前采用软盘启动,故将DL设置为0,运行结果如下:
检测到eflags的最后一位为0(0x6即0110b),表示CF=0,说明读取操作成功。此时寄存器bl的值为4,查阅规格表可知该值对应1.44MB软盘规格,和当前使用规格一致。
Values for diskette drive type: 01h 360K 02h 1.2M 03h 720K 04h 1.44M 05h ??? (reportedly an obscure drive type shipped on some IBM machines). 2.88M on some machines (at least AMI 486 BIOS) 06h 2.88M 10h ATAPI Removable Media Device其他寄存器值信息如下:
- CL寄存器值0x12(00010010b)的bit(5,0)表明每个磁道包含18个扇区。
- CH寄存器值0x4f结合CL的高2位,形成0001001111b(即79),表示最大柱面编号为79,软盘无柱面,因此无用。
- DH寄存器值为1,表示最大磁头编号为1。
- DL寄存器值为1,表示当前有1个驱动器。
3.2.2 保存磁盘参数
# save paramters mov %cx,sectors ... sectors: .word 0实际生效的是扇区数,后续操作虽直接调用sectors值,但仅扇区数真正有效。
驱动器参数值将被存入bootsect的data区段,以备后续调用。
3.3 加载system
3.3.1 输出消息
# get cursor position mov $0x03,%ah mov $0x00,%bh int $0x10 # reset es mov $INITSEG,%ax mov %ax,%es # print msg4 mov $0x13,%ah mov $0x01,%al mov $0x00,%bh mov $0x07,%bl mov $msg4_len,%cx mov $msg4,%bp int $0x10
由于获取驱动器参数时ES会被修改,而BIOS中断0x10的 0x13号功能需要通过ES:BP来确定输出数据地址,因此需要重新设置ES的值。
3.3.2 定义变量
SYSSEG = 0x1000 SYSSIZE = 0x3000 ENDSEG = SYSSEG + SYSSIZE系统将被加载到内存地址范围[0x10000,0x40000]的120KB空间内。
mov $SYSSEG,%ax mov %ax,%es将系统起始段基址存入ES寄存器。
3.3.3 函数调用
从bootset初始运行到此处,首次出现函数调用,通过call指令触发执行流程。该调用机制依赖于栈结构:调用前会将call指令的下一条地址压入栈中,随后跳转至目标函数执行。函数结束时执行ret指令,从栈顶弹出返回地址并恢复至IP寄存器,使程序回到调用点继续执行。值得注意的是,在第二阶段加载setup之前,系统已预先设置好ss和sp寄存器,从而确保了函数调用的正常执行。
call read_it栈采用从高地址向低地址的压栈方式,当前栈顶为0xff00,2字节的IP地址,实际压栈地址为0x9fefe。call指令占用4字节空间,当前指令指针IP为0x00ac,故下一条指令地址为0x00ac+4=0x00af。通过x命令观察call指令执行后,0x9fefe地址的内容由随机值变为0x00af。由于采用小端序存储,实际显示为0xaf 0x00,
同时看到栈顶指针sp的值也自动更新。
3.3.4 边界检查
边界检查:目标地址必须4K对齐,否则系统将进入死循环并宕机。
read_it: mov %es,%ax test $0x0fff,%ax die: jne die3.3.5 循环读取
由于数据读取量较大,系统采用按磁道批量读取的方式,而非逐个扇区进行读取。
- rp_read 主循环
检查当前写入地址是否超出ENDSEG (0x40000)。若超出则终止读取,通过ret指令返回到call read_it的下一条指令;否则跳转至ok1_read继续执行读取操作。
写入内容范围为[0x10000, 0x40000],共 0x30000 字节(384个扇区)。
rp_read: mov %es,%ax cmp $ENDSEG,%ax jb ok1_read ret- ok1_read
将读取的扇区数存入CX寄存器,内存偏移地址存入BX寄存器。
首次读取时,sectors初始值为5(包含1个启动扇区和4个setup扇区)。sread存储的是上一步通过BIOS中断获取的磁道扇区数,因此第一次需读取的扇区数为18-5=13。读取完成后,sectors会被重置为0,后续将读取完整的磁道扇区数(即18个扇区)。
计算读取扇区数时,先将其左移9位(相当于乘以512),得到需要读取的字节数。然后加上已读取的字节数,判断是否超过0xFFFF。若溢出,则用0x0 - 已读取字节数计算剩余可读取的字节数,再右移9位(相当于除以512)得到新的扇区数,随后跳转至ok2_read;否则直接进入ok2_read
ok1_read: mov sectors,%ax sub sread,%ax mov %ax,%cx shl $0x09,%cx add %bx,%cx jnc ok2_read je ok2_read xor %ax,%ax sub %bx,%ax shr $0x09,%ax ... sread: .word 1+SETUPLEN- ok2_read
执行read_track进行实际数据读取操作后,将当前读取的扇区数累加到sread。随后检查当前读取扇区数是否等于磁道扇区总数,若不相等则跳转至ok3_read继续读取(继续读当前磁道剩余扇区)。
若扇区数相等,则进行磁头和磁道调整操作:计算ax=1-head得到新磁头值。当head为0时,设置ax=1并跳转至ok4_read,此时head将被更新为1(进入下一个磁头读取);若head为1,则设置ax=0并将磁道track加1,同样进入ok4_read流程,此时head将被重置为0(进入下一个磁道读取)。
ok2_read: call read_track mov %ax,%cx add sread,%ax cmp %ax,sectors jne ok3_read mov $1,%ax sub head,%ax jne ok4_read incw track ok4_read: mov %ax,head xor %ax,%ax ... head: .word 0 track: .word 0- ok3_read
计算当前读取的字节数(扇区数×512),将其与已读取字节数相加。若未发生溢出,则跳转至rp_read入口重新开始读取流程(未读满0x10000字节);否则,将es寄存器值增加0x1000,重置已读取字节数,再跳转至rp_read入口继续下一轮读取操作(以读0x10000字节)。
ok3_read: mov %ax,sread shl $0x09,%cx add %cx,%bx jnc rp_read mov %es,%ax add $0x1000,%ax mov %ax,%es xor %bx,%bx jmp rp_read- read_track
实际读取数据的函数,流程如下:
- 将寄存器压入堆栈
- 设置BIOS中断所需的参数值
- 调用BIOS中断将磁盘内容读取至内存es:bx处
- 若读取失败,则跳转至bad_rt标签:调用BIOS中断清理读取系统,恢复寄存器后重新跳转至read_track重试
- 若读取成功,恢复寄存器并返回
read_track: push %ax push %bx push %cx push %dx mov track,%dx mov sread,%cx inc %cx mov %dl,%ch mov head,%dx mov %dl,%dh mov $0,%dl and $0x0100,%dx mov $0x02,%ah int $0x13 jc bad_rt pop %dx pop %cx pop %bx pop %ax ret bad_rt: mov $0,%ax mov $0,%dx int $0x13 pop %dx pop %cx pop %bx pop %ax jmp read_track3.3.6 关闭驱动器
读取结束后,调用kill_motor函数关闭软盘驱动器。
call read_it call kill_motor ... kill_motor: push %dx mov $0x03f2,%dx mov $0,%al out %al,%dx pop %dx ret3.4 完整代码
boot/bootsect.s
BOOTSEG = 0x07c0 INITSEG = 0x9000 SETUPSEG = 0x9020 SETUPLEN = 4 SYSSEG = 0x1000 SYSSIZE = 0x3000 ENDSEG = SYSSEG + SYSSIZE .code16 .global _start _start: #=== print msg === # get cursor position mov $0x03,%ah mov $0x00,%bh int $0x10 mov $BOOTSEG,%ax mov %ax, %es #set string base addr mov $0x13, %ah #set function number mov $0x01, %al #set flags(update cursor after writing) mov $0x07, %bl #set attribute mov $msg_len, %cx #set string length mov $msg, %bp #set string offset int $0x10 #=== move bootsec === mov $BOOTSEG,%ax mov %ax,%ds xor %si,%si mov $INITSEG,%ax mov %ax,%es xor %di,%di mov $256,%cx cld rep movsw jmp $INITSEG,$loop loop: mov %cs,%ax mov %ax,%ds mov %ax,%es mov %ax,%ss mov $0xff00,%sp mov $0x03,%ah mov $0x00,%bh int $0x10 mov $0x13, %ah mov $0x01, %al mov $0x07, %bl mov $msg2_len,%cx mov $msg2, %bp int $0x10 load_setup: mov $0x02,%ah mov $SETUPLEN,%al mov $0x00,%ch mov $0x02,%cl mov $0x00,%dh mov $0x00,%dl mov $0x0200,%bx int $0x13 jnc ok_load_setup mov $0x00,%ah mov $0x00,%dl int $0x13 jmp load_setup ok_load_setup: # get cursor position mov $0x03,%ah mov $0x00,%bh int $0x10 # print msg3 mov $0x13,%ah mov $0x01,%al mov $0x00,%bh mov $0x07,%bl mov $msg3_len,%cx mov $msg3,%bp int $0x10 # get disk drive paramters mov $0x08,%ah mov $0x00,%dl int $0x13 # save paramters mov %cx,sectors # get cursor position mov $0x03,%ah mov $0x00,%bh int $0x10 # reset es mov $INITSEG,%ax mov %ax,%es # print msg4 mov $0x13,%ah mov $0x01,%al mov $0x00,%bh mov $0x07,%bl mov $msg4_len,%cx mov $msg4,%bp int $0x10 # load system mov $SYSSEG,%ax mov %ax,%es call read_it call kill_motor todo: jmp todo read_it: mov %es,%ax test $0x0fff,%ax die: jne die xor %bx,%bx rp_read: mov %es,%ax cmp $ENDSEG,%ax jb ok1_read ret ok1_read: mov sectors,%ax sub sread,%ax mov %ax,%cx shl $0x09,%cx add %bx,%cx jnc ok2_read je ok2_read xor %ax,%ax sub %bx,%ax shr $0x09,%ax ok2_read: call read_track mov %ax,%cx add sread,%ax cmp %ax,sectors jne ok3_read mov $1,%ax sub head,%ax jne ok4_read incw track ok4_read: mov %ax,head xor %ax,%ax ok3_read: mov %ax,sread shl $0x09,%cx add %cx,%bx jnc rp_read mov %es,%ax add $0x1000,%ax mov %ax,%es xor %bx,%bx jmp rp_read read_track: push %ax push %bx push %cx push %dx mov track,%dx mov sread,%cx inc %cx mov %dl,%ch mov head,%dx mov %dl,%dh mov $0,%dl and $0x0100,%dx mov $0x02,%ah int $0x13 jc bad_rt pop %dx pop %cx pop %bx pop %ax ret bad_rt: mov $0,%ax mov $0,%dx int $0x13 pop %dx pop %cx pop %bx pop %ax jmp read_track kill_motor: push %dx mov $0x03f2,%dx mov $0,%al out %al,%dx pop %dx ret sread: .word 1+SETUPLEN head: .word 0 track: .word 0 sectors: .word 0 msg: .ascii "Stage1: start moving bootsect.." msg_len = . - msg msg2: .ascii "\r\nStage2: start load setup..\r\n" msg2_len = . - msg2 msg3: .ascii "Stage3:\r\n\t(1) Get disk drive parameters..\r\n" msg3_len = . - msg3 msg4: .ascii "\t(2) start load system..\r\n" msg4_len = . - msg4 .org 510 .word 0xAA55Makefile
all: bootsect dd if=/dev/zero of=disk.img bs=512 count=2880 dd if=bootsect of=disk.img bs=512 count=1 conv=notrunc dd if=/dev/random of=disk.img bs=512 count=4 seek=1 conv=notrunc bochs -f bochsrc -q bootsect: boot/bootsect.o ld -m elf_i386 -Ttext 0 -e _start -s --oformat binary $< -o $@ boot/bootsect.o: boot/bootsect.s as --32 $< -o $@ clean: rm -f boot/bootsect.o bootsect disk.img bochs.logbochsrc
megs: 32 display_library: x floppya: 1_44="disk.img", status=inserted boot: a log: bochs.log sound: waveoutdrv=dummy运行
make3.5 总结
- 在bootsect.s中首次出现函数调用时,会通过设置ss和sp寄存器来建立栈环境。
- 该阶段将system模块读取到内存[0x10000,0x40000]区间,其读取过程不是通过扇区数量控制,而是由内存范围决定,一旦超出该范围即停止读取。
- 读取过程中通过溢出判断是否已读取0x10000字节数据。随后调整es段寄存器值(增加0x1000),同时用bx记录相对于当前es的已读取偏移量。此外,sread记录已读取的扇区数,track和head分别记录当前读取的磁道和磁头位置。
- 在数据段中预留sread,track,head的空间作为临时变量。
3.6 参考
- GET DRIVE PARAMETERS
https://www.ctyme.com/intr/rb-0621.htm - RESET DISK SYSTEM
https://www.ctyme.com/intr/rb-0605.htm - READ SECTOR(S) INTO MEMORY
https://www.ctyme.com/intr/rb-0607.htm