bootsect.s
SYSSIZE = 0x3000 #system模块的长度
.global begtext, begdata, begbss, endtext, enddata, endbss
.text #代码段
begtext:
.data #初始化的全局变量和静态变量
begdata:
.bss #为初始化的全局变量和静态变量
begbss:
.text #返回到代码段
SETUPLEN = 4 #setup程序所占的扇区
BOOTSEG = 0x07c0 #启动扇区加载到内存的段地址
INITSEG = 0x9000 #启动扇区搬移后的地址
SETUPSEG = 0x9020 #setup程序的加载地址
SYSSEG = 0x1000 #system加载的段地址
EDNSEG = SYSSEG+SYSSIZE #system加载的结束地址
ROOT_DEV = 0x306 #第二个硬盘的第一个分区
entry _start #告知链接程序,程序从 _start 标号开始执行
_start: #程序入口
mov ax,#BOOTSEG #将引导扇区的段地址0x07c0加载到ax
mov ds,ax #将启动代码与ds寄存器相关联,这样DS:00就对应实际内存0x07c00,这是BIOS加载的起始位置
mov ax,#INITSEG #启动代码要被复制的目的地址的段地址为0x9000
mov es,ax #将目的地址与es寄存器相关联,这样ES:00就对应实际内存0x90000,这是启动代码即将被复制的地址
mov cx,#256 #循环控制字节,搬运256次,一次2字节,共256字节
sub si,si #si清零,ds:si即0x07c00
sub di,di #di清零,es:si即0x90000
rep #循环直到cx=0
movw #传送一个字,将ds:si复制到es:di,每复制一次,si和di加2,将引导扇区的内容搬到0x90000处
jmpi go,INITSEG #段间跳转,跳转到0x9000:go处
go: mov ax,cs #因为启动代码复制到了新的位置,需要更改相应寄存器的值
mov ds,ax
mov es,ax #cs = ds = es = ss = 0x9000
mov ss,ax #开始引入栈
mov sp,#0xFF00 #栈空间的起始地址为0x9ff00
/*
setup程序是BIOS加载到内存中执行的第二阶段引导程序
setup从0x90200地址开始,大概占用四个扇区,一个扇区是0x200字节,所以一共占用0x800字节,
程序还多预留了一个扇区空间,所以setup一共占用5个扇区,即0xA00字节
栈的大小设置成0xFF00-0xA00 = 0xF500,即0xF500字节,足够大
内存地址增长方向 →
0x90000 --------------------------> 0x9FFFF
|---- setup 占用 ----|
0x90200 --- 0x90A00 |
|
栈 (Stack) |
← ← ← ← ← |
(生长方向) |
|
0x9FF00 --------------------------
*/
load_setup:
mov dx,#0x0000 #驱动器号(DL)0,磁头号(DH)0
mov cx,#0x0002 #柱面的低8为(CH)0,柱面的高两位(CL[7:6])0,起始扇区(CL[5:0])2
mov bx,#0x0200 #ES:BX读出数据的缓存位置
mov ax,#0x0200+SETUPLEN #功能号(AH)0x02读取磁盘,要读取的扇区数(AL)4
int 0x13 #进入中断服务程序,将磁盘setup.s对应的程序加载至内存指定位置即0x90200
/*
功能号 0x02 — 读取磁盘扇区
---------------------------------------
- 功能描述:从指定的磁道、磁头、扇区读取数据到内存
- 入口参数:
AH = 0x02 ; 功能号,表示读取扇区
AL = 扇区数量 ; 读取的扇区数量
CH = 柱面号 (低 8 位)
CL = 扇区号 (低 6 位) + 柱面号 (高 2 位)
DH = 磁头号
DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)
ES:BX = 目标内存地址
- 返回值:
CF = 0 (成功), 1 (失败)
AH = 错误码 (如果 CF = 1)
AL = 实际读取到的扇区数
---------------------------------------
*/
jnc ok_load_setup #CF标志寄存器,中断的返回值会传入CF,CF=0则跳转至ok_load_setup
mov dx,#0x0000 #驱动器号(DL)0,磁头号(DH)0
mov ax,#0x0000 #功能号(AH)0x00,复位磁盘
int 0x13 #进入中断服务程序,复位磁盘
/*
功能号 0x00 — 重置磁盘
---------------------------------------
- 功能描述:复位磁盘系统,相当于重新初始化磁盘接口
- 入口参数:
AH = 0x00 ; 功能号,表示复位磁盘
DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)
- 返回值:
CF = 0 (成功), 1 (失败)
AH = 错误码 (如果 CF = 1)
---------------------------------------
*/
j load_setup #再次读取
ok_load_setup:
mov dl,#0x00 #驱动器号(DL)0
mov ax,#0x0800 #功能号(AH)0x08,表示获取驱动器的参数
int 0x13 #进入中断服务程序
/*
功能号 0x08 — 获取磁盘参数
---------------------------------------
- 功能描述:获取磁盘的几何信息(柱面数、磁头数、扇区数)
- 入口参数:
AH = 0x08 ; 功能号,表示获取磁盘参数
DL = 驱动器号 (0x00 表示软盘,0x80 表示硬盘)
- 返回值:
CF = 0 (成功), 1 (失败)
AH = 错误码 (如果 CF = 1)
BL = 驱动器类型
CH = 最大磁道号(低 8 位)
CL = 每磁道最大扇区号(低 6 位) +最大磁道号(高 2 位)
DH = 最大磁头数
DL = 驱动器数量
ES:DI = 指向 DPT (Drive Parameter Table) 的地址,软驱磁盘参数表
---------------------------------------
*/
mov ch,#0x00 #对ch置零,因为保存了最大磁道号,避免与下面获取扇区数造成干扰
seg cs #指示下一条指令的操作数在 CS 段中
mov sectors,cx #将最大扇区数存放在cs:sectors中
mov ax,#INITSEG #由于中断返回值修改了es,因此需要恢复es
mov es,ax #es恢复成0x9000
mov ah,#0x03 #读取光标位置
xor bh,bh #页号设置成0
int 0x10 #进入中断服务程序
/*
功能号 0x03 — 获取光标位置和页面信息
---------------------------------------
- 中断号:0x10
- 功能描述:获取光标在指定页面的列号和行号,同时返回当前光标形状。
- 入口参数:
AH = 0x03 ; 功能号 0x03,获取光标位置
BH = 页号 ; 0 表示默认页
- 返回值:
CH = 光标开始扫描线号 ; 光标形状的起始位置
CL = 光标结束扫描线号 ; 光标形状的结束位置
DH = 光标所在的行号 ; 光标所在屏幕的行位置
DL = 光标所在的列号 ; 光标所在屏幕的列位置
---------------------------------------
*/
mov cx,#24 #字符串长度24
mov bx,#0x0007 #页面后为0,字符属性0x7,黑底白字
mov bp,#msg1 #指向要显示的字符串地址
mov ax,#0x1301 #功能号(AH)0x13,写字符串,显示方式(AL)0x01
int 0x10 #进入中断服务程序
/*
功能号 0x13 — 显示字符串并移动光标
---------------------------------------
- 中断号:0x10
- 功能描述:在指定位置显示字符串,并根据参数选择是否移动光标。
- 入口参数:
AH = 0x13 ; 功能号 0x13,显示字符串
AL = 显示方式 ;
位 0:字符串中只含显示字符,显示属性在BL中;显示后,光标位置不变
位 1:字符串中只含显示字符,显示属性在BL中;显示后,光标位置跟随字符串改变
位 2:字符串中含有显示字符和显示属性;显示后,光标位置不变
位 3:字符串中含有显示字符和显示属性;显示后,光标位置跟随字符串改变
BH = 页号 ; 0 表示默认页
BL = 颜色属性 ; 0x07 表示黑底白字
CX = 字符串长度 ; 要显示的字符数
DH = 行号 ; 如果使用光标位置,指定行号
DL = 列号 ; 如果使用光标位置,指定列号
ES:BP = 字符串地址 ; 要显示的字符串内存地址
- 返回值:
无
*/
mov ax,#SYSSEG #设置ax=0x1000
mov es,ax #设置es=0x1000
call read_it #调用read_it方法,将system模块存放在0x1000:0x0000处
call kill_motor #调用kill_motor方法,关闭驱动器马达
/*
设备号计算与软驱类型识别
---------------------------------------
- 主设备号:
0x01 - 内存设备 (RAM Disk)
0x02 - 代表软盘设备 (Floppy Disk)
0x03 - IDE 硬盘 (IDE Disk)
0x04 - 串口设备 (TTY)
- 次设备号计算公式:
次设备号 = type * 4 + nr
- type:软驱类型
- 2 表示 1.2MB 的软驱
- 7 表示 1.44MB 的软驱
- nr:软驱编号
- 0 表示软驱 A:
- 1 表示软驱 B:
- 2 表示软驱 C:
- 3 表示软驱 D:
由于是可引导的驱动器,所以默认是 A:,即 nr = 0
- 设备号计算:
设备号 = (主设备号 << 8) + 次设备号
- 对于 1.2MB 的软驱:
次设备号 = 2 * 4 + 0 = 8
设备号 = (0x02 << 8) + 0x08 = 0x0208
- 对于 1.44MB 的软驱:
次设备号 = 7 * 4 + 0 = 28
设备号 = (0x02 << 8) + 0x1C = 0x021C
*/
seg cs
mov ax,root_dev #设置ax=root_dev
cmp ax,#0
jne root_defined #如果root_dev不等于则跳转到root_defined
seg cs
mov bx,sectors #取每磁道扇区数
mov ax,#0x0208 #/dev/ps0 - 1.2Mb
cmp bx,#15 #如果等于15,说明是1.2MB的软盘
je root_defined
mov ax,#0x021c #/dev/PS0 - 1.44Mb
cmp bx,#18 #如果等于18,说明是1.44MB的软盘
je root_defined
undef_root:
jmp undef_root #死循环
root_defined:
seg cs
mov root_dev,ax #将检查过的设备号保存到root_dev中
jmpi 0,SETUPSEG #到此本程序就结束了,跳转到setup去执行
sread: .word 1+SETUPLEN #当前磁道已经读取的扇区数(引导扇区 + setup模块 = 5)
head: .word 0 #当前磁头号
track: .word 0 #当前磁道号
read_it:
mov ax,es
test ax,#0x0fff #test指令执行与运算,当运算结果为0时,test设置零标志ZF=1,这里使ax与0xfff按位与,测试es是否为0x1000的整数倍
die: jne die #jne的跳转条件是ZF=0,因此不会跳转,如果test结果不为0,说明es不是0x1000的整倍数,则陷入死循环
xor bx,bx #bx作为段内偏移地址清零
rp_read:
mov ax,es
cmp ax,#ENDSEG #实际上求ax-ENDSEG,看是否到了末尾,如果不相等,则继续读取
jb ok1_read #当CF=1,ax<ENDSEG,则跳转
ret #如果没有跳转到ok1_read,即ax超过或等于ENDSEG时,执行ret,返回调用处
ok1_read:
seg cs
mov ax,sectors #将cs:[sectors]中存放的每磁道的扇区数赋值给ax
sub ax,sread #ax=ax-sread,得出本磁道未读取扇区数
mov cx,ax #将剩余未读的扇区数赋值给cx
shl cx,#9 #cx*512代表剩余扇区需要读取的总字节数
add cx,bx #上面三行相当于cx=cx*512+bx,bx是段内偏移地址
jnc ok2_read #判断这次加法是否产生了进位,是否16位溢出即cx<0x1000,CF=0没有进位,则跳转到ok2_read
je ok2_read #判断加法结果是否为0,如果是0,ZF=1,说明刚好没有越界,则跳转到ok2_read
xor ax,ax #执行到这里说明越界,则ax=0
sub ax,bx #计算bx离边界多远,用0-bx结果存放在ax中
shr ax,#9 #除以512,得到要读的扇区数,AL作为参数,传给read_track
ok2_read:
call read_track #传递al参数,读取al个扇区到ES:BX
mov cx,ax #返回值al,本次读取的扇区数,cx=ax
add ax,sread #当前磁道已经读取的扇区数
seg cs
cmp ax,sectors #如果当前磁道还有扇区没有读完,跳转到ok3_read
jne ok3_read
mov ax,#1 #说明当前磁道已经读完
sub ax,head #ax=1-磁头号
jne ok4_read #判断sub值是否为0,如果不是0则跳转,此时ax=1即磁头号是1,磁道号是0
inc track #此时说明磁头号是1,ax等于0,设置磁头号等于0,磁道号增加1
ok4_read:
mov head,ax #保存当前磁头号
xor ax,ax #清楚已读扇区,设置为0
ok3_read:
mov sread,ax #更新当前磁道已经读取的扇区数
shl cx,#9 #cx是该次操作已经读取的扇区数,乘以512字节
add bx,cx #更新偏移地址
jnc rp_read #没有进位,则跳转到rp_read
mov ax,es #有进位,说明bx达到了64kb边界
add ax,#0x1000
mov es,ax #es增加0x1000
xor bx,bx #bx等于0
jmp rp_read #继续读取
read_track:
push ax #保存ax,bx,cx,dx寄存器
push bx
push cx
push dx
mov dx,track #获取当前的磁道号
mov cx,sread #获取当前磁道上已经读取的扇区数
inc cx #CL起始扇区号
mov ch,dl #CH当前磁道号
mov dx,head #DH当前磁头号
mov dh,dl #DH是磁头号
mov dl,#0 #DL驱动器号
and dx,#0x0100 #将dx与0x0100相与,目的是使磁头号小于等于1
mov ah,#2 #ah=2,读磁盘扇区功能号
int 0x13 #进入中断服务程序,读取剩余的扇区数al到es:bx
jc bad_rt #如果出错,CF=1,表示出错,跳转到bad_rt复位磁盘
pop dx #取出ax,bx,cx,dx寄存器
pop cx
pop bx
pop ax
ret #返回
bad_rt: mov ax,#0 #ah=0,磁盘复位功能
mov dx,#0 #dl是驱动器号,DH当前磁头号
int 0x13 #进入中断服务程序
pop dx #取出ax,bx,cx,dx寄存器
pop cx
pop bx
pop ax
jmp read_track #重新获取
kill_motor:
push dx
mov dx,#0x3f2 #软盘控制器的端口-数字输出寄存器端口,只写
mov al,#0 #驱动器A,关闭FDC,禁止DMA和中断请求,关闭马达
outb #将al的值写入端口dx
pop dx
ret
sectors:
.word 0
msg1:
.byte 13,10 #13 是回车 (Carriage Return, CR),10 是换行 (Line Feed, LF)
.ascii "Loading system ..." # 输出的 ASCII 字符串 "Loading system ..."
.byte 13,10,13,10 # 两次回车换行
/*
.org 508 伪指令的作用
---------------------------------------
- `.org` 是汇编伪指令 (Assembler Directive)。
- 作用是告诉编译器接下来的代码或数据应该放置在特定的偏移地址。
- 这里的 `508` 是十进制,换算成十六进制是 `0x1FC`。
➡️ 0x1FC 是引导扇区 (Boot Sector) 中的重要位置:
- 引导扇区是 BIOS 从磁盘读取的第一个扇区,共 512 字节。
- BIOS 把这个扇区加载到内存地址 `0x7C00`,并跳转到那里执行。
➡️ 引导扇区的内存布局:
| 偏移地址 (十六进制) | 大小 | 描述 |
|----------------------|------|----------------------------|
| 0x000 | 446 | 主引导程序 (Bootstrap Code) |
| 0x1BE | 64 | 分区表 (Partition Table) |
| 0x1FC | 2 | 自定义数据 (通常存放设备号) |
| 0x1FE | 2 | 引导签名 (0x55AA) |
- 0x1FC 和 0x1FD 可以存放引导加载器自定义的内容,比如设备号、标识符等。
- 0x1FE 和 0x1FF 是固定的签名,如果是 `0x55AA`,表示引导扇区合法。
➡️ `.org 508` 的意义:
- 508 是十进制,对应十六进制是 `0x1FC`。
- 在这个位置通常会放一些特殊的数据:
- 设备号
- 校验码
- 魔数 (Magic Number)
- 这一步是为了让引导加载器能正确识别设备类型。
➡️ 例子:
- 如果在 `.org 508` 之后写:
mov ax, #0x0208
- 那么 `0x0208` 会被放在偏移地址 `0x1FC` 和 `0x1FD` 上。
| 偏移地址 | 内容 |
|-----------|------|
| 0x1FC | 08 |
| 0x1FD | 02 |
| 0x1FE | 55 |
| 0x1FF | AA |
➡️ BIOS 加载过程:
- BIOS 从磁盘读取第一个扇区 (512 字节) 到内存 `0x7C00`。
- 检查 `0x1FE` 和 `0x1FF` 是否是 `0x55AA`,如果不是,加载失败。
- 如果是合法引导扇区,继续执行从 `0x7C00` 开始的引导代码。
- 0x1FC 的设备号会被引导代码读取,用来判断是软盘还是硬盘。
*/
.org 508 ; 偏移地址设置到 0x1FC 位置
root_dev:
.word ROOT_DEV #存放根文件系统所在设备号(init/main.c中会用)
boot_flag:
.word 0xAA55
.text
endtext:
.data
enddata:
.bss
endbss: