在上一篇中,我们已经实现了由引导程序加载FAT16系统上的引导装载程序。从现在开始我们将开始编写这个引导装载程序RMOSLDR。这个程序将由2部分组成。第一部分依然是一个汇编语言的程序。它将会把CPU切换到保护模式,并且设置C++的运行环境。而RMOSLDR的第二部分将由C++编写,这段C++代码将会负责把PE格式的操作系统内核从文件系统中加载到内存。
在编写这段程序之前,我们还是要先了解一下必要的背景知识。首先,我们要做的一件事是内存大小的检测。
在实模式下,我们可以通过BIOS来取得系统内存的大小。系统BIOS INT15功能一共提供了3种方式来读取内存大小。
方式一(88h):
这是最古老的BIOS功能之一,它将通过AX返回扩展内存的大小,以KB为单位。由于返回值在16位的AX寄存器中,当系统中的内存大于64MB的时候,这种方式最多只能返回64MB。更糟糕的是,由于这是一个古老的功能,在某些机器上BIOS最多只返回16MB内存。
使用这个方式的好处是,所有的机器都支持这个功能。
输入:
AH 88h
输出:
CF 置位表示出错
AX 内存大小,以KB为单位
方式二(E801h):
这种方式可以返回多至4G的内存大小。但是这种方式通用性不如88h。在一些古老的机器上,BIOS并不支持这种方式。
输入:
AX E801h
输出:
CF 置位表示出错
AX 1MB-16MB空间的扩展内存大小,以KB为单位
BX 16MB-4GB空间的扩展内存大小,以64K位单位
CX 1MB-16MB空间的Configuration Memory(不知如何翻译)大小,以KB为单位
DX 16MB-4GB空间的Configuration Memory(不知如何翻译)内存大小,以64K位单位
我不知道Configuraition Memory和扩展内存有什么区别,但是BIOS对它们返回一样的值。
方式三(E820h):
这个功能返回机器上安装的内存以及BIOS保留地址的映射表. 这个功能的调用方式和前两个不太一样。这个功能需要调用这调用多次。每次调用将返回一块被映射的内存空间。返回值表明了那块空间的起始地址和大小以及那块空间的类型(物理内存/BIOS保留)。
输入:
EAX E820h
EBX 第一次调用时必须为0,后续调用必须将EBX设置成前一次的返回的EBX值
ES:DI 缓冲区指针,返回的数据存放在这里
ECX 缓冲区大小
EDX 必须为字符串'SMAP'
输出:
CF 置位表示错误
EAX 字符串'SMAP'
ES:DI 缓冲区指针,和输入一样
ECX 缓冲区大小
EBX 为下一次调用使用
返回的数据结构。
每次调用E820h功能时,BIOS将在缓冲区内填入一个数据结构,这个数据结构可以用下面的C++结构表示
#pragma pack(1)
struct
{
DWORD64 baseAddress; // 内存区域基地址
DWORD64 size; // 内存区域大小
WORD type; // 内存类型, 1表示可用内存,2表示保留空间,其他值没有定义
};
我们可以看到,baseAddress和size都是64位的,也就是说这个功能最多可以报告2的64次方字节的内存。
在上面所说的3种方式中,我们可以看到,E820功能是最强大的。但是支持的机器也是最少的。不过幸好,近年来新出的机器都支持这个功能。我们接下来就打算用这个功能来报告系统中内存的大小。