Kernel版本:4.0.0
ARM处理器,Contex-A9,QEMU平台
内核初始化arm页表的内容,low_memory映射过程,之前也大概写了。但是在arm平台中,引入了硬件页表、linux页表的概念,本文描述为何要这样处理,以及内核、arm硬件页表属性进行说明。
参考书籍《奔跑吧Linux内核:基于Linux4.x内核源代码问题分析》2.2.1 ARM32页表映射。
Linux中ARM32位架构采用2级页表映射方式,MMU查找地址关系如下图,画图麻烦,直接从书抄了一张过来。
当片上TLB中不包含要求访问的虚拟地址入口是时,转换过程被启动。转换表基地址寄存器保存着第一级转换表基址的物理地址。只有bit[31:14]有效,bits[13:0]应该是0。所以第一级页表必须16KB对齐。对应的32位系统中,section大小1M,总共4096个section,每个section 4个字节,所以一级页表大小是16Kb。
ARM MMU页表如上图所示。如果采用单层的段映射,内存中有个段映射表,表中4096个表项,每个表项的大小是4Byte,所以这个段映射表的大小是16KB。每个段对应1MB大小的地址空间。当cpu访问内存时,32位虚拟地址的高12位用于访问段映射表的索引,从表中找到相应的表项,每个表项提供了一个12位的物理段地址,以及相应的标志位,如可读、可写等标志位。将这个12位物理地址和虚拟地址的低20位拼凑在一起,就可以得到32位的物理地址。linux初始化时,内核low memory使用的大部分是段映射的方式。
如果采用页表映射的方式,段映射表就变成一级映射表,linux内核中成为pmd。其表项提供的不再是物理段地址,而是二级页表的基地址。32位虚拟地址的高12位作为一级页表的索引值,找到相应的一级页表,每个表项指向一个二级页表。从虚拟地址的次8位作为访问二级页表的索引值,得到相应的页表项,从这个页表项中找到20位的物理页面地址。最后将这20位物理页面地址和虚拟地址的低12位拼凑在一起,得到最终的32位物理地址。这个过程在ARM32架构中由MMU硬件完成,软件不需要介入。
早期Linux内核是基于x86体系结构设计的,x86页表中有3个标志位是ARM32硬件页表没有的。
PTE_DIRTY:cpu在写操作时会设置该标志位,表示对应页面被写过,为脏页。
PTE_YOUNG:CPU访问该页时会设置该标志位。在页面换出时,如果该标志位位置了,说明该页刚被访问过,页面是young的,不适合把该页换出,同时清除该标志位。
PTE_PRESENT:表示页在内存中。
针对这种情况,Linux维护了一份Linux版本的页表,用于模拟这些标志位。由于一个页面是4KB大小,每个section 1MB空间,需要256个pte页表项,占用1KB,Linux版本也需要占用1KB,所以Linux创建二级页表时,以2MB为单位,申请1个PAGE存放对应的页表项。前2KB存放linux版本页表,后2KB存放硬件页表。存放地址如下。
* pgd pte
* | |
* +--------+
* | | +------------+ +0
* +- - - - + | Linux pt 0 |
* | | +------------+ +1024
* +--------+ +0 | Linux pt 1 |
* | |-----> +------------+ +2048
* +- - - - + +4 | h/w pt 0 |
* | |-----> +------------+ +3072
* +--------+ +8 | h/w pt 1 |
* | | +------------+ +4096
下图表示ARM硬件二级页表属性,Linux内核使用的是Small page。
可以看到每个页表项中不仅包含了下级页表或者物理地址的映射地址(除了无效页表项),而且包含了诸如:TEX S SBZ AP 等属性位,这些属性位影响着 CPU 对内存的访问动作。
C 位代表是否cache。
B 位代表是否使能高速buffer。
TEX C B 用来设置内存种类与缓存策略。正常内核开发时,需要关心的基本也就是是否使能cache和buffer。
APX/AP 被称作访问权限标志位,规定了映射到的内存的访问权限:可读、可写、只读等等。
S 标志某块内存的共享性,设置了标志位 S 的表项所映射的物理内存是可共享的,反之则是非可共享的。
XN 代表可执行,设置了表示页面不可执行。
内核中预定义了一些宏,用于页表属性设置,包括Linux版本页表,以及硬件版本页表。
先看Linux版本页表对应的宏,L开头代表Linux。由于使用2级映射,所以宏位于pgtable-2level.h。
/*
* "Linux" PTE definitions.
*
* We keep two sets of PTEs - the hardware and the linux version.
* This allows greater flexibility in the way we map the Linux bits
* onto the hardware tables, and allows us to have YOUNG and DIRTY
* bits.
*
* The PTE table pointer refers to the hardware entries; the "Linux"
* entries are stored 1024 bytes below.
*/
#define L_PTE_VALID (_AT(pteval_t, 1) << 0) /* Valid */
#define L_PTE_PRESENT (_AT(pteval_t, 1) << 0)
#define L_PTE_YOUNG (_AT(pteval_t, 1) << 1)
#define L_PTE_DIRTY (_AT(pteval_t, 1) << 6)
#define L_PTE_RDONLY (_AT(pteval_t, 1) << 7)
#define L_PTE_USER (_AT(pteval_t, 1) << 8)
#define L_PTE_XN (_AT(pteval_t, 1) << 9)
#define L_PTE_SHARED (_AT(pteval_t, 1) << 10) /* shared(v6), coherent(xsc3) */
#define L_PTE_NONE (_AT(pteval_t, 1) << 11)
/*
* These are the memory types, defined to be compatible with
* pre-ARMv6 CPUs cacheable and bufferable bits: n/a,n/a,C,B
* ARMv6+ without TEX remapping, they are a table index.
* ARMv6+ with TEX remapping, they correspond to n/a,TEX(0),C,B
*
* MT type Pre-ARMv6 ARMv6+ type / cacheable status
* UNCACHED Uncached Strongly ordered
* BUFFERABLE Bufferable Normal memory / non-cacheable
* WRITETHROUGH Writethrough Normal memory / write through
* WRITEBACK Writeback Normal memory / write back, read alloc
* MINICACHE Minicache N/A
* WRITEALLOC Writeback Normal memory / write back, write alloc
* DEV_SHARED Uncached Device memory (shared)
* DEV_NONSHARED Uncached Device memory (non-shared)
* DEV_WC Bufferable Normal memory / non-cacheable
* DEV_CACHED Writeback Normal memory / write back, read alloc
* VECTORS Variable Normal memory / variable
*
* All normal memory mappings have the following properties:
* - reads can be repeated with no side effects
* - repeated reads return the last value written
* - reads can fetch additional locations without side effects
* - writes can be repeated (in certain cases) with no side effects
* - writes can be merged before accessing the target
* - unaligned accesses can be supported
*
* All device mappings have the following properties:
* - no access speculation
* - no repetition (eg, on return from an exception)
* - number, order and size of accesses are maintained
* - unaligned accesses are "unpredictable"
*/
#define L_PTE_MT_UNCACHED (_AT(pteval_t, 0x00) << 2) /* 0000 */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 0x01) << 2) /* 0001 */
#define L_PTE_MT_WRITETHROUGH (_AT(pteval_t, 0x02) << 2) /* 0010 */
#define L_PTE_MT_WRITEBACK (_AT(pteval_t, 0x03) << 2) /* 0011 */
#define L_PTE_MT_MINICACHE (_AT(pteval_t, 0x06) << 2) /* 0110 (sa1100, xscale) */
#define L_PTE_MT_WRITEALLOC (_AT(pteval_t, 0x07) << 2) /* 0111 */
#define L_PTE_MT_DEV_SHARED (_AT(pteval_t, 0x04) << 2) /* 0100 */
#define L_PTE_MT_DEV_NONSHARED (_AT(pteval_t, 0x0c) << 2) /* 1100 */
#define L_PTE_MT_DEV_WC (_AT(pteval_t, 0x09) << 2) /* 1001 */
#define L_PTE_MT_DEV_CACHED (_AT(pteval_t, 0x0b) << 2) /* 1011 */
#define L_PTE_MT_VECTORS (_AT(pteval_t, 0x0f) << 2) /* 1111 */
#define L_PTE_MT_MASK (_AT(pteval_t, 0x0f) << 2)
内核对应的宏,位于pgtable-2level-hwdef.h
/*
* + Level 2 descriptor (PTE)
* - common
*/
#define PTE_TYPE_MASK (_AT(pteval_t, 3) << 0)
#define PTE_TYPE_FAULT (_AT(pteval_t, 0) << 0)
#define PTE_TYPE_LARGE (_AT(pteval_t, 1) << 0)
#define PTE_TYPE_SMALL (_AT(pteval_t, 2) << 0)
#define PTE_TYPE_EXT (_AT(pteval_t, 3) << 0) /* v5 */
#define PTE_BUFFERABLE (_AT(pteval_t, 1) << 2)
#define PTE_CACHEABLE (_AT(pteval_t, 1) << 3)
/*
* - extended small page/tiny page
*/
#define PTE_EXT_XN (_AT(pteval_t, 1) << 0) /* v6 */
#define PTE_EXT_AP_MASK (_AT(pteval_t, 3) << 4)
#define PTE_EXT_AP0 (_AT(pteval_t, 1) << 4)
#define PTE_EXT_AP1 (_AT(pteval_t, 2) << 4)
#define PTE_EXT_AP_UNO_SRO (_AT(pteval_t, 0) << 4)
#define PTE_EXT_AP_UNO_SRW (PTE_EXT_AP0)
#define PTE_EXT_AP_URO_SRW (PTE_EXT_AP1)
#define PTE_EXT_AP_URW_SRW (PTE_EXT_AP1|PTE_EXT_AP0)
#define PTE_EXT_TEX(x) (_AT(pteval_t, (x)) << 6) /* v5 */
#define PTE_EXT_APX (_AT(pteval_t, 1) << 9) /* v6 */
#define PTE_EXT_COHERENT (_AT(pteval_t, 1) << 9) /* XScale3 */
#define PTE_EXT_SHARED (_AT(pteval_t, 1) << 10) /* v6 */
#define PTE_EXT_NG (_AT(pteval_t, 1) << 11) /* v6 */
ARMv7架构,set_pte_ext位于arch/arm/mm/proc-v7-2level.S中,代码实现如下:
#define set_pte_ext(ptep,pte,ext) cpu_set_pte_ext(ptep,pte,ext)
该接口有三个参数,Linux版本pte地址,要写入页表的属性,以及额外的属性。
ENTRY(cpu_v7_set_pte_ext)
#ifdef CONFIG_MMU
str r1, [r0] @ linux version @直接把pte属性写入Linux版本页表
bic r3, r1, #0x000003f0 @r1值清除bit4~bit9存储到r3,r3作为临时变量存储硬件pte属性。
bic r3, r3, #PTE_TYPE_MASK @清除r3 bit0、bit1,没有清除bit3、bit2是因为cache、buffer这两位,hw和linux保持一致。
orr r3, r3, r2 @r3=r3|r2,r2是额外属性
orr r3, r3, #PTE_EXT_AP0 | 2 @设置hw bit4(AP0bit),设置hw bit1(根据上图,可以得知small page下bit1必须等于1)
tst r1, #1 << 4 @判断r1传入的页表属性,是否设置了bit4(tex位)
orrne r3, r3, #PTE_EXT_TEX(1) @如果linux属性设置了tex位,则硬件版本也要置位tex(hw bit6~8),两者bit偏移位置不一样。
eor r1, r1, #L_PTE_DIRTY @翻转r1 DIRTY位(bit6)
tst r1, #L_PTE_RDONLY | L_PTE_DIRTY @判断是否设置了只读。+
orrne r3, r3, #PTE_EXT_APX @设置硬件版本hw bit9(APX值(置1只读))
tst r1, #L_PTE_USER @判断传入属性是否设置L_PTE_USR
orrne r3, r3, #PTE_EXT_AP1 @设置硬件版本hw bit5(AP1bit)
tst r1, #L_PTE_XN @判断传入属性是否设置XN(不可执行)
orrne r3, r3, #PTE_EXT_XN @设置硬件版本hw bit0(不可执行位)
tst r1, #L_PTE_YOUNG
tstne r1, #L_PTE_VALID
eorne r1, r1, #L_PTE_NONE
tstne r1, #L_PTE_NONE
moveq r3, #0
ARM( str r3, [r0, #2048]! )
THUMB( add r0, r0, #2048 )
THUMB( str r3, [r0] )
ALT_SMP(W(nop))
ALT_UP (mcr p15, 0, r0, c7, c10, 1) @ flush_pte
#endif
bx lr
ENDPROC(cpu_v7_set_pte_ext)
根据汇编代码,可以看到清除了linux页表属性的bit0、bit1、bit4~bit9。也即bit2、bit3 linux页表和硬件页表一致。其余bit不一样。基本注释都已在上面写了。
DIRTY页模拟,创建映射时,设置硬件页表是只读的。当往页面写入时,会触发写权限缺页中断。(虽然Linux版本页面表标记了可写权限,但是ARM硬件表,没有写入权限。)在缺页中断handle_pte_fault中会在该页的Linux版本PTE中标记为“dirty”。
tst r1, #L_PTE_YOUNG
tstne r1, #L_PTE_VALID
eorne r1, r1, #L_PTE_NONE
tstne r1, #L_PTE_NONE
moveq r3, #0
这部分代码,可以等同于如下:
tst r1, @L_PTE_YOUNG
tstne r1, #L_PTE_PRESENT
moveq r3, #0
如果没有设置L_PTE_YOUNG并且L_PTE_PRESENT置位,那么保持Linux版本页表不变,把ARM硬件版本页表项内容清0。