;这个程序是改写linux的bootsect.s。我用的nasm的语法格式。昨天刚学,今天学完。所以想马上运用一下。
;我想写一个操作系统,现在觉得最简单的方式莫过于先把前辈的实现的东西重新实现一遍。等到对这个
;问题有更深刻认识的时候,再重新思考,写出有自己特色的系统。
;作者:hk0625
;开始时间: 2010年03月18日星期四 21:00
;完成时间: 2010年03月19日星期五 20:55(完成)
;最后修改时间: 2010年04月14日星期三 11:08
;地点:北京化工大学郁夫图书馆文法阅览室小圆桌&北化1#宿舍楼426
;(今天是北化图书馆的一个月一次的闭关日,下午没地方去。呵呵,还是在宿舍吧。)
;声明:下面的注释,很多来自赵炯的《linux-0.11完全注释》,我觉得他注释的不错。
;所以大量借用。在这里,说声谢谢!在前辈的基础上,可以做更多有意义的工作。
;下面let's try!
;把程序加载到内存0x07c0:0000处
org 0x7c00
;假设编译后system模块的大小是 0x30000
SYSSIZE equ 0X3000
global begtext, begdata, begbss, endtext, edndata, endbss
section .text
begtext:
;setup模块的大小,4个扇区
SETUPLEN equ 4
;引导条被装入的位置,0x07c0:0000,从这以下都是段地址
BOOTSEG equ 0x07C0
;将bootsec转移到得位置
INITSEG equ 0x9000
;setup将被载入的的内存地址
SETUPSEG equ 0x9020
;SYSTEM模块将被加载的起始地址
SYSSEG equ 0x1000
;SYSTEM模块加载上限
ENDSEG equ SYSSEG + SYSSIZE
;根文件系统设备号
; 0x000 根文件系统设备使用与引导时同样的软驱设备
; 0x301 根文件系统设备在第一个硬盘的第一个分区上
ROOT_DEV equ 0X301 ;修改,2010年04月14日星期三 11:08
;告知链接程序,程序从start标号开始执行
start:
mov ax, BOOTSEG
; mov ax, cs
mov ds, ax
mov ax, INITSEG
mov es, ax
mov cx, 256 ;256个字等于512字节,O(∩_∩)O~
sub si, si ;源地址 ds:si = 0x07c0:0x0000
sub di, di ;目的地址 es:di = 0x9000:0x0000
rep
movsw
jmp INITSEG:go-0x7c00
go:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0xff00
load_setup:
;下面利用BIOS中断的INT 0X13将setup模块从软盘的第二个扇区
;开始读到0x90200开始处,一共读4个扇区。如果读错,则复位驱动器,
;并不断重试,直到成功。
mov dx, 0x0000 ; dh = 0 磁头号(head), dl = 0 驱动器号(drive)
mov cx, 0x0002 ; ch = 0 磁道号(track), cl = 02 扇区号(sector)
mov bx, 0x0200 ; es:bx = 0x9000:0x0200
mov ax, 0x0200 + SETUPLEN
int 0x13
jnc ok_load_setup
;读取失败,复位软驱。然后重试。
mov dx, 0x0000
mov ax, 0x0000
int 0x13
jmp load_setup
ok_load_setup:
;取软驱的参数,特别是每道的扇区数量。
;INT 13(直接磁盘服务 Direct Disk Service)
;功能08H
;功能描述:读取驱动器参数
;入口参数:AH=08H
; DL=驱动器,00H~7FH:软盘;
; 80H~0FFH:硬盘
;出口参数:CF=1——操作失败,AH=状态代码,参见功能号01H中的说明,
; 否则,BL=01H — 360K
; =02H — 1.2M
; =03H — 720K
; =04H — 1.44M
; CH=柱面数的低8位
; CL的位7-6=柱面数
; CL的位5-0=扇区数
; DH=磁头数
; DL=驱动器数
; ES:DI=磁盘驱动器参数表地址
mov dh, 0x00
mov ax, 0x0800
int 0x13
mov ch, 0x00
mov [sectors - 0x7c00], cx ;将扇区数保存到sectors单元
mov ax, INITSEG ;调用这个BIOS功能会修改es的值
mov es, ax ;所以重新设置es的值是必要的。
;显示一些提示信息
mov ah, 0x03 ;读光标位置
xor bh, bh
int 0x10
;如果有谁不了解这些bios功能调用,我想大家推荐几本书吧。
;王爽编著《汇编语言》,适合当做教材来学。
;殷肖川主编《汇编语言程序设计》附录D中断列表,十分详细。
mov cx, 24 ;字符串大小为24
mov bx, 0x0007 ;第0页,属性为7
mov bp, msg1 - 0x7c00 ;org 0x7c00 让所有的偏移量增加了0x7c00
mov ax, 0x1301
int 0x10
;加载系统模块到0x10000
mov ax, SYSSEG
mov es, ax
call read_it ;读磁盘上system模块,es作为输入参数
call kill_motor ;关闭驱动器马达
;接下来确定使用的根文件系统设备(简称根设备)。如果已经指定了设备,就直接使用
;给定设备。否则这需要根据BIOS报告的每磁道德扇区数来确定到底使用/dev/SP0(2, 28)
;还是 /dev/at0(2, 8)。
mov ax, [root_dev - 0x7c00]
cmp ax, 0x0000
jne root_defined
mov bx, [sectors - 0x7c00]
mov ax, 0x0208
cmp bx, 15
je root_defined
mov ax, 0x021c
cmp bx, 18
je root_defined
undef_root:
jmp undef_root ;死机!
root_defined:
mov [root_dev - 0x7c00], ax
;到此,所有程序加载完毕,我们跳转到被加载在bootsecxt后面setup程序去。
;不过为了编程方便,现在这里暂停。等到我们调试完这个程序的后,需要调试setup时
;再把这条语句注释起来。
jmp SETUPSEG:0
; jmp $ ;这条语句是用来调试用的
;下面是两个子程序
sread: dw 1 + SETUPLEN ;当前磁道中已读的扇区数, 其中1 (bootsec模块)
head: dw 0 ;当前磁头号
track: dw 0 ;当前磁道号
read_it:
;测试输入的段值。从磁盘读入的数据必须存放在位于内存地址64KB的边界处,否则进入
;死循环。 清bx寄存器,用于表示当前段内存放数据的开始位置。
mov ax, es
test ax, 0x0fff ;不太理解?呵呵,现在理解了
die: jne die ;如果es值不是64kb地址边界,则无限循环。如果不太
;理解,建议你应该test指令以及64kb的段地址表示
;这两个概念想清楚
xor bx, bx ;bx为段内偏移,在这里清零。
rp_read:
;判断是否已经读入全部数据。比较当前所读入段是否就是系统数据末端所处的段
;(#ENDSEG),如果不是就跳转至下面ok1_read标号处继续读数据。否则退出子程序返回。
mov ax, es
cmp ax, ENDSEG ;判断数据是否加完?
jb ok1_read
ret
ok1_read:
;计算和验证当前磁道需要读取的扇区数,放在ax寄存器中。
;根据当前磁道还未读取的扇区数以及段内数据字节开始偏移位置,计算机如果全部读取
;这些未读扇区,所读总字节数是否会超过64kb段长度的限制。若会超过,则根据此次
;最多能读入的字节数(64kb - 段内偏移位置),反算出此次需要读入的扇区数。
mov ax, [sectors - 0x7c00] ;每磁道德扇区数,前面获得
sub ax, [sread - 0x7c00] ;减去当前磁道已读扇区数
mov cx, ax ; cx = ax = 当前磁道未读扇区数。
shl cx, 9 ; cx = cx * 512 字节。 2^9 = 512 O(∩_∩)O~
add cx, bx ; cx = cx + 段前偏移量值(bx)
jnc ok2_read ;段偏移量没有超过64kb
je ok2_read ;有点疑惑?;段偏移量刚好等于64kb
xor ax, ax ; ax = 0 =64kb, 呵呵,能理解吧。
sub ax, bx ; ax = 64kb - 段内偏移量
shr ax, 9 ;可以读入的扇区数
ok2_read:
call read_track
mov cx, ax
add ax, [sread - 0x7c00]
cmp ax, [sectors - 0x7c00]
jne ok3_read
mov ax, 1
sub ax, [head - 0x7c00]
jne ok4_read
inc word [track - 0x7c00]
ok4_read:
mov [head - 0x7c00], ax
xor ax, ax
ok3_read:
mov [sread - 0x7c00], ax
shl cx, 9
add bx, cx
jnc rp_read
mov ax, es
add ax, 0x1000
mov es, ax
xor bx, bx
jmp rp_read
;读当前磁道上指定开始扇区和需要读取扇区数的数据到es:bx开始处。
read_track:
pusha
pusha
push ax
push bx
mov ax, 0xe2e ;loading ... message 2e=.
mov bx, 7
int 0x10
pop bx
pop ax
popa
push ax
push bx
push cx
push dx
mov dx, [track - 0x7c00] ;获取当前磁道号
mov cx, [sread - 0x7c00] ;获取已读扇区号
inc cx ;扇区号加 1
mov ch, dl ;将磁道号送往ch,至此ch保存磁道号,而cl保存扇区号
mov dx, [head - 0x7c00] ;这两句的作用将磁头号(即面号)送往dh
mov dh, dl
mov dl, 0 ;原来是mov dh, 0 。错了,呵呵
;这个错在后面在加载system文件时出错。将驱动器号设置为0
and dx, 0x0100
mov ah, 2
int 0x13
jc bad_rt
pop dx
pop cx
pop bx
pop ax
popa
ret
;执行驱动器复位操作(磁盘中断功能号0), 在跳转到read_track处重试。
bad_rt: mov ax, 0
mov dx, 0
int 0x13
pop dx
pop cx
pop bx
pop ax
popa
jmp read_track
;这个子程序用于关闭软驱的马达,这样我们进入内核后它处于已知状态,
;以后也就无须担心它了。
kill_motor:
push dx
mov dx, 0x3f2 ;软驱控制卡的驱动端口,只写。
mov al, 0 ;A软驱动器,关闭FDC,禁止DMA和中断请求,关闭马达。
outb ;将al中的内容输出到dx指定的端口去。
pop dx
ret
sectors: dw 0 ;存放当前驱动软盘的扇区数
msg1: db 13, 10 ;回车, 换行
db 'Loading system ...';一共有二十四个ascii
db 13, 10, 13, 10 ; 2 + 18 + 4 = 24
times 508 - ($ - $$) db 0
root_dev dw ROOT_DEV ;这里存放根文件系统所在的设备号
dw 0xaa55
endtext:
enddata:
section .bss
begbss:
endbss:
;
;注:这是我重写整个linux-0.11代码。现在代码已经有1万多行了吧。我会慢慢公布。
;由于考虑重写,为了便于与原来的代码比较。所以我没有对这个代码结构进行精简。