由于工(zi)作(shen)繁(lan)忙(duo),有几周没更新了,被大家催更,于是立了flag周末更新,咱不能鸽了哈,这篇文章继续介绍引导的部分。
上篇文章中,给大家实现了初步的引导,利用引导打印一串字符串。
VictorYXL:30天自制操作系统-汇编实现初版镜像zhuanlan.zhihu.com后面的内容自然是要用引导扇区实现对系统的引导,这其中读取磁盘内容就是不可缺少的一步。
在开始完善我们的引导之前,需要知道磁盘结构一些内容,我们的系统是现在模拟软盘镜像中,不过结构是一样的。
软盘和硬盘的组成结构是类似的,都是包含若干磁面,每个磁面都有唯一的磁头用来读取信息,磁面中一圈圈的是磁道,磁道又被分为扇区,所以磁盘的容量=磁面数(磁头数)*磁道数(柱面数)*扇区数*扇区大小,软盘包含2个磁头,80个磁道,18个扇区,每个扇区512,共计1440k,这也回答了我们呢之前遗留的问题-为什么我们的软盘大小是1440k。
读取磁盘需要先知道两个汇编指令,一个是条件跳转,一个是磁盘中断。
CMP A,B
Jxx C
表示如果A,B满足某条件,则跳转到C,如
CMP AL, 0
JE jump
表示如果AL=0则跳转到jump处(写了一半突然发现上篇文章也说了这个,尴尬),JB表示小于则跳转,JBE表示小于等于则跳转等等。
磁盘相关操作的中断是0x13中断,其中AH=0x00, DL=0x00表示磁盘复位,AH=0x02表示读磁盘,此时参数如下
DL 磁盘驱动器号
DH 磁头号
CH 磁道号
CL 起始扇区号
AL 读取扇区数
ES:BX 读取数据后的缓冲区
接着就是完善我们的IPL使得他们读取软盘内容,由于软盘的读写具有不可靠性,我们设定读某扇区超过5次则为失败。
在读磁盘内容前,我们先将之前IPL中msg改为成功或者失败的信息,以便与我们查看结果。
; error
error:
MOV SI, error_msg
JMP print
; error msg
error_msg:
DB 0x0a,0x0a,0x0a
DB "load error"
; succeed
succeed:
MOV SI, succeed_msg
JMP print
; succeed msg
succeed_msg:
DB 0x0a,0x0a,0x0a
DB "load successfully"
内容很简单,就是定义了成功和失败的字串,并配上调用的接口。
接下来是读取磁盘内容的部分,包括寄存器初始化,单个扇区的读写和多个扇区的读写。
; Init register
init:
MOV AX,0
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV AX,0x0820
MOV ES,AX
MOV CH,0
MOV DH,0
MOV CL,2
将之前的entry改成了init,增添了后半段的初始化,其中ES:BX作为后面接受读取内容缓冲区的地址,这里对ES,CH,DH和CL初始化,这里寄存器的值将作用于上文所说的0x13中断。
read_sector:
MOV SI,0
try_read:
MOV AH,0x02
MOV AL,1
MOV BX,0
MOV DL,0x00
INT 0x13
JNC next
ADD SI,1
CMP SI,5
JAE error
MOV AH,0x00
MOV DL,0x00
INT 0x13
JMP try_read
这段逻辑还是比较清晰的,如果用C写就是一个循环。
SI记录读取次数,接着进入循环,每次将AH, AL, BX, DL这些寄存器也按照上文所说参数设定好,然后调用0x13中断,可能有些小伙伴不理解为什么这些参数不再初始化的时候设定,那是因为后面的代会使用这些寄存器,所以这里对这些需要初始化。
接着用0x13中断尝试读取扇区内容,如果成功则跳转到next,是后面要介绍的读取多段扇区的部分,否则就是失败,失败次数加1,并判断如果超过5次则跳转到前面定义的失败内容结束程序,如果未超过5次则用调用前面提到的磁盘复位中断,并尝试下一次读取。
接下来尝试读取更多扇区,可能有小伙伴会问为啥是读前10个柱面的扇区,而不是整个80个柱面,一方面是我懒,原书作者就是只读了10个柱面照搬了,另一方面还是我懒,之前定义引导扇区外空间写的4600个空白字节,(4600+8)/512=9,加上自身引导扇区,刚好是10个,也就不想改了。
next:
MOV AX,ES
ADD AX,0x0020
MOV ES,AX
ADD CL,1
CMP CL,18
JBE read_sector
MOV CL,1
ADD DH,1
CMP DH,1
JBE read_sector
MOV DH,0
ADD CH,1
CMP CH,9
JBE read_sector
JMP succeed
在读取单个扇区成功后进去多扇区的逻辑,其实也很简单,第一段将ES后移0x20个单位,因为BX是4位,最多表达到16,所以ES段寄存器需要增加32,以跳过已经读取的一个扇区。话说我在重写code的时候,把0x0020达成了00020,每次都只能读两个磁道就会死机,而且用虚拟机debug还关不了机,只能重复创建虚拟机,心累。
后面的逻辑就是重复读取扇区,当CL超过18表示该磁头的扇区读完了,扇区号归0磁头号+1,磁头超过1表示该柱面读完了,磁头号归0柱面号+1,直到柱面号超过9表示整个读完。
话说我在写到这的时候,突然感觉不能再懒惰了,遂改写了代码,支持读更多柱面。
将原本写引导扇区外的内容改写成
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB (n-1)*512-8
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 80*2*18*512-n*512-8
n为柱面数。
果然勤奋的人会被上天眷顾。80*2*18*512-512*10-8=1649432,上篇文章我居然鬼使神差的写成了1468432,也是佩服我自己。
就这样,我们的引导能够读取足够多的磁盘内容了,后面就是正式开始写系统,并且被我们的引导加载进内存了。