
MMU 在 enable 之后, ARM core 对虚拟地址的访问流程
所谓translation table entry 也就是通常我们说的 page table 映射格式。它分为一级和二级。主要描述如下:

对于 section, super section 映射方式,使用一级短描述符页表项即可,不需要二级描述符;
对于 page 则需要两级描述符进行描述;
各个类型映射如何区分?
First-level short descriptor 的 bit[1:0] 区分
| bit[1:0] | 描述 |
| 0x00 | 无效 |
| 0x01 | Page table |
| 0x1 | section/super section table |
| 0x11 | section/super section 如果 PXN 开启 |

二级短描述区分 large page, small page 方式: 根据 Bit[1:0] 区分。
0x01: large page
0x1x: small page

ARMV8 的 内存属性分为两大类: normal + device
主要用于配置内存相关属性,处理器可以对 normal 属性内存 re-order, repeat 和 merge access
如其名,是用于外设寄存器配置,分为:

上图是根据 linux 虚拟地址映射的index 架构绘制的ARM64 版本,linux 使用如下几个宏来描述页表信息
ARM64 使用 低48 bit 来进行描述地址,PGDIR_SHIFT = 39,PTRS_PER_PGD = (1 << 9)
- #ifndef pgd_index
- /* Must be a compile-time constant, so implement it as a macro */
- #define pgd_index(a) (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
- #endif
-
- static inline pgd_t *pgd_offset_pgd(pgd_t *pgd, unsigned long address)
- {
- return (pgd + pgd_index(address));
- };
-
- #ifndef pgd_offset
- #define pgd_offset(mm, address) pgd_offset_pgd((mm)->pgd, (address))
- #endif
pgd_offset : mm->pgd 指向 pgd_t 的数组,它加上 index 即可所引导 va 对应 pgd entry
pgd_index: arm64 使用 va 中 bit[47:39] 这9 个bit 来描述 pgd_index
- /* include/asm-generic/pgtable-nop4d.h */
- static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address)
- {
- return (p4d_t *)pgd;
- }
ARM64 使用 VA 的 bit[38:30] 这个 9 个 bit 来描述 pud_index, PTRS_PER_PUD = (1 << 9);
- #define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
- #define __va(x) ((void *)__phys_to_virt((phys_addr_t)(x)))
-
- /* include/linux/pgtable.h */
- static inline unsigned long p4d_page_vaddr(p4d_t p4d)
- {
- return (unsigned long)__va(p4d_page_paddr(p4d));
- }
-
- #ifndef pud_index
- static inline unsigned long pud_index(unsigned long address)
- {
- return (address >> PUD_SHIFT) & (PTRS_PER_PUD - 1);
- }
- #define pud_index pud_index
- #endif
-
- #ifndef pud_offset
- static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
- {
- return (pud_t *)p4d_page_vaddr(*p4d) + pud_index(address);
- }
- #define pud_offset pud_offset
- #endif
根据 pud 找到指向 pmd 的数组,然后根据 pmd_index 找到索引,最终组合找到 pmd entry.
ARM64 使用 va 的 bit[11:0] 来作为pte index 索引,PTRS_PER_PTE = (1 << 12);
- /* include/linux/pgtable.h */
- static inline unsigned long pte_index(unsigned long address)
- {
- return (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1);
- }
- #define pte_index pte_index
-
- #ifndef pte_offset_kernel
- static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
- {
- return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
- }
- #define pte_offset_kernel pte_offset_kernel
- #endif
-
- #define pte_offset_map(dir, address) pte_offset_kernel((dir), (address))
- #define pte_unmap(pte) ((void)(pte)) /* NOP */
- /* arch/arm64/kernel/vmlinux.lds.S */
- . = ALIGN(PAGE_SIZE);
- init_pg_dir = .;
- . += INIT_DIR_SIZE;
- init_pg_end = .;
-
- idmap_pg_dir = .;
- . += IDMAP_DIR_SIZE;
- idmap_pg_end = .;
-
- swapper_pg_dir = .;
- . += PAGE_SIZE
其中 init_pg_dir 是 pgtable 地址
idmap_pg_dir 是 identity map 地址
swapper_pg_dir 是页表映射地址
- /* arch/arm64/kernel/head.S */
- SYM_CODE_START(primary_entry)
- bl preserve_boot_args
- bl el2_setup // Drop to EL1, w0=cpu_boot_mode
- adrp x23, __PHYS_OFFSET
- and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
- bl set_cpu_boot_mode_flag
- bl __create_page_tables
- bl __cpu_setup // initialise processor
- b __primary_switch
- SYM_CODE_END(primary_entry)
初始化阶段在 prmary_entry 中创建 page table.
- SYM_FUNC_START_LOCAL(__create_page_tables)
- mov x28, lr
- adrp x0, init_pg_dir
- adrp x1, init_pg_end
- sub x1, x1, x0
- bl __inval_dcache_area
-
- /*
- * Clear the init page tables.
- */
- adrp x0, init_pg_dir
- adrp x1, init_pg_end
- sub x1, x1, x0
- 1: stp xzr, xzr, [x0], #16
- stp xzr, xzr, [x0], #16
- stp xzr, xzr, [x0], #16
- stp xzr, xzr, [x0], #16
- subs x1, x1, #64
- b.ne 1b
- mov x7, SWAPPER_MM_MMUFLAGS
-
- /*
- * Create the identity mapping.
- */
- adrp x0, idmap_pg_dir
- adrp x3, __idmap_text_start // __pa(__idmap_text_start)
- #if (VA_BITS < 48)
-
- #else
- /*
- * If VA_BITS == 48, we don't have to configure an additional
- * translation level, but the top-level table has more entries.
- */
- mov x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)
- str_l x4, idmap_ptrs_per_pgd, x5
- #endif
- 1:
- ldr_l x4, idmap_ptrs_per_pgd
- mov x5, x3 // __pa(__idmap_text_start)
- adr_l x6, __idmap_text_end // __pa(__idmap_text_end)
-
- map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14
进行页表映射是通过 map_memory 汇编实现的。
- .macro map_memory, tbl, rtbl, vstart, vend, flags, phys, pgds, istart, iend, tmp, count, sv
- sub \vend, \vend, #1
- add \rtbl, \tbl, #PAGE_SIZE
- mov \sv, \rtbl
- mov \count, #0
- compute_indices \vstart, \vend, #PGDIR_SHIFT, \pgds, \istart, \iend, \count
- populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
- mov \tbl, \sv
- mov \sv, \rtbl
-
- #if SWAPPER_PGTABLE_LEVELS > 3
- compute_indices \vstart, \vend, #PUD_SHIFT, #PTRS_PER_PUD, \istart, \iend, \count
- populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
- mov \tbl, \sv
- mov \sv, \rtbl
- #endif
-
- #if SWAPPER_PGTABLE_LEVELS > 2
- compute_indices \vstart, \vend, #SWAPPER_TABLE_SHIFT, #PTRS_PER_PMD, \istart, \iend, \count
- populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
- mov \tbl, \sv
- #endif
-
- compute_indices \vstart, \vend, #SWAPPER_BLOCK_SHIFT, #PTRS_PER_PTE, \istart, \iend, \count
- bic \count, \phys, #SWAPPER_BLOCK_SIZE - 1
- populate_entries \tbl, \count, \istart, \iend, \flags, #SWAPPER_BLOCK_SIZE, \tmp
- .endm
在分析 map_memory 之前,我们分析下 compute_indices,它是用来计算 vstart 和 vend 对应的 pgtable level 的 index 的,两者之差保存在 count 中;
populate_entries 最终建立指向下一级的映射或者 last level 映射
- .macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
- .Lpe\@: phys_to_pte \tmp1, \rtbl
- orr \tmp1, \tmp1, \flags // tmp1 = table entry
- str \tmp1, [\tbl, \index, lsl #3]
- add \rtbl, \rtbl, \inc // rtbl = pa next level
- add \index, \index, #1
- cmp \index, \eindex
- b.ls .Lpe\@
- .endm
对于内存的 memory, 其内存属性支持: memory-normal 和 memory-non-cached,其使用函数是:
- /* arch/arm64/include/asm/pgtable.h */
- #define pgprot_writecombine(prot) \
- __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)
对于 device 类型的属性有:
- /* arch/arm64/include/asm/pgtable.h */
- #define pgprot_noncached(prot) \
- __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN)
- #define pgprot_device(prot) \
- __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN)
-
- #define pgprot_tagged(prot) \
- __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_TAGGED))
- #define pgprot_mhp pgprot_tagged
对于 device 类型的,有几种属性:
device-nGnRnE : 这个类似 armv7 的 strongly-ordered
device-nGnRE: 支持 gathering, non-re-order, 但是会有 early-ack 功能。