1 分割源文件(harib03a)
有过编程经验的小伙伴知道:如果很多程序代码都放在一个程序中,对于它的阅读、管理都是较麻烦的,所以这里开始介绍将源文件 bootpack.c 文件分割成几部分。
优点
- 按照处理内容进行分类,将来修改时更容易找到地方。
- Makefile文件写得好,只需要编译修改过的文件,可以提高make的速度。
- 单个源文件都不长,多个小文件比一个大文件好处理。
缺点
- 源文件数量增加
- 分类不当修改时不容易找到地方
分割后的源文件:
对应源文件修改后还需要整理Makefile:
2 整理Makefile(harib03b)
修改之前的Makefile:
使用一般规则可以将上面6个独立的文件生成规则归纳成以下两个一般规则:
一般规则中出现的“$*”符号,可以查阅:
Makefile自动化变量
make.exe会首先寻找普通的生成规则,如果没找到,就尝试用一般规则。所以,即使一般规则和普通生成规则有冲突,也不会有问题。这时候,普通生成规则的优先级更高。比如虽然某个文件的拓展名也是.c,但是想用单独的规则来编译它,也没问题。
3 整理头文件(harib03c)
继续整理源文件,现在的文件大小如下:
源文件中含有重复的函数声明“void io_out8(int port, int data);”等,可以通过统一放在一个头文件bootpack.h中:
头文件中包含了各个文件的函数声明,这样在源文件中就只要包含头文件就可以了:
#include "bootpack.h"
疑问:在包含头文件的时候使用的 < > 与 " " 有什么区别?
双引号(" ")表示该头文件与源文件位于同一个文件夹中,而尖括号(< >)则表示该头文件位于编译器所提供的文件夹里。
这里面用了很多 #define 语句,把用到的地址都只写在了bootpack.h文件里,这么做的好处是因为,如果以后想要变更地址的话,只修改bootpack.h一个文件就行了。
4 意犹未尽
该小节是对昨天的程序代码做解释。
首先说明一下naskfunc.nas的load_gdtr。
这个函数用来指定的段上限(limit)和地址值赋值给名为GDTR的48位寄存器。这是一个很特别的48位寄存器,并不能像我们常用的MOV指令来赋值。给它赋值的方式:指定一个内存地址,从指定的地址读取6个字节(6 * 8 = 48位),然后赋值给GDTR寄存器。完成这一任务的指令,就是LGDT。
该寄存器的低16位(内存的最初两个字节)是段上限,在最初执行的这个函数的时候,DWORD[ESP + 4]里存放的是段上限(即参数limit,调用函数的时候会按照从左到右压入参数,然后再压入返回地址,因此此时SP指向了返回地址4字节,往后移动4字节就是limit参数)
DWORD[ESP + 8]存放的是地址,我们打个比方,用实际的数字来说明代码在做什么。
比如我们传入0x0000ffff以及0x00270000,将它们写出来就是:
MOV AX, [ESP + 4] :将limit参数的值放在寄存器AX中(两字节,16位);
MOV [ESP + 6], AX:对[ESP + 6]内存处赋值,下图展现了赋值过程
LGDT [ESP + 6]:将以[ESP + 6]地址起始的6个字节总计48位写入到 GDTR这个48位寄存器中。
上图为书中作者对此的解释,笔者认为这里数据有点问题,注意第一个红框处:[ FF FF 00 00 00 27 00 ],笔者认为应该是[ FF FF 00 00 00 00 27 00 ], 这里还有待研究。
set_segmdesc函数
函数是按照CPU的规格要求,将段的信息归结成8位写入内存的,这8个字节分别是:
- 段的大小
- 段的起始地址
- 段的管理属性(禁止写入、禁止执行,系统专用等)
为了写入这些信息,我们准备了 struct SEGMENT_DESCRIPTOR 结构体。
struct SEGMENT_DESCRIPTOR {
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
首先看一下段的地址,称为段的基址,所以命名为了base,在结构体中base又被分为了low(2字节),mid(1字节),high(1字节)3个部分,合起来4字节(32位),所以这里按顺序填入数值就行了。
为什么要分为三段呢?
主要是为了与80286时代的程序兼容,有了这样的规格,80286用的操作系统,也可以不用修改就在386以后的CPU上运行。
说一下段上限,表示一个段有多少个字节,这里有个问题:段的表示有32位,那么最大可以表达2 ^ 32 = 4GB,这个数值本身就要占用4个字节,再加上基址(base),一共就要8个字节,这样一来,就没有地方保存段的管理属性信息了,这可不行。
因此段上限只能使用20位,这样的话,段上限最大也只能指定到1MB为止,明明有4GB,却只能用其中的1MB,这。。。不太合理对吧,所以英特尔的叔叔们想了一个办法,在段的属性里设了一个标志位,叫做Gbit。这个标志位为1的时候,limit的单位不解释为字节(byte),而是解释为页(page)。页是什么?在电脑的CPU里,一页指4KB。
这样一来,4KB * 1MB = 4GB,所以可以指定4GB的段。
这20位的段上限分别写到limit_low和limit_high里,一共3字节,即24位,但实际上我们接着要把段属性写入limit_high的高4位,所以最后段上限还是只有20。
最后这12位的段属性,又称为“段的访问权属性”,在程序中用变量名access_right或ar来表示。因为12位段属性中的高4位放在limit_high的高四位里,所以程序有意把ar当作如下的16位构成来处理:
xxxx0000xxxxxxxx(其中x是0或1)
ar的高四位被称为“扩展访问权”,为什么这么说呢,因为这高四位的访问属性在80286的时代还不存在,到386以后才可以使用。这4位是由“GD00”构成的,G是指刚才所说的G bit,D是指段的模式,1是指32位模式,0是指16位模式。这里出现的16位模式主要只用于运行80286的程序,不能用于调用BIOS。所以,除了运行80286程序以外,通常都使用D = 1的模式。
以下内容为作者简要介绍ar的低八位。
5 初始化PIC(harib03d)
PIC指的是“programmable interrupt controller”的缩写,意思是“可编程中断控制器”。PIC与中断的关系可是很密切的,它到底是什么,在设计上,CPU单独只能处理一个中断,这不够用,所以IBM的大叔们在设计电脑时,就在主板上增设了几个辅助芯片。现如今它们已经被集成在了一个芯片组里了。
PIC是将8个中断信号(Interrupt Request,缩写为IRQ)集合成一个中断信号的装置, PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将为一个的输出管脚信号变成ON,并通知给CPU。IBM的大叔们想通过增加PIC来处理更多的中断信号,他们认为电脑会有8个以上的外部设备,所以就把中断信号设计成了15个,并为此增设了2个PIC。
PIC的线路连接:
有关PIC寄存器的简要介绍:
6 中断处理程序的制作(harib03e)
后续暂时没有过多理解,先不放上了,等待后续。。。
感受
今天的课依旧非常的多,所以没有过多的时间放在书上,最多是将书先看了一遍然后再照着书理顺一下自己的思路,毕竟是第二次看,有些地方可能会理解的更好一点,笔者借助这种方式加深自己对原书的理解,就这样稳扎稳打吧,虽然学得慢一些。
不知不觉,明天就周六了,嗯,没课,可以放开心去学习OS了,Over~