以前,在一些文档和代码中看到过说arm-linux的二级页表分为linux版本和硬件版本。一直觉得概念比较混乱,没有仔细研究,今天终于遇到了这个问题,不得不学习一下了。

在do_page_fault()过程中,有下面函数会被调到:

   [c]
static inline void __pmd_populate(pmd_t *pmdp, unsigned long pmdval)
{
pmdp[0] = __pmd(pmdval);
pmdp[1] = __pmd(pmdval 256 * sizeof(pte_t));
flush_pmd_entry(pmdp);
}
[/c]

它的功能是把在这一个新申请的二级页表与PMD关联起来。在这之前,pmdp指向的PMD项是空的,当前的过程正是在为它建立映射。在调用这个函数之前,已经申请好了一张4K大小的二级页表。

pmdp[0] = __pmd(pmdval); 这一行很容易理解,这是在把根据pte生成的PMD表项值赋值给PMD项。但是下面这一句是为什么呢?

首先先看一张二级页表有多大,arm-linux采用的是粗粒度二级页表映射,使用这种一映射关系,一个PMD表项下面映射/覆盖1M内存,一个PTE项下面映射/覆盖4K内存,所以一张二级表应该有1M/4K=256个表项。而一个二级表项是4字节,所以一张二级表应该占用空间256*4=1K字节。一个4K的内存页可以容纳4张二级表。

其次,关于二级页表,不知为什么内核要为一张二级页表提供两份版本(一个Linux版本,一个硬件版本)。而且两个版本的表的位置关系定义得很别扭。看pgalloc.h中的一个注释图:

 

从上(低地址)到下(高地址)分别是:第一张表的硬件版本、第二张表的硬件版本,第一张表的Linux版本、第二张表的Linux版本。可见,同一张表的linux版本和硬件版本是不连续的,但两张不同表的同一版本是连续的。我想,把这样的4张表放在一起,正是为了向4K的页面大小对齐,不至于浪费空间。

这样,一切就都好理解了。上述函数中,接下来这一行:

pmdp[1] = __pmd(pmdval 256 * sizeof(pte_t));

正是把第二张硬件表的对应的PMD值写到接下来的一个PMD表项中去。

这个函数实际上是完成了两张二级表的映射。