我方观点
这个问题有些复杂,需要分成不同的情况来说
先说一下结论:
1. 如果是Cortex-A9/5等不支持LPAE(Large Physical Address Extension)的CPU,或是那些支持LPAE的CPU,Cortex-A7/A15/A17,但是在Kernel没有使能LPAE (CONFIG_ARM_LPAE没有使能). 则只使用一个TTBR0,不使用TTBR1.
2. 支持LPAE的CPU,Cortex-A7/A15/A17,但是在Kernel使能LPAE (CONFIG_ARM_LPAE使能). 则使用TTBR0和TTBR1.
为什么要使用两个页表
Linux的地址空间分成用户和内核空间。
用户地址空间的虚拟地址与物理地址的对应是随不同的Application切换的,但是内核空间的虚拟地址和物理地址的对应是不变的,也就是全局的。
用户空间和内核空间的划分是由kernel配置选项CONFIG_PAGE_OFFSET来决定的。可以配置成3G/1G,或是2G/2G。
每次创建一个新的进程的时候,kernel会copy父进程的memory map,包括kernel的mapping, 这个过程如下:
do_fork--> copy_process --> copy_mm --> dup_mm --> mm_init -->mm_alloc_pgd --> pgd_alloc -->
/*
* Copy over the kernel and IO PGDentries
*/
init_pgd = pgd_offset_k(0);
memcpy(new_pgd + USER_PTRS_PER_PGD,init_pgd + USER_PTRS_PER_PGD,
(PTRS_PER_PGD- USER_PTRS_PER_PGD) * sizeof(pgd_t));
Kernel的mapping会保存在swapper_pg_dir,这是一个全局的页表,它会在以上的过程中拷贝到每个进程的页表中。也许你会想,整个kernel的页表拷贝是不是很大的工作量?而且kerenel mapping一旦有些改变,每个进程的kernel mapping都需要更新?
实际情况是,进程的kernelmapping不需要全部copy,只需要copy第一级顶层页表项就可以。
TTBRx与LPAE
回到TTBR0/TTBR1的话题,在Arm构架中v7-A开始引进TTBR0/TTBR1. 基本想法是,将User和kernel的mapping分开,MMU TTBR0指向User的页表,TTBR1指向kernel的页表。MMU硬件会根据输入的虚拟地址自动选择TTBR0或是TTBR1指向的页表来做虚拟到物理地址转换。
在没有LPAE的CPU上,或是有LPAE hardware支持但是software没有使能,user和kernel空间的划分是有MMU TTBCR.N 来决定的,如下表所示,
TTBCR.N |
TTBR1页表覆盖地址的首地址 |
0b000 |
TTBR1 not used |
0b001 |
0x80000000 |
0b010 |
0x40000000 |
0b011 |
0x20000000 |
0b100 |
0x10000000 |
0b101 |
0x08000000 |
0b110 |
0x04000000 |
0b111 |
0x02000000 |
由此可知,可以划分的地址可以是,TTBCR.N可以划分出,
User/Kernel: 1G/3G, 2G/2Getc, 或者不划分。
可以看出,它不能划分出User/Kernel:3G/1G. 所以实际上如果Kernel配置成2G/2G划分,是可以使用TTBR0/TTBR1的,但是这样做的话,就会导致使用3G/1G和2G/2G的kernel需要不同处理,会带来开发和维护的代价。
所以ArmLinux kernel最终选择只使用TTBR0.这也就RusellKing所说的Arm Linux does not usee TTBR1.
http://hackers4hackers.blogspot.com/2014/02/arm-linux-do-not-use-ttbr1.html
这算是Arm构架没有考虑Linux使用场景的失误,因为在Android, Apple之前,Arm mobile的OS是一Symbian ,WinCE为主。
因此,Arm在加入LPAE扩展的时候,做了一些修改,
1. 允许对地址空间更灵活的划分,
2. 而且上下两个地址空间开始地址和大小可以分别设置
这是由TTBCR.T0SZ和TTBCR.T1SZ来设置的,
TTBCR |
覆盖地址范围 |
||
T0SZ |
T1SZ |
TTBR0 |
TTBR1 |
0b000 |
0b000 |
All addresses |
Not used |
M |
0b000 |
0 to 2^(32-M)-1 |
2^(32-M)到最大地址 |
0b000 |
N |
0 to (2^32-2^(32-N)-1) |
(2^32-2^(32-N)) 到最大地址 |
M |
N |
0 to (2^(32-M)-1) |
2^32-2^(32-N) 到最大地址 |
在有LPAE的CPU上,而且software使能该功能,可以有以下地址分割方式,
所以我们现在可以分割出3G/1GUser/Kernel 地址空间。
代码分析
head.s
stext中,在eanble MMU之后了,跳转到__v7_proc_info->__cpu_flush执行。
proc-v7.S中,__v7_proc_info中使用__v7_proc宏定义定义了其中的一些成员变量,其中__cpu_flush=__v7_setup.__v7_setup在proc-v7.S中定义如下:
#ifdef CONFIG_ARM_LPAE
mov r5, #0 @ highTTBR0
mov r8, r4, lsr#12 @TTBR1 is swapper_pg_dir pfn
#else
mov r8, r4 @ setTTBR1 to swapper_pg_dir
#endif
ldr r12, [r10,#PROCINFO_INITFUNC]
add r12, r12,r10 //r12->__v7_setup
ret r12 //调用__v7_setup
1: b __enable_mmu
在__v7_setup中会设置TTBCR和TTBRx.
proc-v7.s
mcr p15, 0, r10,c8, c7, 0 @invalidate I + D TLBs
v7_ttb_setup r10, r4, r5, r8, r3 @ TTBCR, TTBRx setup
ldr r3, =PRRR @ PRRR
ldr r6, =NMRR @ NMRR
mcr p15, 0, r3,c10, c2, 0 @ writePRRR
mcr p15, 0, r6,c10, c2, 1 @ writeNMRR
根据LPAE有没有使能,__v7_setup可以实现在,
1. LPAE没有使能,实现在Proc-v7-2level.s 中
2. LPAE使能,实现在Proc-v7-3level.s中
没用LPAE的情况
proc-v7-2level.s
/*
* Macro forsetting up the TTBRx and TTBCR registers.
* - \ttb0 and\ttb1 updated with the corresponding flags.
*/
.macro v7_ttb_setup,zero, ttbr0l, ttbr0h, ttbr1, tmp
mcr p15, 0,\zero, c2, c0, 2 @ TTB control register, set TTBCR=0, so only use TTBR0
ALT_SMP(orr \ttbr0l,\ttbr0l, #TTB_FLAGS_SMP)
ALT_UP(orr \ttbr0l,\ttbr0l, #TTB_FLAGS_UP)
ALT_SMP(orr \ttbr1,\ttbr1, #TTB_FLAGS_SMP)
ALT_UP(orr \ttbr1,\ttbr1, #TTB_FLAGS_UP)
mcr p15, 0, \ttbr1, c2, c0, 1 @load TTB1
.endm
使用LPAE的情况
/*
* TTBR0/TTBR1 split (PAGE_OFFSET):
* 0x40000000:T0SZ = 2, T1SZ = 0 (not used)
* 0x80000000: T0SZ = 0, T1SZ = 1
* 0xc0000000: T0SZ = 0, T1SZ = 2
*
* Only use this feature if PHYS_OFFSET <=PAGE_OFFSET, otherwise
* booting secondary CPUs would end up usingTTBR1 for the identity
* mapping set up in TTBR0.
*/
#if definedCONFIG_VMSPLIT_2G
#defineTTBR1_OFFSET 16 /* skip two L1 entries */
#elifdefined CONFIG_VMSPLIT_3G
#defineTTBR1_OFFSET (4096 * (1 + 3)) /* only L2, skip pgd + 3*pmd */
#else
#defineTTBR1_OFFSET 0
#endif
#defineTTBR1_SIZE (((PAGE_OFFSET>> 30) - 1) << 16)
proc-v7-3level.s
.macro v7_ttb_setup,zero, ttbr0l, ttbr0h, ttbr1, tmp
ldr \tmp,=swapper_pg_dir @swapper_pg_dir virtual address
cmp \ttbr1, \tmp,lsr #12 @PHYS_OFFSET > PAGE_OFFSET?
mrc p15, 0,\tmp, c2, c0, 2 @ TTBcontrol egister
orr \tmp, \tmp,#TTB_EAE //使能LPAE
ALT_SMP(orr \tmp,\tmp, #TTB_FLAGS_SMP)
ALT_UP(orr \tmp,\tmp, #TTB_FLAGS_UP)
ALT_SMP(orr \tmp,\tmp, #TTB_FLAGS_SMP << 16)
ALT_UP(orr \tmp,\tmp, #TTB_FLAGS_UP << 16)
/*
* Only use splitTTBRs if PHYS_OFFSET <= PAGE_OFFSET (cmp above),
* otherwisebooting secondary CPUs would end up using TTBR1 for the
* identity mappingset up in TTBR0.
*/
orrls \tmp, \tmp,#TTBR1_SIZE @ 设置TTBCR.T1SZ
mcr p15, 0,\tmp, c2, c0, 2 @设置 TTBCR
mov \tmp, \ttbr1,lsr #20
mov \ttbr1,\ttbr1, lsl #12
addls \ttbr1,\ttbr1, #TTBR1_OFFSET
mcrr p15, 1,\ttbr1, \tmp, c2 @ 设置TTBR1
.endm
Arm64的情况基本和Arm32使用LPAE是一样的.
如果疑问,欢迎探讨。