zoukankan      html  css  js  c++  java
  • 内存访问全过程

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

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

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

    段页结合

    全过程分析(高能)

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

    写了一段 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的值。

  • 相关阅读:
    静态页面复习--网格嵌套练习
    静态页面复习--用semantic UI写苹果官网
    静态页面复习--用网格写一个landing page
    静态页面复习--用semantic UI画美国队长盾牌
    静态页面复习--semantic UI搭建简单博客页面
    SQL脚本--总耗CPU最多的前个SQL --平均耗CPU最多的前个SQL
    C#中基于GDI+(Graphics)图像处理系列
    MVC Action 返回类型
    Axure RP 7 8
    MVC object htmlAttributes,IDictionary<string, object> htmlAttributes 写法
  • 原文地址:https://www.cnblogs.com/zhouzhiyao/p/12864090.html
Copyright © 2011-2022 走看看