内存访问全过程

这一篇,是重点!我们将去讲解操作系统根据代码(逻辑)地址去访问真实物理地址的全过程。

将把全面几节的东西全部用上,并完全梳理,完善细节。

前面讲了分段、分页机制,他们都可以实现,从虚拟地址(地址空间)向物理地址的转换。但是,实际使用过程中,使用的是分段+分页机制,段页结合。

段页结合

全过程分析(高能)

我们现在采用边实验边讲解翻译全过程。

写了一段 c 代码,编译,然后在 Linux 0.11 中,进行调试

#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。

我们想做的是,通过编译,找到变量 i 的逻辑地址,然后经过一系列的地址转换,获得物理地址。通过查看物理地址的内容,是否是 0x12345678。

获得虚拟地址

将运行的代码进行反编译,可以看到 cmp dword ptr 这一部分。这一部分,对应的就是上面c语言的 while(i) 部分。

可以看到熟悉的 ds:0x3004,这是什么?

这就是我们之前分段章节里面的间接寻址。也就是说,我们要找到 ds 段的基址,然后加上3004的偏移量。

这里的 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表,是全局描述表。从这里的 描述 二字与上面的 段描述符可以看出:GDT表中保存着上面提到的段描述符。

LDT表

LDT表,是局部描述表。里面也保存着段描述符。

GDTR寄存器

此寄存器,记录着 GDT表的基址。

LDTR寄存器

跟我们之前说的选择符是一样的,它表明了 LDT表在 GDT表中的位置。

我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。

LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。由于LDT本身同样是一段内存,也是一个段,所以它也有个描述符描述它,这个描述符就存储在GDT中,对应这个表述符也会有一个选择子,LDTR装载的就是这样一个选择子。

注意,LDT表中也保存着描述符,是我们需要的。

也就是说,我们首先要获取 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的值。

posted @ 2020-05-10 16:55  土堆碎念  阅读(909)  评论(2编辑  收藏  举报