第三天的内容主要在于IPL的实现,在完成IPL之后又为进入32位模式与导入C语言做了准备。
1. 磁盘结构
IPL的作用是读取操作系统程序,所以IPL最主要的工作就是读取磁盘,将程序读取到内存中。先来看一下磁盘的结构与读取方式。
磁盘的样子也类似于这样一个圆柱体,实际读取数据时是将磁盘划分为一圈一圈,每一圈称为一个柱面,每个柱面又被平均划分为扇区。磁盘的正反面都可以存储数据,一个柱面的正反面可以通过两个磁头分别进行读取。
本书使用的是软盘,对于软盘来说,柱面的编号为0-79,共80个。而每一个柱面又被平均分为18个扇区,每个扇区的大小是512字节,这样一张磁盘的字节数
80x18x512x2 = 1474560
换算为KB则恰好是1440KB。
2. 磁盘读取程序
2.1 读取一个扇区
了解了磁盘的结构,继续来看读取磁盘的程序。
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
MOV AH,0x02 ; AH=0x02 :读盘
MOV AL,1 ; 读1个扇区
MOV BX,0
MOV DL,0x00 ; 驱动器号
INT 0x13 ; 调用BIOS
JC error
核心的代码其实很简单,也是要通过调用BIOS。这段程序做的主要是两件事:
(1)设置内存地址用于保存硬盘中读出的内容;
(2)设置读盘参数,调用BIOS读盘;
如果我们用一个寄存器来表示地址,由于只有16位,则最多也只能表示65536个地址,也就是64K。为了扩展寻址范围,上一篇提到的ES寄存器也就派上用场了。
这里使用ES:BX的方式来确定地址,即
ES*16 + BX
这样寻址范围就扩大到了1024K,也就是1M。对于现在至少也有几个G内存的电脑来说仍然是小的可怜。不过当时的设计者们谁也没料到计算机能有今天的发展,1M的内存在当时就已经很够用了。
前几条指令将ES设置为了0x0820,将BX设置为0,即将读取出的数据保存在内存中0x8200地址开始的地方。接下来则是设置读盘参数,分别指定柱面、磁头、扇区的编号,以及读取扇区的个数。对于多个磁盘,还需要指定驱动器号,这里默认为0。参数设置好之后,既可以通过INT 0x13指令调用BIOS进行读盘了。
最后出现了一条新指令JC error。JC是jump if carry的缩写,即如果进位标志被置位,则进行跳转,是用来检测读取是否出错的。如果读取出错,会将进位标志置位。
2.2 增加重试机制
软盘读取出错的概率比较高,因此这里还增加了重试的机制。这里用一个寄存器SI来记录读取出错的次数。JNC代表jump if not carry,即进位标志没有置位,此时跳转到fin,表示读取成功,继续下一步处理,否则将SI增加1。
JAE代表jump if above or equal,即如果SI >= 5,则跳转到error,表示重试此时超过5次之后停止重试并报错。否则执行
MOV AH, 0x00
MOV DL, 0x00
INT 0x13
来复位软盘状态,并跳转回retry重新读取。
MOV AX,0x0820
MOV ES,AX
MOV CH,0
MOV DH,0
MOV CL,2
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02
MOV AL,1
MOV BX,0
MOV DL,0x00
INT 0x13
JNC fin ;读取成功,跳转到fin
ADD SI,1 ;读取失败,次数加1
CMP SI,5 ;比较失败次数
JAE error ;失败次数>=5,跳转到error
MOV AH,0x00 ;
MOV DL,0x00 ;
INT 0x13 ;这三条指令为复位软盘状态
JMP retry ;跳转回retry,继续重试读盘
2.3 读取18个扇区与10个柱面
实现了读取一个扇区的程序,继续读取也就顺理成章了,接下来先增加了读取多个扇区的程序,在以上程序的基础上增加了next后的代码代码:
MOV AX,0x0820
MOV ES,AX
MOV CH,0
MOV DH,0
MOV CL,2
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02
MOV AL,1
MOV BX,0
MOV DL,0x00
INT 0x13
JNC fin ;读取成功,跳转到fin
ADD SI,1 ;读取失败,次数加1
CMP SI,5 ;比较失败次数
JAE error ;失败次数>=5,跳转到error
MOV AH,0x00 ;
MOV DL,0x00 ;
INT 0x13 ;这三条指令为复位软盘状态
JMP retry ;跳转回retry,继续重试读盘
next:
MOV AX,ES
ADD AX,0x0020
MOV ES,AX ; 通过以上三条指令将内存地址增加0x200,即后移512字节,为读取下一个扇区做准备
ADD CL,1 ; 将CL增加1
CMP CL,18 ; 比较CL与18,读取18个扇区
JBE readloop ; 未读满18个扇区,继续读取
新增的程序也比较简单,每成功读取一个扇区之后,将内存地址向后移动512字节,为准备读入的下一个扇区腾空间,并用CL存储成功读取的扇区数,未达到想要读取的数量(这里设置为18)则继续循环读取。
说句题外话,在C语言里面除非不得已是不用go to这种跳转指令的,以上的实现用for循环会很简单。但是编译成机器语言之后,也会像汇编语言这样有很多跳转吧。
这里为什么不一次读入多个扇区而是选择一个一个扇区循环读取呢?因为BIOS的0x13功能有限制,读取时不能跨过多个磁道,一次读取也不能超过64KB。这对于以上的读取其实是没有影响的,但如果读取的内容比较多,或者恰好在两个相邻的柱面上,就会有问题了。作者这里应该是为了简化,预先把这个问题规避掉了。
在此基础上就是读取多个柱面了。在此基础上可以形成一个读取10个柱面的启动区的代码:
CYLS EQU 10
ORG 0x7c00
JMP entry
DB 0x90
DB "HARIBOTE"
DW 512
DB 1
DW 1
DB 2
DW 224
DW 2880
DB 0xf0
DW 9
DW 18
DW 2
DD 0
DD 2880
DB 0,0,0x29
DD 0xffffffff
DB "HARIBOTEOS "
DB "FAT12 "
RESB 18
entry:
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
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02
MOV AL,1
MOV BX,0
MOV DL,0x00
INT 0x13
JNC fin ;读取成功,跳转到fin
ADD SI,1 ;读取失败,次数加1
CMP SI,5 ;比较失败次数
JAE error ;失败次数>=5,跳转到error
MOV AH,0x00 ;
MOV DL,0x00 ;
INT 0x13 ;这三条指令为复位软盘状态
JMP retry ;跳转回retry,继续重试读盘
next:
MOV AX,ES
ADD AX,0x0020
MOV ES,AX ; 通过以上三条指令将内存地址增加0x200,即后移512字节,为读取下一个扇区做准备
ADD CL,1 ; 将CL增加1
CMP CL,18 ; 比较CL与18,读取18个扇区
JBE readloop ; 未读满18个扇区,继续读取
MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ; DH < 2
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ; CH < CYLS
读完18个扇区之后,将CL重置为1,然后根据DH来判断是否完成一个柱面的读取。DH中存放的是磁头编号,0和1分别代表一个柱面的正反面。读完正面的18个扇区之后,将DH增加1,继续读取反面的数据。JB表示jump if below,如果DH达到了2,说明这一柱面的正反面都已读取完毕,可以继续读取下一个柱面。CH中存放的是柱面号,这只需将CH加1里去,即从下一个柱面开始读取。最终比较CH是否达到定义的柱面数CYLS即可。
这样启动区就完成了。
3. 进入32位模式前的准备工作
后续需要进入功能更为强大的32位模式,但32位模式与16位模式使用的是不同的机器语言,进入32位模式之后,以上16位的功能,包括BIOS都无法继续使用,因此在进入32位模式之前需要把通过BIOS进行的设置都完成。新增加的代码如下:
; BOOT_INFO相关
CYLS EQU 0x0ff0 ; 设定启动区
LEDS EQU 0x0ff1
VMODE EQU 0x0ff2 ; 关于颜色数目的信息。颜色的位数
SCRNX EQU 0x0ff4 ; 分辨率的X(screen x)
SCRNY EQU 0x0ff6 ; 分辨率的Y(screen y)
VRAM EQU 0x0ff8 ; 图像缓冲区的开始地址
ORG 0xc200 ; 程序装在的位置
MOV AL,0x13 ; VGA显卡,320x200x8位彩色
MOV AH,0x00
INT 0x10
MOV BYTE [VMODE],8 ; 记录画面模式
MOV WORD [SCRNX],320
MOV WORD [SCRNY],200
MOV DWORD [VRAM],0x000a0000
; 用BIOS取得键盘上各种LED指示灯的状态
MOV AH,0x02
INT 0x16 ; keyboard BIOS
MOV [LEDS],AL
fin:
HLT
JMP fin
这里主要做了两件事。
(1)设置画面显示模式。通过INT 10调用BIOS,参数为AH = 0x00, AL = 0x13时,对应的是VGA图形模式,320 x 200 x 8位彩色模式,调色板模式。
(2)获取键盘上的指示灯状态。
在获取完以上信息之后,还将这些信息保存了起来。
VRAM指的是显卡内存,这一块内存用来在画面上显示图案。在上面这种画面模式下,对应的内存地址为0xa000-0xaffff,共64KB.
4. 导入C语言
完成以上准备工作之后,就可以切换到32位模式并导入C语言了。为了调用C语言,作者还写了大量的汇编代码,又怕读者当前无法理解,于是放在后面去讲解了,那么这一部分就在后面的内容讲到时再进行整理。这里讲的关于C语言的内容其实主要是在一个main函数中调用汇编语言函数,算是先开个头。
首先是汇编语言程序,用于实现HLT:
; naskfunc
; TAB=4
[FORMAT "WCOFF"] ; 制作目标文件的模式
[BITS 32] ; 制作32位模式用的机器语言
;制作目标文件的信息
[FILE "naskfunc.nas"] ; 源文件名信息
GLOBAL _io_hlt ; 程序中包含的函数名
; 以下是实际的函数
[SECTION .text] ;目标文件中写了这些之后再写程序
_io_hlt: ; void io_hlt(void);
HLT
RET
对于汇编语言编译成的目标文件,需要链接到C语言编译成的目标文件,才能形成可执行文件。需要注意几点,文件中需要先声明源文件名的信息,再声明下面的函数名。需要链接的函数名前面需要加上"_"与GLOBAL编号。这样在C文件中可以这样调用:
void io_hlt(void);
void HariMain(void)
{
fin:
io_hlt(); /*执行naskfunc.nas中的_io_hlt函数 */
goto fin;
}
了解了如何在C语言中调用汇编语言的函数,在第四天的内容中作者将使用C语言实现画面显示,敬请期待。
标签:读取,读书笔记,18,30,Day3,MOV,扇区,柱面,AX From: https://blog.csdn.net/Ocean1994/article/details/141112786