ARM中的linux页表与硬件页表(linux4.0)

 

背景:

在看内核代码初始化arm页表的时候,发现linux内核做了一些特殊处理,引入了硬件页表、linux页表的概念,这篇文章描述为何需要这样处理以及具体的实现方式。

原因:

linux中ARM32采用2级页表映射方式,MMU映射过程如下:

ARM中的linux页表与硬件页表(linux4.0)

                                                    图1  ARM MMU映射过程

从图1可以得知,ARM MMU页表(称为硬件页表或hw pt)如下:

ARM中的linux页表与硬件页表(linux4.0)

页表中的每一项称为一个entry,entry存放的是物理地址值,PGD entry值指向2级页表(PTE页表),PTE entry值指向物理页。

由于以下两个原因,linux代码对图2的映射过程做了一些调整:

1)PTE entry中的一些低bit位被硬件使用了,没有linux需要的“accessed”、“dirty”等标志位。

参考内核代码注释: Hardware-wise, we have a two level page table structure, where the first level has 4096 entries, and the second level has 256 entries.  Each entry is one 32-bit word.  Most of the bits in the second level entry are used by hardware, and there aren't any "accessed" and "dirty" bits。

ARM中的linux页表与硬件页表(linux4.0)

2)linux希望PTE页表本身也是一个页面大小。

参考内核代码注释:However, Linux also expects one "PTE" table per page, and at least a "dirty" bit.,但图1表明PTE页表是256*4 byte=1k大小。

解决方案

针对上面提到的问题,linux做了一些处理,使内核中实现的页表能够满足硬件需求,最终的arm页表见图4。

ARM中的linux页表与硬件页表(linux4.0)

对于图4,解释如下:

1)软件实现必须符合硬件要求。ARM要求4096个PGD entry、256个PTE entry。

解决:PGD每个entry为8 bytes,定义为pmdval_t pgd[2],故共2048*2=4096 PGD entry。ARM MMU用va的bit[31,20](见图1)在PGD 4096项中找到对应的entry,每个entry指向一个hw页表(见图4中pmdp)。每一个hw页表有256个entry,ARM MMU用va的bit[19,12]在hw页表中找到对应的entry。所以从硬件角度看,linux实现的arm页表,完全符合硬件要求。

2)Linux需要 "accessed" and "dirty"位。

解决:从图3中可以看出,PTE entry的低位已经被硬件占用,所以只能再复制出一份页表(称为linux页表或linux pt),图4的hw pt 0对应Linux pt 0,linux页表的低bit位被linux系统用来提供需要的 "accessed" and "dirty"位。hw pt由MMU使用,linux pt由操作系统使用。

3)Linux期望PTE页表占用1个page。

解决:ARM的hw pt为256*4 bytes=1k,不满一个page大小。内核代码在实现上采用了一个小技巧,让一个PGD entry映射2个连续的hw pt,同时将对应的2个linux pt也组织在一起,共1k*4=4k。

因为linux代码让PGD一次映射2个hw pt,所以软件需要做一些处理来实现这个目的。软件定义PGD表项为pmdval_t pgd[2],pgd[i]指向一个hw pt,所以PGD表项一共有4096/2=2048项,也就是说需要用bit[31,21]来寻址这2048项,所以pgtable-2level.h中定义了:#define PGDIR_SHIFT 21 (注意,图1中PGD偏移20bit,那是给硬件MMU用的,跟我们这里的软件偏移没有关系)。

 

上面部分内容,可以结合代码来仔细品味(从__create_mapping函数看起),代码路径:

start_kernel-->setup_arch-->paging_init-->map_lowmem->

create_mapping-->__create_mapping-->alloc_init_pud-->alloc_init_pmd

-->alloc_init_pte