这一篇,是重点!咱们将去讲解操做系统根据代码(逻辑)地址去访问真实物理地址的全过程。操作系统
将把全面几节的东西所有用上,并彻底梳理,完善细节。翻译
前面讲了分段、分页机制,他们均可以实现,从虚拟地址(地址空间)向物理地址的转换。可是,实际使用过程当中,使用的是分段+分页机制,段页结合。3d
咱们如今采用边实验边讲解翻译全过程。调试
写了一段 c 代码,编译,而后在 Linux 0.11 中,进行调试code
#include <stdio.h> int i = 0x12345678; int main(void) { printf("The logical/virtual address of i is 0x%08x", &i); fflush(stdout); while (i) ; return 0; }
注意:咱们程序中的变量 i 的大小为 0x12345678。blog
咱们想作的是,经过编译,找到变量 i 的逻辑地址,而后通过一系列的地址转换,得到物理地址。经过查看物理地址的内容,是不是 0x12345678。索引
将运行的代码进行反编译,能够看到 cmp dword ptr 这一部分。这一部分,对应的就是上面c语言的 while(i) 部分。进程
能够看到熟悉的 ds:0x3004
,这是什么?内存
这就是咱们以前分段章节里面的间接寻址。也就是说,咱们要找到 ds 段的基址,而后加上3004的偏移量。io
这里的 ds:0x3004 就是这一部分。你会发现 0x3004 只有16位啊,下图的偏移量标记的是32位。
由于在 Linux 0.11中,给每一个进程划分了 64M 的虚拟内存,2的16次方就是64M。
下图中的偏移量位32位,是给每一个进程划分了 4G 的虚拟内存。
注意:看下图红色方框部分,其中的0-15位选择符用来选择程序中的段的。后面的0-31偏移值,是每一个段中的偏移量。
分段机制,假设一个程序中有不少个段(个数由选择符的位数决定),并且每一个段均可以占有一个大小的空间(由偏移值位数决定)。
在下图中,因为选择符0-15中只有14位用来指定段的,因此下图中的虚拟地址,能够指定214个段,每一个段能够有4G(232)的大小空间。
从上面,咱们得到变量 i 的虚拟地址为 ds: 0x3004。
经过下图,咱们查看寄存器,能够得到ds=0x0017,因此ds:0x3004=0x0017: 3004。
咱们来看ds=0x0017的解读。
这其实也叫选择符,看下图。
重点看,TI 位,也就是2号位。0x0017=0x 17 = 0x 0001 0111,也就是 TI 位为1。
当 TI 为0时,说明咱们要找的有关段表信息就在 GDT表中,咱们能够经过继续对 0x0017的3-15位进行解读,获取有关段表信息在 GDT表中的索引。
当 TI 为1的时候,说明咱们要找的 有关段表信息 在 LDT表中。
每一个段都有一个段描述符。
段描述符指定段的大小、访问权限和段的特权级、段类型以及段的第一字节在线性地址空间中的位置(也就是段基址)。
GDT表,是全局描述表。从这里的 描述 二字与上面的 段描述符能够看出:GDT表中保存着上面提到的段描述符。
LDT表,是局部描述表。里面也保存着段描述符。
此寄存器,记录着 GDT表的基址。
跟咱们以前说的选择符是同样的,它代表了 LDT表在 GDT表中的位置。
咱们能够这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。
LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不一样,LDTR的内容是一个段选择子。因为LDT自己一样是一段内存,也是一个段,因此它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。
注意,LDT表中也保存着描述符,是咱们须要的。
也就是说,咱们首先要获取 LDT表的描述符,而后在 LDT表中获取咱们须要的段描述符。
咱们已经知道,咱们的段选择符为ldtr。
因此,咱们如今得得到 GDTR 和 LDTR寄存器中的内容。
能够看到,LDTR寄存器中的值为 0x0068, GDTR寄存器中的值为 0x00005cb8。
因此,咱们将0x0068=0000 0000 0110 1000,咱们保留3-15位,1101=13。
因此咱们如今知道了,咱们须要的段描述符在GDT表开始位置的第13个位置处。
咱们在GDT表得到偏移13个位置处的内容。
咱们已经得到了段描述符的内容了,离目标愈来愈近了。只要解读出段描述符的内容,咱们就能够得到段表的基址了。
其解读以下,咱们利用上面的结果,并结合下图,去得到基址。
因此,咱们得到 LDT表的物理地址为0x00fd52d0。
就像咱们以前谈到的,LDT表存储的也是段描述符。
因此咱们也须要像以前那样,去获取相应位置的段描述符,而后进行解读。
还记得咱们以前的ds=0x0017嘛?
0x0017=0x 17 = 0x 0001 0111,其中索引为2。
如今咱们得到 LDT表基址开始处的内容。
由于索引都是从0开始的,因此获取的段描述符为 0x00003ffff 0x10c0f300。
解读方式如上,这样咱们求得段表的基址为0x10000000。
以后,将段基址与偏移量相加,便可得到线性地址。0x10003004。
由于采用了多级页表,因此分页页目录和页表。其中位数解读,如图所示。
注意,页目录的基址存储在 cr3 寄存器中。
以下图,咱们得到的页目录表的基址为0x0。
说明页目录表的基址为 0。
由于0x10003004=0x 0001 0000 0000 0000 0011 0000 0000 0100。
因此知道,目录为 0001 0000 00 ,为64。
页面为 00 0000 0011,为3。
偏移为0000 0000 0100,为4。
咱们要得到页目录号为64的内容:
能够看到基址为12到31为,因此地址为0x00fa5000。
页表所在物理页框为0x00fa5000位置,从该位置开始查找3号页表项,获得:
这个解读同上,因此最后得到的基址为0x00f99000。
加上前面提到的偏移4,最终的物理地址为:0x00f99004。
最后,咱们查看这个物理地址的内容,发现,是咱们程序中设置的i的值。