《Linux源码情景分析》--2.1 Linux内存管理的基本框架

2.1 Linux内存管理的基本框架

​ Linux内核的设计要考虑在各种不同的CPU上的实现,还要考虑64位CPU,所以不能仅仅针对i386结构来设计它的映射机制,要以一种假象的、虚拟的CPU和MMU(内存管理单元)为基础,设计出一种通用模型。在32位的内存空间下,两层映射系统比较有效,但是在64位内存空间下,两层映射会降低内存空间的效率。因此,Linux内核的映射机制设置为三层,在页面目录和页面表之间加入了一层“中间目录”。PGD、PMD、PT均为数组。相应的,在逻辑上也把线性地址从高到低划分为4个位段,分别用作目录PGD中下标、中间目录PMD中的下标、页面表中的下标、以及物理页面内的位移。
《Linux源码情景分析》--2.1 Linux内存管理的基本框架

​ 但是,这个虚拟的映射模型必须落实到具体的CPU和MMU物理映射机制。这里主要介绍了32位地址的二层映射:

#define PGDIR_SHIFT 22  //线性地址中PGD下标的起始位置,PGD位于线性地址的最高字段,因此PGD位22位到31位,一共10位。
#define PTRS_PER_PGD 1024  //PGD表中指针的个数位1024个,2的10次方。在32位系统中指针大小为4字节,因此PGD表大小4KB

/* 
 * The i386 is two-level,so wo don't really have any 
 * PMD direcotary physically.
 */

#define PMD_SHIFT 22 //PMD下标起始位置也为22,表示PMD长度为0。每个PMD表项的大小和PGD是一样的。
#define PTRS_PER_PMD 1 //每个PMD表中只有一个PMD表项,只有一个PT表指针。

#define PTRS_PER_PTE 1024 

​ 这样,上述的4步映射过程对于内核(软件)和i386MMU就成为:

  1. 内核为MMU设置好映射目录PGD,MMU用线性地址中的最高的那一个位段(10位)作为下标在PGD中找到的表项。该表项逻辑上指向一个中间目录PMD,但是物理位置上直接指向相应的页表,MMU并不知道PMD的存在。
  2. PMD只是逻辑上存在,既对内核软件概念上的存在,但是表中只有一个表项,而所谓的映射就是保持原值不变;现在一转手却指向页表了。
  3. 内核为MMU设置好了所有的页面表,MMU用线性地址中的PT位段作为下标在相应页面表中找到相应的表项PTE,该表项中存放的就是指向物理页面的指针。
  4. 线性地址中最后位段为物理页面内的相对位移量,MMU将此位移量与物理页面的起始地址相加便得到相应的物理地址。

​ 32位地址意味着4G字节的虚拟空间,Linux内核将最高的1G(虚地址 0xC0000000~0xFFFFFFFF)用于内核本身,称为“系统空间”,剩下的3G的空间(虚地址 0x0~0xBFFFFFFF)用作各个进程的“用户空间”。

《Linux源码情景分析》--2.1 Linux内存管理的基本框架

​ 虽然系统空间占据了最高的1G空间,但是物理的内存总是从最低的地址开始。因此,对于内核来说,其地址的映射是简单的线性映射:

#define __PAGE_OFFSET (0xC0000000)

#define PAGE_OFFSET   ((unsigned long)__PAGE_OFFSET)
#define __pa(x)       ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x)       ((void *)((unsigned long)(x)+PAGE_OFFSET))

​ 每个进程都有自己的页面目录表PGD,在切换进程时也要将进程的PGD加载到CR3寄存器中,而内核记录的是PGD的虚拟地址,CR3需要的是物理地址,这时候就要用到__pa()了:

asm volatile("movl %0,%%cr3": :"r"(__pa(next->pgd)));//next->pgd 表示下一个进程的页面目录起始地址。
上一篇:java – 忽略带有Test注释的方法的PMD规则


下一篇:Servlet的三种实现方式(详解)