深度探索Linux系统虚拟化:原理与实现
上QQ阅读APP看书,第一时间看更新

2.2.1 内核是如何获取内存的

为了更好地理解kvmtool如何模拟BIOS中的获取内存信息的0x15号中断服务,我们首先在这一节从使用者,即Linux内核的角度具体体会一下上层软件是如何使用BIOS获取内存信息的。对于eax寄存器为0xE820的0x15号中断,每次中断处理函数都将返回给上层调用者一个内存段的信息,需要为0x15号中断的处理函数准备的相关参数如下:

1)存储e820记录的内存地址。每次发起0x15号中断时,中断处理函数会将一条e820记录复制到es:di指向的内存处。所以在首次发起中断前,需要设置es:di指向保存e820记录的内存地址。然后在后续每次发起中断前,都需要将di寄存器增加一个e820记录大小的偏移,即下一个存储e820记录的位置。

2)e820记录的索引。每次调用时,调用者需要告知0x15号的中断的处理函数获取哪个e820记录,这个索引使用ebx寄存器传递。首次调用时,调用者需要将ebx设置为0,即从第1个e820记录开始。然后每调用一次,如果还有e820记录没有读取完毕,中断处理函数会将ebx寄存器增加1,即指向下一个e820记录。当所有e820记录都复制完成后,中断处理函数会将ebx寄存器设置为0,上层调用者可以根据这个寄存器的值确认是否所有e820记录已经读取完毕。

3)每个e820记录的大小。需要调用者通过ecx寄存器告知中断处理函数。

4)需要按照0x15号中断的约定将edx寄存器设置为魔数0x534D4150。

内核中的具体代码如下:


linux-2.3.16/arch/i386/boot/setup.S
01 meme820:
02     mov edx, #0x534d4150        ! ascii `SMAP'
03     xor ebx, ebx            ! continuation counter
04
05     mov di, #E820MAP            ! point into the whitelist
06 …
07 jmpe820:
08     mov eax, #0x0000e820        ! e820, upper word zeroed
09     mov ecx, #20            ! size of the e820rec
10     …
11     int 0x15                ! make the call
12     …
13 good820:
14     …
15     mov ax, di
16     add ax, #20
17     mov di, ax
18
19 again820:
20     cmp ebx, #0         ! check to see if ebx is
21     jne jmpe820         ! set to EOF

linux-2.3.16/   include/asm-i386/e820.h
22 #define E820MAP 0x2d0       /* our map */

第5行代码设置了存储e820记录的内存地址,宏E820MAP的定义在第22行代码处,可见这个地址位于内核中所谓的零页(zero page)。内核在实模式下通过BIOS获取的一些信息存储在这里,在内核进入保护模式后,会到这里读取具体的信息。

在每次0x15中断后,内核会将di寄存器增加一个e820记录的尺寸,即20个字节,指向保存下一个e802记录的地址,见代码第15~17行。

中断处理函数从寄存器ebx获取内核读取的e820记录的索引,显然,ebx寄存器应该从0开始,第3行代码将该寄存器初始化为0。每执行一次中断后,内核会判断ebx寄存器中的值是否为0,如果非0,就说明还有e820记录没有读完,跳转到标号jmpe820处进入下一个循环,读取下一个e820记录,见第20、21行代码。当完成全部e820记录的读取后,中断处理函数会将ebx寄存器设置为0,内核结束读取过程。

内核通过ecx寄存器告知中断处理函数的e820记录的大小,见第9行代码。由此可见,一个e820记录占据20个字节大小。