Linux内存管理基本框架
Linux内核的映射机制设计成三层:页面目录(PGD),中间目录(PMD),页面表(PT)
【其中页面表项简写为PTE】
1、三层地址映射示意图:
当地址位数为32位时,linux内核采用二层映射模式,当内存管理设置成PAE模式时,采用三层映射模式,接下来看看linux内核如何在保证三层模式的架构上实现二层映射模式:
#define PGDIR_SHIFT 22 //线性地址中PGD下标位段的起始位置从23到32共十位
#define PTRS_PER_PGD 1024 //PGD表中指针个数
#define PMD_SHIFT 22 //晃过了中间目录,从22到22,既长度为0
#define PTRS_PER_PMD 1 //长度为0表示就一项而已
#define PTRS_PER_PTE 1024 //页目表1024个指针
由上可知,PMD在二层映射中知识逻辑上存在而已…
2、进程虚拟空间示意图
在32位地址下,进程拥有4G的虚存空间,linux内核将最高的1G字节(0xC000 0000 到0xffff ffff)用于内核本身,称为系统空间。将较低的3G字节用作各个进程的用户空间。换句话说每个进程拥有自己的3G字节的用户空间,系统空间由所有的进程共享,每当一个进程通过系统调用进入内核,该进程就在共享的系统空间中运行。
注:系统空间虽占据了每个虚存空间的高1G 字节,但在物理的内存中却总是从最低的地址(0)开始的。所以对于内核来说,其地址的映射是简单的线性映射,0Xc000 0000就是两者之间的位移量(内核空间物理地址和虚拟地址间的转换)。
3、地址映射的全过程
Linux内核在建立一个进程时,都要将其段寄存器设置好,以便晃过80386的段机制,设置统一如下:
regs>xds = __USER_DS;
regs>xes = __USER_DS;
regs>xss = __USER_DS;
regs>xcs = __USER_CS;
#define __KERNEL_CS 0X10
#define __KERNEL_DS 0X18
#define __USER_CS 0X23
#define __USER_DS 0X2B
对照如下
TI全为0 ,这表明所有的段全使用GDT,接下来我们看看那GDT中内容:
GDT中的第一项(下标为0)是不用的,这是为了防止在加电后段寄存器未经初始化就进入保护模式并使用GDT,第二项也不用,下标2至5共4项对应于前面的四种段寄存器数值。
我们再来看看段描述符定义:
可以得出如下的结论:
(1) 四个段的下列内容都是相同的。
B0-B31都是0 :基地址全是0;
L0-L19都是1 :段的上限全是0xfffff
G位都是1 :段长单位均为4KB
D位都是1 :对四个段的访问都是32位指令
P位都是1 :四个段都在内存中
(2) 有区别的地方:
KERNEL_CS: DPL=0,表示0级;S位为1,表示代码段或数据段:type为1010,表示代码段,可读,可执行,尚未受到访问;
KERNEL_DS: DPL=0,表示0级;S位为1,表示代码段或数据段:type为0010,表示数据段,可读,可写,尚未受到访问;
USER_CS: DPL=3,表示3级;S位为1,表示代码段或数据段:type为1010,表示代码段,可读,可执行,尚未受到访问;
USER_DS: DPL=3,表示3级;S位为1,表示代码段或数据段:type为0010,表示数据段,可读,可写,尚未受到访问;
由上分析可知,linux内核通过对GDT的巧妙设置,轻松的晃过了段机制。
3.1 页式访问内存具体流程如下:
1: 根据指令的性质来确定应该使用哪一个寄存器;
2:查看寄存器的TI段,全为0,将GDTR寄存器和段寄存器中下标结合,在全局段描述符表中获取对应的段描述符;
3:从描述符中得到基地址全为0,将指令中发出的地址作为位移,与段描述结构中规定的段长度相比,看看是否越界;
4:根据指令的性质和段描述符中的访问权限来确定是否越权;
5:将指令中发出的地址作为位移,与基地址相加而得到线性地址
6:取线性地址的前十位作为PGD表的下标,与CR3寄存器结合,获取指向页面表的指针,再取中间10位作为页面表的下标获得具体的物理页面,该物理页面的物理地址与线性地址后十二位相加便是最终的物理地址。
注意:置于缺页中断等等情况,以后会详细阐述