购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.4 检测物理内存

检测物理内存,实际上借助BIOS中断15H/E820完成。获得的内存块存储在物理地址0x70000,是以数组的形式存储。SU并不对物理内存进行管理,它只管收集物理内存,内存的管理要到Loader之后,这是第4章的内容。

计算机中的物理内存分成若干区域,有些区域是可用的,还有少量区域则被保留。SU将调用ConstructMemoryDescriptors函数来检测可用的内存区域,生成一个内存描述符链表结构,由MemoryDescriptorList链表指针所指。这条内存描述链将被后面的Loader使用。内存描述链的每个表项都是MEMORY_DESCRIPTOR结构,用来描述一块内存区域的基址及长度。内存描述符结构如下:

这个结构提供两个字段,BlockBase指明物理内存的起始地址,BlockSize指明物理内存块的大小。这里还有另一个提供给BIOS中断15H/E820使用的结构,用来检测和返回物理内存块的信息,它描述一块物理内存的具体信息:

E820FRAME也包括Descriptor结构,为什么这样设计呢?因为,E820FRAME为链表所使用,而Descriptor则专门为INT 15H/E820所使用,它们共用一个结构空间。

下面给出ConstructMemoryDescriptors函数的C语言代码,让大家能从整体上把握物理内存检测的主要步骤。

内存描述链表指针MemoryDescriptorList指向的第1个表项(即MEMORY_DESCRIPTOR结构)的BlockBase和BlockSize均先初始化为0,指示第1个内存块为空,也就表示当前没检测到内存块。

接下来进入循环检测物理内存阶段。调用Int15E820函数进行物理内存块的枚举(其内部使用BIOS中断INT 15H/E820)。循环条件是E820Frame.Key,当该值不为0时,表示还有物理内存块存在,需要继续进行检测,这个Key值是调用INT 15H后由ebx寄存器返回的结果值(读者可以参考关于BIOS中断15H的详细用法)。

Key值为0时,表示已无物理内存块存在,可以停止检测。调用Int15E820函数返回一块物理内存的具体信息,若该物理内存信息无错误,计算该块物理地址的起始地址和结束地址,因为这里最多只支持4GB内存,所以内存地址的高字节只能为0。若参数值MemoryType为BiosMemoryUsable(1),表示该物理内存可用,那么调用InsertDescriptor函数就可以将该物理内存块插入到物理内存描述链表中。

接下来我们学习该函数的C语言代码对应的汇编代码,同函数LoadSector一样将其转换为汇编:

对于上面的代码,我们做以下几点解释:

1.equ指令。在代码的开始给出了一些参数定义,这里的equ是一个代码替换指令,可以理解成C语言里面的宏定义,将equ后面的值赋给对应参数。

2.push DESCRIPTOR_ADDRESS>>4,因为物理内存链表最开始是固定在70000h位置的,但是要注意的是,当前SU运行在实模式下,处理器的寄存器都是16位的,因此在这里需要将DESCRIPTOR_ADDRESS右移4位,由原本的20位变成16位,以便在下一条指令时能赋值给es。

3.Int15E820函数调用。我们调用Int15E820返回一块物理内存块的描述信息,主要是通过BIOS int 15H指令完成的。在调用int 15H之前需要对寄存器做一些设置:

· eax=e820。

· ebx存放后续值,目的是得到下一块物理内存段,事实上是上一次调用Int15E820函数的返回值,若是第一次使用,则必须设为0。在这里,指的就是E820Frame中的Key。

· ecx=缓冲区结构的大小,指的是E820Frame中的Size。

· edx="SMAP",主要供BIOS对系统映像信息进行校验。

· di=缓冲区结构指针,指的是E820Frame中的BaseAddrLow。

调用完Int15E820之后需要将E820Frame.Key值写回ebx中,以供下次调用使用。

Int15E820的具体实现代码如下,这是个实模块下的中断调用,没法写成C语言函数:

当通过调用Int15E820返回一块物理内存的描述信息之后,若检测到该块物理内存是可用的,那么我们会将该块内存通过调用InsertDescriptor函数插入到物理内存描述链表。InsertDescriptor函数会根据情况将内存块信息写入内存链表项里(MEMORY_LIST_ENTRY结构)。InsertDescriptor函数将从链表头开始遍历,找到当前一个空闲内存链表项,其工作流程大致如下:

假如当前内存链表项的BlockSize为0,则表示当前的内存链表项为空,需要将插入的内存块的base与size写入这个内存链表项。

假如当前内存链表项的BlockSize大于0,需要检查插入的内存块与当前内存链表项记录的内存块是否相邻。若相邻,则需要合并内存块;若不相邻,则继续搜索,重复上面两步工作,直到写入内存块信息为止。

内存链表位置在0x70000,在设计时已指定。内存链表项的分配方式是直接递增指针,找到下一个链表项结构(即CurrentEntry++)。

站在插入内存块的角度,相邻分为底部相邻及顶部相邻两种。底部相邻时,需要将当前内存链表项的BlockSize值增加。而顶部相邻时,需要将当前内存链表项的BlockBase改写为插入内存块的base值。

InsertDescriptor函数对应的汇编代码如下: 4N8MMdkMdf/w5xtAGk8qxnEt2MlufoLGoiXSx8/6JaHWgm7/YHs6Is89dMFHtQfu

点击中间区域
呼出菜单
上一章
目录
下一章
×