0. 前言
前提
本文描述的硬件架构基于ARMV7-AR 与Cortex A9,软件内核版本基于Linux 3.4。
缩写
PGD:Page Global Directory
PUD:Page Upper Directory
PMD:Page Middle Directory
PTE:Page Table Entry
1. 概述
Linux X86 页表采用三级映射:PGD-->PMD-->PTE,Linux X64 采用四级映射:PGD-->PUD-->PMD-->PTE,多了个PUD;而ARM32 Linux在实现过程中省略了PMD,采用两级映射,即PGD-->PTE, 除非在定义了CONFIG_ARM_LPAE才会使用三级映射。
如下图所示,ARM32 Linux主要用到了PGD段映射和PTE小页表映射。其中PGD为一级映射,PTE为二级映射,一级映射有4096个段页表项,二级映射有256个小页表项,每个页表项大小都是32bit。实际的虚拟地址-->物理地址转换过程如下:
32位虚拟地址的高12位(bit[31:20])作为访问一级页表的索引值,找到相应的表项,每个表项指向一个二级页表。
虚拟地址的次8位(bit[19:12])作为访问二级页表的索引值,得到相应的页表项,从这个页表项中找到20位的物理页面地址。
最后将这20位物理页面地址和虚拟地址的低12位拼凑在一起,得到最终的32位物理地址。
整个过程由ARM32 MMU硬件完成,软件不需要介入。
2. 硬件实现
ARMV7 的映射表支持两种结构,一种是为32 位系统设计的,叫做Short-descriptor format短描述符格式, 而另一种是支持64 位的,叫做Long-descriptor format长描述符格式。
发展的趋势是64 位,但目前流行的应用还是基于32 位的,本文只描述Short-descriptor format,详细可参阅《ARM Architecture Reference Manual(ARMv7-AR)》B3.3。
针对Short-descriptor format短描述符格式,ARMV7可配置为一级映射或者二级映射结构,下图为映射表的实现:
从上图可以看出,一级页表支持两种格式,一种是叫做Section 可以用映射1M 空间,另外一种是叫做Super section 可以用来映射16M空间。
对二级页表也支持两种格式,分别是Large Page和Small Page。其中Large Page一个页表项映射64KB空间,Small page一个页表项映射4KB空间。
如果只需要支持Section 和Super section,那么采用一级页表映射即可;如果要支持Large Page和Small Page,那么就需要用到二级页表了。
目前大多数操作系统像WinCE 和Linux 使用的都是Section + Small Page 这样的设置。
针对Section + Small Page的配置,一级页表描述符格式如下:
Page table base address[31:10]: 页表基地址
Domain[8:5]:A domain is a collection of memory regions.The domain field specifies which of the 16 domains the entry is in, and a two-bit field in the DACR defines the permitted access for each domain.
SBZ[4]: Should be zero
NS[3]: Non-secure bit
NS == 0 Access the Secure physical address space.
NS == 1 Access the Non-secure physical address space.
PXN[2]:The Privileged execute-never bit,When supported, the PXN bit determines whether the processor can execute software from the
region when executing at PL1
Bit[1:0]
0b00: Invalid or fault entry
0b01: Page table
The descriptor gives the address of a second-level translation table, that specifies the mapping of the
associated 1MByte VA range.
###判断有没有二级页表是通过一级页表的表项的低2 位bit 来决定的###
0b10: Section or Supersection
The descriptor gives the base address of the Section or Supersection. Bit[18] determines whether
the entry describes a Section or a Supersection.
If the implementation supports the PXN attribute, this encoding also defines the PXN bit as 0.
0b11: Section or Supersection
if the implementation supports the PXN attribute
If an implementation supports the PXN attribute, this encoding is identical to 0b10, except that it
defines the PXN bit as 1.
0b11:Reserved
if the implementation does not support the PXN attribute
An attempt to access the associated VA generates a Translation fault.
On an implementation that does not support the PXN attribute, this encoding must not be used.
二级页表描述符格式如下:
PA[31:12]:二级页表基地址,即转换后的物理地址31~12位;
nGbit[11] :The not global bit. Determines how the translation is marked in the TLB
nG == 0 The translation is global, meaning the region is available for all processes.
nG == 1 The translation is non-global, or process-specific, meaning it relates to the current ASID, as defined
by the CONTEXTIDR.
Sbit[10]: The Shareable bit.
AP[2], AP[1:0]
Access Permissions bits(See Page1349)
TEX[2:0], C, B
Memory region attribute bits (See Page1358)
Bit[1:0]
0b00, Invalid or fault entry
The associated VA is unmapped, and attempting to access it generates a Translation fault.
Software can use bits[31:2] of the descriptor for its own purposes, because the hardware ignores
these bits.
0b01, Large page
The descriptor gives the base address and properties of the Large page.
0b1x, Small page
The descriptor gives the base address and properties of the Small page.
In this descriptor format, bit[0] of the descriptor is the XN bit.
XNbit[0]
The Execute-never bit. Determines whether the processor can execute software from the addressed
region
3. 针对Linux的调整
Linux页表管理也是分级结构,为了和ARM32硬件相匹配,只使用PGD和PTE,构成了软件上的两级映射结构。
从上一节二级页表描述中可以看到,二级页表项描述符中的数据位基本上都被硬件使用了,且并没有Linux中pte_t需要的accessed、dirty等位,这样就和Linux的页表管理有些出入;另外Linux希望PTE页表本身也是一个页面大小(4KB),而现在256个二级页表项每个4字节总共才1K。因此Linux内核在实现上做了些细微的调整,具体调整为:
(1)一级页表放2048个页表项,每个页表项为8字节,总共16KB;
(2) 对于一级页表的每个页表项,二级页表通过两个连续的硬件PTE与之对应,这样硬件PTE总大小为2KB;
(3) 在硬件PTE的前面放两个软件版本的PTE(Linux PTE); 这两个PTE不为硬件所使用,但可以在这两个PTE中加入Linux 页面管理所需要的字段信息,满足Linux 自身页表管理需求,软件PTE的总大小也为2KB;
(4) 软件PTE + 硬件PTE = 4KB, 实现了PTE页表本身也是一个页面大小的目标;
(5)一级页表的最后1/4 的4KB空间对应了虚拟地址(0xC000_0000 -- 0xFFFF_FFFF),由于此空间在Linux是分配给内核使用的,用户进程不能使用,所以这4KB一级页表项拷贝了内核一级页表最后4KB数据,所有用户进程都是一样的。
通过一个具体示例可以更清晰地看到这一调整实现:
(1)打开一个DUMP现场,读取当前的TTBR0寄存器,跳转到内存的TTB0基地址即用户进程一级页表(PGD)基地址。跳转过去可以看到,该位置开头存放了两个一级页表项,取该也页表项的Bit[31:10]可以得到两个地址,分别是0x2419a800和0x2419ac00。
(2)跳转到内存0x2419a800和0x2419ac00,打开二级页表PTE,两个PTE基地址相差0x400 = 1K,符合前面描述的两个连续的硬件PTE的设定。
以第一个PTE为例,如果对照转换方法进行手工地址转换,其结果应该是:
PTE[0]: 对应虚拟地址0x0000_0000,页表项为空,不映射;
PTE[1]: 对应虚拟地址0x0000_1000,页表项为空,不映射;
......
PTE[8]: 对应虚拟地址0x0000_8000,页表项为0x23e70a3e,取其Bit[31:12]位,得到物理地址0x23e70000,得出映射关系 0x0000_8xxx <--> 0x23e7_0xxx;
PTE[9]: 对应虚拟地址0x0000_9000,页表项为0x23e63a3e,取其Bit[31:12]位,得到物理地址0x23e63000,得出映射关系 0x0000_9xxx <--> 0x23e6_3xxx;
......
打开MMU Table List,可以看到仿真器解析的页表映射关系与上面手工解析的是一致的:
(3)第一个页表基地址 0x2419a800对应PTE页表项总共256个,对应虚拟地址0x0000_0000~0x000f_ffff;
第二个页表基地址 0x2419ac00对应PTE页表项总共256个,对应虚拟地址0x0010_0000~0x001f_ffff;
按照之前描述,在硬件PTE的前面会放置着两个软件版本的PTE,换算下来软件PTE的首地址=0x2419a800-256*4*2 = 0x2419a000, 打开该地址进行对比:
可以看到软件PTE对应的PTE页表项和硬件PTE页表项,其对应的转换后的物理地址(Bit[31:12])是一致的,只有后面的属性值有差异。
=================================================================================
参考:
<ARM Architecture Reference Manual(ARMv7-AR) >
刘永生:《基于ARM CPU的Linux物理内存管理》
https://www.cnblogs.com/arnoldlu/p/8087022.html
https://cloud.tencent.com/developer/article/1626149
http://blog.chinaunix.net/uid-7449002-id-3821621.html