从 Linux 2.6.11 开始,内核使用了独立于硬件架构的四级页表。但支持几级页表应该是硬件支持为标准,Linux 如何做到四级页表的呢?
下面看一段页表初始的代码就知道了。
PKMAP 固定内存部分的页表初始化
首先弄清,以下分析都是建立在配置了大于 1G 内存,并且未开启 PAE 情况下的 X86 架构的一些宏的值。
一些观点列出也都默认是以上条件下。
CallStack: page_table_range_init (arch\x86\mm\init_32.c) permanent_kmaps_init (arch\x86\mm\init_32.c) pagetable_init (arch\x86\mm\init_32.c) paging_init (arch\x86\mm\init_32.c) setup_arch (arch\x86\kernel\setup.c) start_kernel (init\main.c) startup_32 (head_32.S) static void __init page_table_range_init(unsigned long start, unsigned long end, pgd_t *pgd_base) { int pgd_idx, pmd_idx; unsigned long vaddr; pgd_t *pgd; pmd_t *pmd; vaddr = start; pgd_idx = pgd_index(vaddr); pmd_idx = pmd_index(vaddr); pgd = pgd_base + pgd_idx; for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) { pmd = one_md_table_init(pgd); pmd = pmd + pmd_index(vaddr); for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) { one_page_table_init(pmd); vaddr += PMD_SIZE; } pmd_idx = 0; } }
+------------------------------+ | PGD | PUD | PMD | PTE | PAGE | +------------------------------+
那么问题来了,X86 只识别两级页表,而 Linux 代码中分布管理是以四级页表实现的,如何实现这一点呢,首先要明确,代码总是建立的硬件实现的基础上,所以说 Linux 的四级页表其实是虚拟的四级页表,也就是在代码实现上,好像是四级分布,其实,是用了四级分页的代码,来填充两级页表,看 pmd_index 的实现便知道了。
#define pmd_index(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
其实页目录的计算都是既定的这些公式,只是规定的位偏移和长度不同而已,取出 PMD 在虚拟地址中对应的值,然后根据该项的取值范围做掩码,就可以得到在该级中的项的偏移,由于 PTRS_PER_PMD 为 1,即该项的索引应该小于 1,即只能为 0,但知道了项的索引值也不足以明白如何构建的二级页表,那接着看下去。
当知道各级的项对应的索引后,就可以初始化整个页表了,首先外层循环填充页全局目录,然后填充每一个页全局目录项对应的 PUD, PMD, 由函数 one_md_table_init 来实现,传入页全局目录项的虚拟地址,返回页中间目录的虚拟地址,因为只相当于兼容了三级页表,所以 PUD 的初始化相当于省略掉了,它的实现只有两条语句,
pud = pud_offset(pgd, 0);
pmd_table = pmd_offset(pud, 0);
pud_offset 得到 pud 的偏移, #define pud_offset(pgd, start) (pgd), 它直接返回了 pgd 的地址,
pmd_offset 也同样返回了 pud 的虚拟地址。也就是 pgd 项的地址,然后函数返回。
然后开始初始化页中间目录,循环设置每一项,其实只有一项,把这些项设置为页表的值, one_page_table_init 来设置页表,首先申请一个页表,然后赋值给相应的 pmd 项,这样就依次把 page_table_range_init 传入的这段虚拟地址的页表给建立起来了。
总结一下,由于该架构只支持二级页表,所以在计算 PUD 和 PMD 时,都是返回的传入的上级目录项虚拟地址,也就是 PGD 的目录项虚拟地址。
按四级来分,示意图如下:
PGD PUD PMD PT +-----+ +-----+ +-----+ +-----+ | a |--->| a |-------------->| a |------------>| t0 | +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ | b |------------>| b |-------------->| b | | t1 | +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ | c |--->| c |-------------->| c | | t2 | +-----+ +-----+ +-----+ +-----+ | ... | | ... |
PGD PT +-------------+ +-----+ +------+ |a(PUD)(PMD) |-------------->| t0 |-------->| Page | +-------------+ +-----+ +------+ | b | | t1 | +-------------+ +-----+ | c | | t2 | +-------------+ +-----+ | ... | | ... |