揭秘Arm32 Linux的User和Kernel的页表映射

原创: ZenonXiu(修志龙) 微信公众号 MindShare思享 



我方观点


这个问题有些复杂,需要分成不同的情况来说

先说一下结论:

 

1.   如果是Cortex-A9/5等不支持LPAELarge Physical Address Extension)的CPU,或是那些支持LPAECPUCortex-A7/A15/A17,但是在Kernel没有使能LPAE (CONFIG_ARM_LPAE没有使能)则只使用一个TTBR0,不使用TTBR1.

2.   支持LPAECPUCortex-A7/A15/A17,但是在Kernel使能LPAE (CONFIG_ARM_LPAE使能)则使用TTBR0TTBR1.

 

为什么要使用两个页表


Linux的地址空间分成用户和内核空间。

用户地址空间的虚拟地址与物理地址的对应是随不同的Application切换的,但是内核空间的虚拟地址和物理地址的对应是不变的,也就是全局的。

用户空间和内核空间的划分是由kernel配置选项CONFIG_PAGE_OFFSET来决定的。可以配置成3G/1G,或是2G2G

 

每次创建一个新的进程的时候,kernelcopy父进程的memory map,包括kernelmapping, 这个过程如下:

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));

 

Kernelmapping会保存在swapper_pg_dir,这是一个全局的页表,它会在以上的过程中拷贝到每个进程的页表中。也许你会想,整个kernel的页表拷贝是不是很大的工作量?而且kerenel mapping一旦有些改变,每个进程的kernel mapping都需要更新?

 

实际情况是,进程的kernelmapping不需要全部copy,只需要copy第一级顶层页表项就可以。

 

TTBRx与LPAE


回到TTBR0/TTBR1的话题,在Arm构架中v7-A开始引进TTBR0/TTBR1. 基本想法是,将Userkernelmapping分开,MMU TTBR0指向User的页表,TTBR1指向kernel的页表。MMU硬件会根据输入的虚拟地址自动选择TTBR0或是TTBR1指向的页表来做虚拟到物理地址转换。

 


在没有LPAECPU上,或是有LPAE hardware支持但是software没有使能,userkernel空间的划分是有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, 或者不划分。

 

可以看出,它不能划分出UserKernel:3G/1G. 所以实际上如果Kernel配置成2G/2G划分,是可以使用TTBR0/TTBR1的,但是这样做的话,就会导致使用3G/1G2G2Gkernel需要不同处理,会带来开发和维护的代价。

所以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 mobileOS是一Symbian WinCE为主。

 

因此,Arm在加入LPAE扩展的时候,做了一些修改,

1.   允许对地址空间更灵活的划分,

2.   而且上下两个地址空间开始地址和大小可以分别设置

这是由TTBCR.T0SZTTBCR.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) 到最大地址



在有LPAECPU,而且software使能该功能,可以有以下地址分割方式,

所以我们现在可以分割出3G1GUser/Kernel 地址空间。

 

代码分析

head.s

stext中,在eanble  MMU之后了,跳转到__v7_proc_info->__cpu_flush执行。

proc-v7.S中,__v7_proc_info中使用__v7_proc宏定义定义了其中的一些成员变量,其中__cpu_flush=__v7_setup.__v7_setupproc-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中会设置TTBCRTTBRx.


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是一样的.


如果疑问,欢迎探讨。



猜你喜欢

转载自blog.csdn.net/weixin_39366778/article/details/80491257