原文出处 linux内核0.11完全注释。重写一遍!!
! bootsect.s 2011-04-06
! 处理基本步骤:
! 1. bootsect.s 被BIOS-启动子程序加载至0x7c00(31k)处,并将自己
! 移到了地址0x90000(576k)处,并跳转至那里;
!
! 2. 使用BIOS中断将setup直接加载到自己的后面(0x90200)(576.5k),
! 并将system加载至地址0x10000处;
!
! 注意,目前的内核系统最大长度限制为(8*65536)(512k)字节,即使是
! 在将来也应该没有问题的,我想让它保持简单明了。这样512k的最大内核
! 长度应该是足够的,尤其是这里没有像minix中一样包含缓冲区高速缓冲。
!
! 加载程序已经做的够简单了,所有持续的读出错将导致死循环。只能手工
! 重启。只要可能,通过一次读取素有扇区,加载过程可以做的很快。
SYSSIZE = 0x3000 ! 指编译连接后system模块的大小,这里给出最大的默认值
! 定义了6个全局标识符
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text ! 文本段
begtext:
.data ! 数据段
begdata:
.bss ! 未初始化数据段(Block Started by Symbol)
begbss:
.text ! 文本段
SETUPLEN = 4 ! setup程序的扇区数
BOOTSEG = 0x07c0 ! bootsect的原始地址
INITSEG = 0x9000 ! 将bootsect移到这里
SETUPSEG = 0x9020 ! setup程序从这里开始
SYSSEG = 0x1000 ! system模块加载到0x10000(64kB)处
ENDSEG = SYSSEG + SYSSIZE ! 停止加载的段地址
ROOT_DEV = 0x306 ! 指定根文件系统设备是第2个硬盘第1个分区,
! 这是linux老式硬盘命名方式,具体值的含义如下:
! 设备号 = 主设备号*256 + 次设备号
! (主设备号: 1-内存 2-磁盘 3-硬盘 4-ttyx
! 5-tty 6-并行口 7-非命名管道)
! 一个硬盘有1-4个分区,即次设备号
! 从linux内核0.95版本后已经使用与现在相同的命名方法
entry start ! 告知链接程序,程序从start标号开始执行
start: ! 以下代码是将自身(bootsect)从0x07c0(31k)移动到
! 0x9000(576k)处,共256字(512字节),然后跳转到移动后
! 代码的go标号处
mov ax, #BOOTSEG ! 将es段寄存器置为0x7c0
mov ds, ax
mov ax, #INITSEG ! 将es段寄存器置为0x9000
mov es, ax
mov cx, #256 ! 移动计数值 = 256字
sub si, si ! 源地址 ds:si = 0x07c0:0x0000
sub di, di ! 目的地址 es:di = 0x9000:0x0000
rep ! 重复执行,直到cx = 0
movw ! 移动1个字
jmpi go,INITSEG ! 间接跳转
! 从下面开始,CPU执行已移动到0x9000段处得代码
go:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax ! 将堆栈指针sp指向0x9ff00(0x9000:0xff00)处
mov sp, #0xFF00
! 在bootsect程序块后紧跟着setup模块的代码数据
! 注意es已经设置好了
load_setup:
! 利用BIOS中断INT 0x13将setup模块从磁盘第2个扇区开始读到0x90200开始处,
! 共读4个扇区。如果读出错,则复位驱动器,并重试,没有退路。
! INT 0x13用法如下:
! 读扇区:
! ah = 0x02 - 读磁盘扇区到内存; al = 需要读出的扇区数量;
! ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5位),磁道号高2位(6-7)
! dh = 磁头号; dl = 驱动器号(如果是硬盘则位7要置位);
! es:bx -->指向数据缓冲区; 如果出错则CF标志置位
mov dx, #0x0000
mov cx, #0x0002
mov bx, #0x0200
mov ax, #0x0200+SETUPLEN
int 0x13
jnc ok_load_setup
mov dx, #0x0000
mov ax, #0x0000
int 0x13
j load_setup
ok_load_setup:
! 取磁盘驱动器的参数,特别是每道的扇区数量
! INT 0x13调用格式和返回信息如下:
! ah = 0x08 dl = 驱动器号(如果是硬盘则要置位7为1)
! 返回信息:
! 如果出错则CF置位,并且ah = 状态码
! ah = 0, al = 0, bl = 驱动器类型(AT/PS2)
! ch = 最大磁道号的低8位 cl = 每磁道最大扇区数(位0-5),最大磁道号高2位(bit 6-7)
! dh = 最大磁头数 dl = 驱动器数量
! es:di ---> 软驱磁盘参数表
mov dl, #0x00
mov ax, #0x0800
int 0x13
mov ch, #0x00
seg cs ! 表示下一条语句的操作数在cs段寄存器所指的段中
mov sectors, cx ! 保存每磁道扇区数
mov ax, #INITSEG
mov es, ax ! 因为上面取磁盘参数中断改掉了es的值,这里重新改回
! 显示信息('Loading system ...' 回车换行,共24个字符)
mov ah, #0x03 ! 读光标位置
xor bh, bh
int 0x10
mov cx, #24 ! 共24个字符
mov bx, #0x0007 ! page 0 attribute 7 (normal)
mov bp, #msg1 ! 指向要显示的字符串
mov ax, #0x1301 ! 写字符串并移动光标
int 0x10
! 现在开始将system模块加载到0x10000(64k)处
mov ax, #SYSSEG
mov es, ax
call read_it ! 读磁盘上system模块,es为输入参数
call kill_motor ! 关闭驱动器马达,这样就可以知道驱动器状态了
! 此后,我们检查要使用哪个根文件系统设备(简称根设备)。
! 如果已经指定了设备(!=0)就直接使用给定的设备。否则就需要根据BIOS报告的
! 每磁道扇区数来确定到底使用/dev/PS0(2,28) 还是 /dev/at0(2,8)。
! 说明: Linux中软驱的主设备号是2,次设备号 = type*4 + nr, 其中
! nr为0-3分别对应软驱A、B、C或D;type是软驱的类型(2->1.2M 或 7->1.44M等)
! 因为7*4 + 0 = 28, 所以:
! /dev/PS0(2,28)指的是1.44M A驱动器,其设备号为0x21c,同理
! /dev/at0(2,8) 指的是1.2M A驱动器, 其设备号是0x0208
seg cs
mov ax, root_dev
cmp ax, #0
jne root_defined
seg cs
mov bx, sectors
mov ax, #0x208 ! 如果sectors=15,则说明是1.2M的驱动器
cmp bx, #15
je root_defined
mov ax, #0x021c ! 如果sectors=18,则说明是1.44Mb软驱
cmp bx, #18
je root_defined
undef_root: ! 如果都不一样,死循环,死机
jmp undef_root
root_defined:
seg cs
mov root_dev, ax ! 将检查过的设备号保存起来
! 到此,所有程序都加载完毕,跳转到bootsect后面的setup程序去
jmpi 0, SETUPSEG ! 跳转到0x9020:0000(setup.s程序的开始处)
!!!! 本程序到此结束了
! 下面是两个子程序。
! 该子程序是将system模块加载到内存0x10000处,并确定没有跨越64KB的内存边界。
! 试图尽快的进行加载,只要可能,就每次加载整条磁道的数据
! 输入: es -- 开始内存地址段值(通常是0x1000)
sread: .word 1+SETUPLEN ! 当前磁道中已读的扇区数
! 开始已经读了1扇区的引导扇区,加上setup
head: .word 0 ! 当前磁头号
track: .word 0 ! 当前磁道号
read_it:
mov ax, es
test ax, #0x0fff
die:
jne die
xor bx, bx
rp_read:
mov ax, es
cmp ax, #ENDSEG ! 是否已经加载了全部数据
jb ok1_read
ret
ok1_read:
! 计算和验证当前磁道需要读取的扇区数,放在ax寄存器中
! 根据当前磁道中还未读取的扇区数以及段内数据字节开始偏移位置,
! 计算如果全部读取这些未读扇区所读总字节数,是否会超过64KB段长度的限制。
! 若会超过,则根据此次最多能读入的字节数(64KB - 段内偏移),反算出此次需要读取的扇区数
seg cs
mov ax, sectors ! 取每磁道扇区数
sub ax, sread ! 减去当前磁道已读扇区数
mov cx, ax
shl cx, #9 ! cx = cx * 512
add cx, bx ! cx = cx + 段内当前偏移值(bx)
! 此次读操作后,段内共读入的字节数
jnc ok2_read ! 若没有超过64KB字节,则跳转至ok2_read处执行
je ok2_read
xor ax, ax ! 若加上此次将读磁道删所有未读扇区会超过64KB
sub ax, bx ! 则计算此时最大能读入的字节数(64KB - 段内读偏移位置)
shr ax, #9 ! 再转换成要读取的扇区数
ok2_read:
call read_track
mov cx, ax ! cx = 该次操作已读取的扇区数
add ax, sread ! 当前磁道上读取的扇区数
seg cs
cmp ax, sectors ! 如果当前磁道上的还有扇区未读,则跳转到ok3_read
jne ok3_read
! 读该磁道的下一磁头面(1号磁头)上的数据。如果已经完成,则去读下一磁道
mov ax, #1
sub ax, head ! 判断当前磁头号
jne ok4_read ! 如果是0磁头,则再读1磁头面上的扇区数据
inc track ! 否则去读下一磁道
ok4_read:
mov head, ax ! 保存当前磁头号
xor ax, ax ! 清当前磁道已读扇区数
ok3_read:
mov sread, ax ! 保存当前磁道已读扇区数
shl cx, #9 ! 上次已读扇区数*512字节
add bx, cx ! 调整当前段内数据开始位置
jnc rp_read ! 若小于64KB边界值,则跳转到rp_read,继续读数据
! 否则调整当前段,为读下一段数据做准备
mov ax, es
add ax, #0x1000 ! 将段基址调整为指向下一64KB内存开始处
mov es, ax
xor bx, bx ! 清段内数据开始偏移处
jmp rp_read
! 读当前磁道上指定开始扇区和需读扇区数的数据至es:bx开始处
! int 0x13 ah=2的说明
! al - 需读扇区数; es:bx - 缓冲区开始位置
read_track:
push ax
push bx
push cx
push dx
mov dx, track ! 取当前磁道号
mov cx, sread ! 取当前磁道上已读扇区数
inc cx ! cl = 开始读扇区
mov ch, dl ! ch = 当前磁道号
mov dx, head ! 取当前磁头号
mov dh, dl ! dh = 磁头号
mov dl, #0 ! dl = 驱动器号(为0表示当前A驱动器)
and dx, #0x0100 ! 磁头号不大于1
mov ah, #2 ! ah = 2, 读磁盘扇区功能号
int 0x13
jc bad_rt ! 若出错,跳转至bad_rt
pop dx
pop cx
pop bx
pop ax
ret
! 执行驱动器复位操作(磁盘中断功能号0),再跳转到read_track处重试
bad_rt:
mov ax, #0
mov dx, #0
int 0x13
pop dx
pop cx
pop bx
pop ax
jmp read_track
kill_motor:
push dx
mov dx, #0x3f2
mov al, #0
outb
pop dx
ret
sectors:
.word 0
msg1:
.byte 13,10 ! 回车、换行的ASCII码
.ascii "Loading system..."
.byte 13,10,13,10 ! 共24个ASCII码字符
.org 508 ! 表示下面语句从地址508(0x1FC)开始,所以root_dev
! 在启动扇区的第508开始的2个字节中
root_dev:
.word ROOT_DEV ! 这里存放根文件系统所在的设备号(init/main.c中会用到)
boot_flag:
.word 0xAA55 ! 硬盘有效标识
.text
endtext:
.data
enddata:
.bss
endbss: