【读书笔记-《30天自制操作系统》-2】Day3

第三天的内容主要在于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语言实现画面显示,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值