linux0.11 - bootsect.s 第三阶段(加载system)

系统将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 = . - msg3

3.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 die

3.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_track

3.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
    ret

3.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 0xAA55

Makefile

复制代码
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.log

bochsrc

复制代码
megs: 32
display_library: x
floppya: 1_44="disk.img", status=inserted
boot: a
log: bochs.log
sound: waveoutdrv=dummy

运行

复制代码
make

3.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
相关推荐
wgl6665202 小时前
ELF文件 && 链接与加载
linux·运维·服务器
执笔仗剑天涯3 小时前
CentOS 7上离线安装Claude Code
linux·centos·claude code
南境十里·墨染春水3 小时前
linux学习进展 mysql数据库
linux·数据库·学习
liuluyang5303 小时前
linux kernel CONFIG_FHANDLE解析
linux·运维·服务器
2601_958320574 小时前
【详细版教程】Windows/macOS/Linux 安装 OpenClaw 2.6.6 指南(包含安装包)
linux·运维·windows·macos·小龙虾·open claw一键安装
HackTwoHub4 小时前
可视化未授权访问批量探测工具、支持批量目标、并发扫描、SOCKS5 全局代理、CSV 导出
linux·windows·macos·网络安全·网络攻击模型
ACP广源盛139246256734 小时前
磐石 100 :IX6012 :ASM1812@ACP#国产 PCIe 2.0 交换芯片,轻量级算力扩展应用分享
大数据·linux·运维·网络·人工智能·嵌入式硬件·电脑
A-刘晨阳4 小时前
K8s之负载均衡
linux·运维·容器·kubernetes·负载均衡
学困昇4 小时前
彻底搞懂 Linux 基础 IO:文件描述符、重定向、dup2、缓冲区一次讲透!
linux·运维·服务器·开发语言·c++