📄 bootsect.s
字号:
.model tiny
.386p
;// SYSSIZE是要加载的节数(16字节为1节)。3000h共为30000h字节=192kB
;// 对当前的版本空间已足够了。
SYSSIZE = 3000h ;// 指编译连接后system模块的大小。
;// 这里给出了一个最大默认值。
SETUPLEN = 4 ;// setup程序的扇区数(setup-sectors)值
BOOTSEG = 07c0h ;// bootsect的原始地址(是段地址,以下同)
INITSEG = 9000h ;// 将bootsect移到这里
SETUPSEG = 9020h ;// setup程序从这里开始
SYSSEG = 1000h ;// system模块加载到10000(64kB)处.
ENDSEG = SYSSEG + SYSSIZE ;// 停止加载的段地址
;// DEF_ROOT_DEV: 000h - 根文件系统设备使用与引导时同样的软驱设备.
;// 301 - 根文件系统设备在第一个硬盘的第一个分区上,等等
ROOT_DEV = 301h;//指定根文件系统设备是第1个硬盘的第1个分区。这是Linux老式的硬盘命名
;//方式,具体值的含义如下:
;//设备号 = 主设备号*256 + 次设备号
;// (也即 dev_no = (major<<8 + minor)
;//(主设备号:1-内存,2-磁盘,3-硬盘,4-ttyx,5-tty,6-并行口,7-非命名管道)
;//300 - /dev/hd0 - 代表整个第1个硬盘
;//301 - /dev/hd1 - 第1个盘的第1个分区
;//... ...
;//304 - /dev/hd4 - 第1个盘的第4个分区
;//305 - /dev/hd5 - 代表整个第2个硬盘
;//306 - /dev/hd6 - 第2个盘的第1个分区
;//... ...
;//309 - /dev/hd9 - 第1个盘的第4个分区
;/* ************************************************************************
; boot被bios-启动子程序加载至7c00h(31k)处,并将自己移动到了
; 地址90000h(576k)处,并跳转至那里。
; 它然后使用BIOS中断将'setup'直接加载到自己的后面(90200h)(576.5k),
; 并将system加载到地址10000h处。
;
; 注意:目前的内核系统最大长度限制为(8*65536)(512kB)字节,即使是在
; 将来这也应该没有问题的。我想让它保持简单明了。这样512k的最大内核长度应该
; 足够了,尤其是这里没有象minix中一样包含缓冲区高速缓冲。
;
; 加载程序已经做的够简单了,所以持续的读出错将导致死循环。只能手工重启。
; 只要可能,通过一次取取所有的扇区,加载过程可以做的很快的。
;************************************************************************ */
code segment ;// 程序从_main标号开始执行。
assume cs:code
start: ;// 以下10行作用是将自身(bootsect)从目前段位置07c0h(31k)
;// 移动到9000h(576k)处,共256字(512字节),然后跳转到
;// 移动后代码的 go 标号处,也即本程序的下一语句处。
mov ax,BYTE PTR BOOTSEG ;// 将ds段寄存器置为7C0h
mov ds,ax
mov ax,BYTE PTR INITSEG ;// 将es段寄存器置为9000h
mov es,ax
mov cx,256 ;// 移动计数值 = 256字 = 512 字节
sub si,si ;// 源地址 ds:si = 07C0h:0000h
sub di,di ;// 目的地址 es:di = 9000h:0000h
rep movsw ;// 重复执行,直到cx = 0;移动1个字
; jmp INITSEG:[go] ;// 间接跳转。这里INITSEG指出跳转到的段地址。
db 0eah ;// 间接跳转指令码
dw go
dw INITSEG
go: mov ax,cs ;// 将ds、es和ss都置成移动后代码所在的段处(9000h)。
mov ds,ax ;// 由于程序中有堆栈操作(push,pop,call),因此必须设置堆栈。
mov es,ax
;// put stack at 9ff00. 将堆栈指针sp指向9ff00h(即9000h:0ff00h)处
mov ss,ax
mov sp,0FF00h ;/* 由于代码段移动过了,所以要重新设置堆栈段的位置。
; sp只要指向远大于512偏移(即地址90200h)处
; 都可以。因为从90200h地址开始处还要放置setup程序,
; 而此时setup程序大约为4个扇区,因此sp要指向大
; 于(200h + 200h*4 + 堆栈大小)处。 */
;// 在bootsect程序块后紧跟着加载setup模块的代码数据。
;// 注意es已经设置好了。(在移动代码时es已经指向目的段地址处9000h)。
load_setup:
;// 以下10行的用途是利用BIOS中断INT 13h将setup模块从磁盘第2个扇区
;// 开始读到90200h开始处,共读4个扇区。如果读出错,则复位驱动器,并
;// 重试,没有退路。
;// INT 13h 的使用方法如下:
;// ah = 02h - 读磁盘扇区到内存;al = 需要读出的扇区数量;
;// ch = 磁道(柱面)号的低8位; cl = 开始扇区(0-5位),磁道号高2位(6-7);
;// dh = 磁头号; dl = 驱动器号(如果是硬盘则要置为7);
;// es:bx ->指向数据缓冲区; 如果出错则CF标志置位。
mov dx,0000h ;// drive 0, head 0
mov cx,0002h ;// sector 2, track 0
mov bx,0200h ;// address = 512, in INITSEG
mov ax,0200h+SETUPLEN ;// service 2, nr of sectors
int 13h ;// read it
jnc ok_load_setup ;// ok - continue
mov dx,0000h
mov ax,0000h ;// reset the diskette
int 13h
jmp load_setup
ok_load_setup:
;/* 取磁盘驱动器的参数,特别是每道的扇区数量。
; 取磁盘驱动器参数INT 13h调用格式和返回信息如下:
; ah = 08h dl = 驱动器号(如果是硬盘则要置位7为1)。
; 返回信息:
; 如果出错则CF置位,并且ah = 状态码。
; ah = 0, al = 0, bl = 驱动器类型(AT/PS2)
; ch = 最大磁道号的低8位,cl = 每磁道最大扇区数(位0-5),最大磁道号高2位(位6-7)
; dh = 最大磁头数, 电力= 驱动器数量,
; es:di -> 软驱磁盘参数表。 */
mov dl,00h
mov ax,0800h ;// AH=8 is get drive parameters
int 13h
mov ch,00h
;// seg cs ;// 表示下一条语句的操作数在cs段寄存器所指的段中。
mov cs:sectors,cx ;// 保存每磁道扇区数。
mov ax,INITSEG
mov es,ax ;// 因为上面取磁盘参数中断改掉了es的值,这里重新改回。
;// Print some inane message 在显示一些信息('Loading system ... '回车换行,共24个字符)。
mov ah,03h ;// read cursor pos
xor bh,bh ;// 读光标位置。
int 10h
mov cx,27 ;// 共24个字符。
mov bx,0007h ;// page 0, attribute 7 (normal)
mov bp,offset msg1 ;// 指向要显示的字符串。
mov ax,1301h ;// write string, move cursor
int 10h ;// 写字符串并移动光标。
;// ok, we've written the message, now
;// we want to load the system (at 10000h) 现在开始将system 模块加载到10000h(64k)处。
mov ax,SYSSEG
mov es,ax ;// segment of 010000h es = 存放system的段地址。
call read_it ;// 读磁盘上system模块,es为输入参数。
call kill_motor ;// 关闭驱动器马达,这样就可以知道驱动器的状态了。
;// 此后,我们检查要使用哪个根文件系统设备(简称根设备)。如果已经指定了设备(!=0)
;// 就直接使用给定的设备。否则就需要根据BIOS报告的每磁道扇区数来
;// 确定到底使用/dev/PS0(2,28)还是/dev/at0(2,8)。
;// 上面一行中两个设备文件的含义:
;// 在Linux中软驱的主设备号是2(参加第43行注释),次设备号 = 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驱动器,其设备号是021c
;// 同理 /dev/at0(2,8)指的是1.2M A驱动器,其设备号是0208。
;// seg cs
mov ax,cs:root_dev
cmp ax,0
jne root_defined ;// 如果 ax != 0, 转到root_defined
;// seg cs
mov bx,cs:sectors ;// 取上面保存的每磁道扇区数。如果sectors=15
;// 则说明是1.2Mb的驱动器;如果sectors=18,则说明是
;// 1.44Mb软驱。因为是可引导的驱动器,所以肯定是A驱。
mov ax,0208h ;// /dev/ps0 - 1.2Mb
cmp bx,15 ;// 判断每磁道扇区数是否=15
je root_defined ;// 如果等于,则ax中就是引导驱动器的设备号。
mov ax,021ch ;// /dev/PS0 - 1.44Mb
cmp bx,18
je root_defined
undef_root: ;// 如果都不一样,则死循环(死机)。
jmp undef_root
root_defined:
;// seg cs
mov cs:root_dev,ax ;// 将检查过的设备号保存起来。
;// 到此,所有程序都加载完毕,我们就跳转到被
;// 加载在bootsect后面的setup程序去。
; jmp SETUPSEG:[0] ;// 跳转到9020:0000(setup程序的开始处)。
db 0eah
dw 0
dw SETUPSEG
;//------------ 本程序到此就结束了。-------------
;// ******下面是两个子程序。*******
;// 该子程序将系统模块加载到内存地址10000h处,并确定没有跨越64kB的内存边界。
;// 我们试图尽快地进行加载,只要可能,就每次加载整条磁道的数据
;//
;// 输入:es - 开始内存地址段值(通常是1000h)
;//
sread dw 1+SETUPLEN ;// 当前磁道中已读的扇区数。开始时已经读进1扇区的引导扇区
head dw 0 ;// 当前磁头号
track dw 0 ;// 当前磁道号
read_it: ;// 测试输入的段值。必须位于内存地址64KB边界处,否则进入死循环。
mov ax,es ;// 清bx寄存器,用于表示当前段内存放数据的开始位置。
test ax,0fffh
die:
jne die ;// es值必须位于64KB地址边界!
xor bx,bx ;// bx为段内偏移位置。
rp_read:
;// 判断是否已经读入全部数据。比较当前所读段是否就是系统数据末端所处的段(#ENDSEG),如果
;// 不是就跳转至下面ok1_read标号处继续读数据。否则退出子程序返回。
mov ax,es
cmp ax,ENDSEG ;// have we loaded all yet? 是否已经加载了全部数据?
jb ok1_read
ret
ok1_read:
;// 计算和验证当前磁道需要读取的扇区数,放在ax寄存器中。
;// 根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算如果全部读取这些
;// 未读扇区,所读总字节数是否会超过64KB段长度的限制。若会超过,则根据此次最多能读
;// 入的字节数(64KB - 段内偏移位置),反算出此次需要读取的扇区数。
;// seg cs
mov ax,cs:sectors ;// 取每磁道扇区数。
sub ax,sread ;// 减去当前磁道已读扇区数。
mov dx,ax ;// ax = 当前磁道未读扇区数。
mov cl,9
shl dx,cl ;// dx = ax * 512 字节。
add dx,bx ;// cx = cx + 段内当前偏移值(bx)
;// = 此次读操作后,段内共读入的字节数。
jnc ok2_read ;// 若没有超过64KB字节,则跳转至ok2_read处执行。
je ok2_read
xor ax,ax ;// 若加上此次将读磁道上所有未读扇区时会超过64KB,则计算
sub ax,bx ;// 此时最多能读入的字节数(64KB - 段内读偏移位置),再转换
shr ax,cl ;// 成需要读取的扇区数。
ok2_read:
call read_track
mov dx,ax ;// dx = 该此操作已读取的扇区数。
add ax,sread ;// 当前磁道上已经读取的扇区数。
;// seg cs
cmp ax,cs: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 dx,cl ;// 上次已读扇区数*512字节。
add bx,dx ;// 调整当前段内数据开始位置。
jnc rp_read ;// 若小于64KB边界值,则跳转到rp_read处,继续读数据。
;// 否则调整当前段,为读下一段数据作准备。
mov ax,es
add ax,1000h ;// 将段基址调整为指向下一个64KB段内存。
mov es,ax
xor bx,bx
jmp rp_read
;// 读当前磁道上指定开始扇区和需读扇区数的数据到es:bx开始处。
;// 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表示当前驱动器)。
and dx,0100h ;// 磁头号不大于1
mov ah,2 ;// ah = 2, 读磁盘扇区功能号。
int 13h
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 13h
pop dx
pop cx
pop bx
pop ax
jmp read_track
;///*
;//* 这个子程序用于关闭软驱的马达,这样我们进入内核
;//* 后它处于已知状态,以后也就无须担心它了。
;//*/
kill_motor:
push dx
mov dx,3f2h ;// 软驱控制卡的驱动端口,只写。
mov al,0 ;// A驱动器,关闭FDC,禁止DMA和中断请求,关闭马达。
out dx,al ;// 将al中的内容输出到dx指定的端口去。
pop dx
ret
sectors dw 0 ;// 存放当前启动软盘每磁道的扇区数。
msg1 db 13,10 ;// 回车、换行的ASCII码。
db "Loading my system ..." ;// 我加了my,共有27个字符了
db 13,10,13,10 ;// 共24个ASCII码字符。
org 508 ;// 表示下面语句从地址508(1FC)开始,所以root_dev
;// 在启动扇区的第508开始的2个字节中。
root_dev dw ROOT_DEV ;// 这里存放根文件系统所在的设备号(init/main.c中会用)。
boot_flag dw 0AA55h ;// 硬盘有效标识。
code ends
end
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -