地址映射与共享
3.1 实验目的
深入理解操作系统的段、页式内存管理,深入理解段表、页表、逻辑地址、线性地址、物理地址等概念;
实践段、页式内存管理的地址映射过程;
3.2 实验内容
本次实验的基本内容是:
用Bochs调试工具跟踪Linux 0.11的地址翻译(地址映射)过程,了解IA-32和Linux 0.11的内存管理机制;
3.2.1 跟踪地址翻译过程
首先以汇编级调试的方式启动bochs,引导Linux 0.11,在0.11下编译和运行test.c。它是一个无限循环的程序,永远不会主动退出。然后在调试器中通过查看各项系统参数,从逻辑地址、LDT表、GDT表、线性地址到页表,计算出变量i的物理地址。最后通过直接修改物理内存的方式让test.c退出运行。test.c的代码如下:
#include <stdio.h>
int i = 0x12345678;
int main(void)
{
printf("The logical/virtual address of i is 0x%08x", &i);
fflush(stdout);
while (i) //注意是字母i不是数字1
;
return 0;
}
3.3 思考题
完成实验后,在实验报告中回答如下问题:
1.对于地址映射实验部分,列出你认为最重要的那几步(不超过4步),并给出你获得的实验数据。
2.test.c退出后,如果马上再运行一次,并再进行地址跟踪,你发现有哪些异同?为什么?
3.4 实验提示
《注释》中的5.3节和第13章对Linux 0.11的内存管理有详细分析、讲解,很值得一看。
3.4.1 IA-32的地址翻译过程
Linux 0.11完全遵循IA-32(Intel Architecture 32-bit)架构进行地址翻译,Windows、后续版本的Linux以及一切在IA-32保护模式下运行的操作系统都遵循此架构。因为只有这样才能充分发挥CPU的MMU的功能。关于此地址翻译过程的细节,请参考《注释》一书中的2.3.1-2.3.3节。
3.4.2 用Bochs汇编级调试功能进行人工地址翻译
此过程比较机械,基本不消耗脑细胞,做一下有很多好处。
3.4.2.1 准备
编译好Linux 0.11后,首先通过运行dbg-asm.bat启动调试器,此时Bochs的窗口处于黑屏状态,而命令行窗口显示:
====================================================================== Bochs x86 Emulator 2.3.7
Build from CVS snapshot, on June 3, 2008
======================================================================00000000000i[ ] reading configuration from ./bochs/bochsrc.bxrc
00000000000i[ ] installing x module as the Bochs GUI
00000000000i[ ] using log file ./bochsout.txt
Next at t=0
(0) [0xfffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b; ea5be000f0
<bochs:1>_
“Next at t=0”表示下面的指令是Bochs启动后要执行的第一条软件指令。单步跟踪进去就能看到bios的代码。不过这不是本实验需要的。直接输入命令“c”,continue程序的运行,Bochs一如既往地启动了Linux 0.11。
在Linux 0.11下输入(或拷入)test.c,编译为test,运行之,打印如下信息:
The logical/virtual address of i is 0x00003004
只要test不变,0x00003004这个值在任何人的机器上都是一样的。即使在同一个机器上多次运行test,也是一样的。
test是一个死循环,只会不停占用CPU,不会退出。
3.4.2.2暂停
当test运行的时候,在命令行窗口按“ctrl+c”,Bochs会暂停运行,进入调试状态。绝大多数情况下都会停在test内,显示类似如下信息:
(0) [0x00fc8031] 000f:00000031 (unk. ctxt): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
其中加粗的“000f”如果是“0008”,则说明中断在了内核里。那么就要c,然后再ctrl+c,直到变为“000f”为止。如果显示的下一条指令不是“cmp ...”,就用“n”命令单步运行几步,直到停在“cmp ...”。
使用命令“u /7”,显示从当前位置开始7条指令的反汇编代码,如下:
10000031: ( ): cmp dword ptr ds:0x3004, 0x00000000 ; 833d0430000000
10000038: ( ): jz .+0x00000002 ; 7402
1000003a: ( ): jmp .+0xfffffff5 ; ebf5
1000003c: ( ): xor eax, eax ; 31c0
1000003e: ( ): jmp .+0x00000000 ; eb00
10000040: ( ): leave ; c9
10000041: ( ): ret ; c3
这就是test.c中从while开始一直到return的汇编代码。变量i保存在ds:0x3004这个地址,并不停地和0进行比较,直到它为0,才会跳出循环。
现在,开始寻找ds:0x3004对应的物理地址。
3.4.2.3 段表
ds:0x3004是虚拟地址,ds表明这个地址属于ds段。首先要找到段表,然后通过ds的值在段表中找到ds段的具体信息,才能继续进行地址翻译。每个在IA-32上运行的应用程序都有一个段表,叫LDT(本地描述符表),段的信息叫段描述符。
LDT在哪里呢?ldtr寄存器是线索的起点,通过它可以在GDT(全局描述符表)中找到LDT的物理地址。
用“sreg”命令:
cs:s=0x000f, dl=0x00000002, dh=0x10c0fa00, valid=1
ds:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=3
ss:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
es:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
fs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
gs:s=0x0017, dl=0x00003fff, dh=0x10c0f300, valid=1
ldtr:s=0x0068, dl=0xc2d00068, dh=0x000082f9, valid=1
tr:s=0x0060, dl=0x52e80068, dh=0x00008bfd, valid=1
gdtr:base=0x00005cc8, limit=0x7ff
idtr:base=0x000054c8, limit=0x7ff
可以看到ldtr的值是0x0068=0000000001101000(二进制),表示LDT表存放在GDT表的1101(二进制)=13(十进制)号位置(每位数据的意义参考后文叙述的段选择子)。而GDT的位置已经由gdtr明确给出,在物理地址的0x00005cc8。用“xp /32w 0x00005cc8”查看从该地址开始,32个字的内容,及GDT表的前16项,如下:
0x00005cc8 : 0x00000000 0x00000000 0x00000fff 0x00c09a00
0x00005cd8 : 0x00000fff 0x00c09300 0x00000000 0x00000000
0x00005ce8 : 0xa4280068 0x00008901 0xa4100068 0x00008201
0x00005cf8 : 0xf2e80068 0x000089ff 0xf2d00068 0x000082ff
0x00005d08 : 0xd2e80068 0x000089ff 0xd2d00068 0x000082ff
0x00005d18 : 0x12e80068 0x000089fc 0x12d00068 0x000082fc
0x00005d28 : 0xc2e80068 0x00008bf9 0xc2d00068 0x000082f9
0x00005d38 : 0x00000000 0x00000000 0x00000000 0x00000000
GDT表中的每一项占64位(8个字节),所以我们要查找的项的地址是“0x00005cc8 + 13 * 8”。“xp /2w 0x00005cc8 + 13 * 8”,得到:
0x00005d30 : 0xc2d00068 0x000082f9
上两步看到的数值可能和这里给出的示例不一致,这是很正常的。如果想确认是否准确,就看sreg输出中,ldtr所在行里,dl和dh的值,它们是Bochs的调试器自动计算出的,你寻找到的必须和它们一致。
“0xc2d00068 0x000082f9”将其中的加粗数字组合为“0x00f9c2d0”,这就是LDT表的物理地址(为什么这么组合,参考后文介绍的段描述符)。“xp /8w 0x00f9c2d0”,得到:
0x00f9c2d0 : 0x00000000 0x00000000 0x00000002 0x10c0fa00
0x00f9c2e0 : 0x00003fff 0x10c0f300 0x00000000 0x00f9d000
这就是LDT表的前4项内容了。