Linux下的虚拟地址映射详解(二)线性地址到物理地址的映射

原文:http://blog.****.net/qq_33225741/article/details/71987485

现在有这么一个函数

 

void main()

{

int a = 2;

cout<<&a<<endl;

 

while(a)

{

;

}

cout<<”fun end”<<endl;

}

 

    函数运行起来会是这个样子:先打印出a的地址,假设是0x12345678,接着会进入死循环。现在如果要求你寻找某个办法,来跳出循环打印”fun end”,然后结束整个函数。(函数正在运行,不允许修改代码,也不能ctrl+c)你会怎么做呢?

 

    从上一篇的博文我们知道了打印出来的地址0x12345678是逻辑地址,如果是在i386以前的操作系统里,我们只要从SS里找到段基址,再加上0x12345678,就能获得变量a的物理地址,修改这个物理地址上的值,把它变为0.就自然跳出循环打印结束了。但是,如果是在i386之后的32位操作系统会是怎么样呢?

 

    我们常说英特尔X86体系,是由于后来者能够兼容前者但是又能够做一些前者不能够做到的事情。i386之后,在分段式管理的基础上又使用了分页式管理模式。也就是说,i386之前的线性地址即为物理地址这个说法,在i386之后是不一定正确的。

 

    那我们又怎么知道这个系统是否打开了分页式管理呢?

 

    有一个寄存器,叫CR0,他的PG位就是标识着分页式管理是否被打开。如果PG位为1,就代表着使用了内存分段和内存分页。如果为0,就只使用了内存分段。如果打开了页映射,还要进行多级映射。32位系统需要进行二级映射,64位系统要进行四级映射。

 

在讨论页映射之前,我们先要介绍下CR系列寄存器。  

 

CR0 最重要的就是他的PG位保存着是否打开页映射。

CR2 保存当前缺页异常时的虚拟地址

CR3 保存着当前进程页目录的起始地址

CR4 比较重要的就是PAE位记录着是否打开物理地址拓展。

 

 

下面再来谈谈32位系统下的页映射。

 

    在确定了开启页映射,先从CR3寄存器里寻找页面目录。找到页目录的起始地址之后,让我们来看看他的线性地址。假设文章开头的a的线性地址是0x23456789,那么16位的线性地址将转化成32位的二进制地址,前十位代表着页目录的下标,中间十位代表着页表的下标,后12位代表物理页面的偏移量。如下图


Linux下的虚拟地址映射详解(二)线性地址到物理地址的映射

现在再来看看线性地址是怎么映射到物理地址的。


Linux下的虚拟地址映射详解(二)线性地址到物理地址的映射

来看一下页目录。页目录是一个数组,数组里放着指针,一个指针是4字节,线性地址前10位表示页目录的下标,2^10=1024个下标,那么1024*4=4K,同理,一个页表大小也为4K,也就是说页表是以4K对齐的。一个物理页面的大小也为4K,也是以4K对齐的。那么,页目录的起始地址又在哪里放着呢?哦,回头看看,不就在CR3放着吗?不过进程切换的时候,你CR3还能保存当前页目录的起始地址吗?

 

如果来看下linux的源码,在切换进程的时候,函数的最末,会将即将调用的页目录的起始地址放到CR3里。这样就能够保证了一定能够找到当前的页目录。

 

通过CR3找到页目录的起始地址,通过前10位确定页表,通过中间10位确定物理页面的起始地址,加上最后12位的偏移量获得真正的物理地址。看起来是挺不错的。但实际上呢?

如果我们按照上面的思路,那么权限就会出问题。假设现在有两个进程,一号进程通过MMU最后映射到了某个物理页面,二号进程通过某种方法最后也找到了同一个页面。如果没有权限,二号进程修改了一号进程私有的物理页面内的数据。这乐子可就大了。

 

在页表和页目录里放的都是指针,一个指针4个字节,也就是32位。一个物理页面的大小为4K4K转化为二进制,后12位都是0.那么,我们只保存高20位,需要的时候我们再转回32位,剩下12位我们拿来保存权限不就好了么?最后成了这个样子。


Linux下的虚拟地址映射详解(二)线性地址到物理地址的映射


其中,低12位里的最低一位是present位。如果为0,就代表着这个物理页面在swap交换分区中,如果为1,就代表这个物理页面就在物理内存中。

 

有一些文章会提到PTE这个词,PTE的全称就是Page Table Entry。就是页表里对应物理页面起始地址的那一项。在这里要讨论一下他的几种情况。

1、如果高20位为0present0。代表着该物理页面未被分配。

2、如果高20位不为0present0。代表着该物理页面在交换分区中。

3、如果高20位不为0present也不为0。代表着该物理页面就在物理内存中。

 

 

所以现在很明了了,文章开头那个函数应该这么处理:

1、先通过LDT[DS>>3]+逻辑地址 找到线性地址

2、将线性地址分为3块,从CR3寄存器里寻找页目录,线性地址高10位转成了10进制找到页目录的下标,这一个元素的前20位记录着页表的起始地址的高20位,左移12位找到页表的起始地址。找到页表以后通过线性中间10位找到物理页面的起始地址的高20位,左移12位找到页表的起始地址,最后加上偏移量,获得a的物理地址。把他修改成0,函数自然就结束了。