Linux下的虚拟地址映射详解(一)逻辑地址到线性地址的映射

来源:互联网 发布:武汉淘宝商学院骗局 编辑:程序博客网 时间:2024/05/16 05:12

现在假设有这么一段代码:

 

void main()

{

int a = 100;

cout<<&a<<endl;

}

 

如果编译执行,先打印地址,假设是0x12345678。那么,这个地址,是逻辑地址呢,还是线性地址,亦或是物理地址呢?

 

    首先我们需要引入一些别的东西。在英特尔的X86体系下,从他的发展可以看出他是从实模式向保护模式发展的,内存管理是从分段是管理往分页式管理的。既然是一个体系,后来的东西必须要能够兼容前面的东西。就像word2007一定能够兼容word2003一样。在i386之前的操作系统里,存在着段寄存器比如CS(代码段寄存器),DS(数据段寄存器),SS(堆栈段寄存器)。IP(偏移量)等。

 

      来说说实模式:

    实模式依然是段式管理,存在着段寄存器和IP寄存器。32位系统的地址总线是20条,但是段寄存器是从X86体系下继承过来的,那么段寄存器的大小是16位,规定每个内存段的起始地址必须是16的倍数,那么一个段的物理内存为2^16=64K。但是2016明显对比不上。怎么办呢?让我们来看看16的二进制写法,0010 000。也就是低4位全部为0。那么20位的低4为全部为0,剩下的高16位就能够放进去了。所以寻址方式成了这个样子:如果要寻找某个数据的内存,那么要从DS寄存器里找,DS寄存器里放的是高16位,那么得左移4位得到所在段的地址,再加上IP寄存器里的偏移量,最后获得数据的地址,也就是物理地址。如下图:



逻辑地址就是代表着内存段上的偏移量,本文最开始打印出的,就是逻辑地址。

 

此时还没有操作系统这个概念,假设你装了4G的内存,但是在你的电脑一连上电启动之前,依旧只能够访问2^20 = 1M内存。

此时的实模式没有任何的保护措施,你可以随时操纵段寄存器和IP寄存器来修改任何数据。

 

由于实模式的不安全性,我们开始思考着怎么保护内存,最后出现了保护模式。保护模式是基于32位系统的,由于使用了32位系统,原来的寄存器已经不够用了,所以又引入了新的东西比如CR,GDTR,LDTR等等。

保护模式出现以后,对于内存起始地址,内存段的大小,内存的访问权限需要有一个东西来保存。但是段寄存器依旧是16位,为了存储这些东西,我们需要采用一些描述符表来保存,这就是表地址寄存器GDTRLGTR,全局段描述符表和局部段描述符表,里面放着表头地址。下面来讨论段寄存器和GDTR

 

此时的段寄存器里依旧是16位,放着段选择码。选择码如图:



最低两位代表着用户态和内核态,第三位代表使用的是GDT还是LDT,前13位代表着所能够用的段描述符,2^13 = 8192。但是为什么又减了12呢?

Linux里我们可以看到他在主函数的末尾有这么一串指令。


仔细数数,正好有12个。所以8192-12 = 8180个。这8180个就是我们能够使用的段描述符。

 

 

现在再来谈谈GDTR

GDTR存着描述符表的起始地址。GDT实际上就是一个数组,如图


通过下标找到了段描述符,段描述符是长这个样子的:


地址从L0开始B31结束。

 

B开头的记录着内存的起始地址,L开头的记录着长度,2^20 = 1M,但是我们也知道,有时候不一定是字节。G记录着L的意义,如果为0,代表长度代表字节,即1M。如果为1,那么短描述符表的长度单位就是页面,即4k。此时该描述符表能够描述4G的大小。

 

理一下思路,段寄存器DS/CS/SS里的高13位放着的是段选择码,接着从第14位确定是找GDT还是LDT,第14位为1,也就是找的GDT。从GDTR寄存器找到GDT,获得起始地址(起始地址为GDT[DS>>3].Addr),获得起始地址之后加上IP长度,看有没有超过最大值,如果没有,就获得了线性地址。最大值是1M或者4G,具体是什么,要看G的值。这个就是逻辑地址到线性地址映射的过程

0 0