Linux虚拟地址映射

我们定义一个局部变量,然后打印出这个局部变量的地址,那么这个局部变量的地址是线性地址?物理地址?还是逻辑地址?要明白这些,先来看看以下的知识吧。

X86体系:指的是特定微CPU执行的有些计算机语言指令集,定义芯片的基本用规则

CPU的位数:ALU一次性最多能处理的整数的字节数,也即ALU的宽度,ALU的数据是从寄存器拿来的,寄存器中的值是从数据总线拿来的,所以也可以说是数据总线的条数。但不可以说是地址总线的条数,因为我们经常说32位地址总线是因为32位的操作系统数据总线和地址总线都是32条。但是16位的数据总线是16条,地址总线是20条,8位的数据总线是8条,地址总线是16条。

 

X86体系之前8位的CPU实际上它的地址总线是16条的,也即它的最大寻址地址可以达到64k。8086是20位地址总线,可以达到1M

 

8080/8085芯片,寄存器也就1个字节,但是地址总线是16位的,也即地址的格式都是两个字节的(eg:0X0012),这个地址就没办法放入寄存器中了(数据的位数和地址的位数不对等),只能分开存放了。所以究其历史原因,当时用的是16位的汇编指令解决(这个很复杂,不做介绍,实际上是我也不会啊!!!)

 

接下来我们就要进入X86体系了,既然是体系那就要保证你之后所创立的新的芯片要兼容这个体系之前的芯片。所以创建的第一个8086是至关重要的,这时候8086已经达到16位了,But我们要找寻更大的地址,所以我们的地址总线扩大为20条。那么我们这时如果要解决数据的位数和地址的位数不对等,难道要向X86之前那样,集成20位的汇编指令吗?答案显然是不,因为那个汇编设计起来非常复杂,而且我们要开创一个新的体系。所以8086芯片开始我们就在CPU里面增加了4个寄存器(当然了这4个寄存器也是2字节哒)

CS (代码段寄存器)  DS(数据段寄存器)   SS/ES(堆栈段寄存器)  IP(偏移量寄存器)

 

在这个体系中有这么一个规定,他把内存(物理内存)划分成为一个段一个段的,规定每个内存段的起始地址必须是16的倍数,又因为IP寄存器最大可以偏移2^16(64K),所以内存中的段的大小就是在16到64k之间,那就是你的代码跟数据堆栈有多少。

所以这个20位的地址怎么放到16位的寄存器中呢?因为我们上面规定了每个段的起始地址都是16的倍数,所以我们可以知道,一个数能被16整除,2进制的这个数满足怎样的特征呢?

例如0X00010000(16) 0X00100000 (32)   0X01000000 (64) 

可以看出能被16整除的数,它的低4位都是0。所以我们就可以将内存段的起始地址的高16位存储在相应的段寄存器中。

所以在这种情况下,我们就需要这样寻址:

我们CPU可以根据指令的译码(eg:mov lea call)可以判断出我们对于那个段进行的操作。比如我们是mov,那么这就是对于数据段的操作,那么我们如何找到我们所要操作的数据地址在哪呢?首先我们就会去访问DS,因为这个DS存的是数据段高16位的地址,所以我们就需要将DS中的数据左移4位,才是真正的段的起始地址,那么数据在这个段的那个位置呢?这就要看我们的IP的寄存器了,IP寄存器放的就是偏移量,所以我们就可以找到数据的地址。

因为没有操作系统,也就没有权限的控制,只要调用驱动接口,就可以更改段寄存器中的数值,那么就可以直接更改到物理内存上的东西,没有操作系统的保护。我们将这种没有操作系统的保护的机制叫做实地址模式(实模式),实模式是非常不安全的。

实模式下能访问的最大物理内存也就是2^20(1M),对于X86体系之后的芯片(例如80386),在操作系统没有启动起来,通上电的时候实际上强制进入的是实模式(也即8086的模式)。所以我们看到Linux内核的代码,内核的image(镜像)加载的时候都加载到了0X100000(1M),为什么从这个地址开始加载?因为在这个之前都是实模式运行的,实模式所占的内存之后才是内核的镜像所放置的位置。那么这个前1M放的什么东西?比如软件的驱动代码,显卡的缓存等等。

虚拟地址是从0XC0100000加载。

 

我们在访问内存的时候需要什么信息呢?

1:内存的起始地址

2:内存段的大小(如果没有这个大小size,我们如果给IP地址一个非常大的值,我们就有可能从代码段偏移到数据段了,所以如果size<IP,那么这个IP就是无效的)

3:内存的访问权限(比如:代码段只读,数据段读写…)

还有诸如此类的信息

那么这些东西不可能全放在段寄存器中,我们的段寄存器是16位的(即便是80386仍然是16位,因为这是8086有的,以前存在的东西在这个体系中是不能被更改的,只能新增,这样才可以向前兼容)

所以对于这些信息,我们必须要重新的一种结构,所以从80386以后,我们新增了几个寄存器

GDTR  全局的段描述附表寄存器    LDTR  局部的段描述表寄存器

GDTR存放的是GDT(全局的段描述附表,在内存中(物理内存),可以将它视为数组)

要访问这个数组中的元素,还需要一个下标,那我们下标放在那里呢?图中可以看出内存的起始地址在数组中,那么我们的段寄存器就空下来了,我们就可以将这些下标放在段寄存器中了。

所以从80386开始,我们这些段寄存器就不存放内存的起始地址,而是存放的是段描述附表的索引。

那么在这种情况下,我们就无需用实模式底下的那样去寻址了,而是首先根据指令的译码(eg:mov lea call)判断出我们对于那个段进行的操作。比如我们是mov,那么我们就会去访问DS,根据这个DS就可以找到GDT中的索引信息,访问到内存。

这个段寄存器中的索引是如何存放的呢?

段描述符表项是什么结构呢?我们来看一下

我们现在内核已经不用LDT了,用的是GDT,GDT是所有进程共享的,LDT是每个进程独有的。我们以GDT来举例

保护模式下内存分段的地址映射是什么?

检查有没有开启分页机制,我们CPU中有这几个寄存器

CR0  最高PG位   0未开启分页  1开启分页

CR2   发生缺页异常的虚拟地址

CR3  也目录的起始地址      

CR4 PAE位 物理地址扩展   0表示未开启   1表示开启

所以当我们基地址等于0时,我们的逻辑地址也就等于线性地址/虚拟地址,那么这个基地址是否为0,这是不一定的,要看内核源码实现了。CPU在发出一个地址的时候分为内总线与外总线,CPU在程序上发出来的地址,就是内总线,这个地址要经过一系列映射才能把地址放到外总线上,外总线上直接连的就是我们内存,放在外总线的地址就是我们直接映射的物理地址。

关于分段分页映射,明日再更。

 

 

 

猜你喜欢

转载自blog.csdn.net/Eunice_fan1207/article/details/81486898