2.4 页表
页表用于建立用户进程的虚拟地址空间和系统物理内存(内存、页帧)之间的关联,用于向每个进程提供一致的虚拟地址空间,应用程序看到的地址空间是一个连续的内存区。层次化的页表用于支持对大地址空间的快速、高效的管理,对比起线性寻址好一些。
页表也将虚拟内存页映射到物理内存,因而支持共享内存的实现(几个进程同时共享的内存),还可以在不额外增加物理内存的情况下,将页换出到块设备来增加有效的可用内存空间。
不管底层处理器,内核的内存管理一般假定使用四级页表。例如,默认情况下IA-32系统只使用2级分页系统(不使用PAE扩展),所以第三和第四级页表由特定于体系结构的代码来模拟。
页表的管理分为两个部分,第一部分依赖于体系结构,第二部分是体系结构无关的。但是所有数据结构和操作数据结构的几乎所有函数都是定义在特定于体系结构的文件中。不同CPU的实现有较大的差别,这里暂时不细谈。
通常基于体系结构相关的文件中提供的接口。定义可以在头文件include/asm-arch/page.h和include/asm-arch/pgtable.h中找到。虽然AMD64和IA-32已经统一为一个体系结构,但在处理页表方面仍然不同,所以相关的定义在两个不同的文件:include/asm-x86/page_32.h和include/asm-x86/page_64.h,类似地有pgtable_XX.h。
2.4.1 page.h和pgtable.h
C语言中的void *数据类型用于定义可能指向内存中任何字节位置的指针,它所需的比特位数目依不同体系结构而不同。常见的处理器都使用32位或64位。
内核源代码假定void *和unsigned long类型所需的比特位数相同,即sizeof(void *) == sizeof(unsigned long)
,所以它们可以进行强制转换,在Linux支持的所有体系结构上都是正确的。
2.4.1.1 内存地址的分解(页表项数目)
四级页表需要虚拟内存地址分为5部分(4个表项PTE,PMD,PUD,PGD用于选择页,1个索引表Offset示页内位置)。不同体系结构地址字长度不同,地址字拆分方式也不同。所以内核定义了宏,将地址分解为各个分量。
具体见下图,BITS_PER_LONG定义用于unsigned long变量的比特位数目,因而也适用于指向虚拟地址空间的通用指针。
以下值单位都是比特位。
-
PAGE_SHIFT:即为页内偏移量Offset,位于末端,PAGE_SHIFT是具体数目,一般是12位,即一页大小为2的12次方字节=4096字节,也就是4KB。
-
PMD_SHIFT:PMD(Page Middle Directory)页中间目录,此值是页内偏移量和三级页表索引的和。PMD_SHIFT-PAGE_SHIFT即为三级页表项索引所需比特位。PMD_SHIFT表明了中间层页表项管理的部分地址空间的大小,即2的PMD_SHIFT次方字节。
-
PUD_SHIFT:PUD(Page Upper Directory)页上级目录,此值是PMD_SHIFT加上二级页表索引的和。
-
PGDIR_SHIFT:PGDIR(Page Global Directory)页全局目录,此值是PUD_SHIFT加上一级页表索引的和。对全局页目录中的一项所能寻址的部分地址空间长度计算以2为底的对数,即为PGDIR_SHIFT。
在各级页目录/页表中所能存储的指针数目,也可以通过宏确定。
-
PTRS_PER_PGD:页全局目录中项的数目。
-
PTRS_PER_PMD:页中间目录。
-
PTRS_PER_PUD:页上级目录中项的数目。
-
PTRS_PER_PTE:页表中项的数目。
若是只有两级页表的体系结构,则PTRS_PER_PMD和PTRS_PER_PUD定义为1。就像提供了四级页转换结构,实际上只有两级页表。由于只有极少数系统使用四级页表,
头文件include/asm-generic/pgtable-nopud.h来提供模拟上层页目录所需的所有声明。
头文件include/asm-generic/pgtable-nopmd.h用于在只有二级地址转换的系统上模拟中间层页表。
n比特位长的地址字可寻址的地址区域长度为2的n次方字节。内核定义了宏变量保存计算得到的值,避免多次重复计算:
#define PAGE_SIZE (1UL << PAGE_SHIFT)