• ARM64 MMU 映射


    MMU 架构示意

    MMU 在 enable 之后, ARM core 对虚拟地址的访问流程

    1. ARM Core 到 MMU 的 TLB 中查找是否有地址转换缓存,若有则返回给ARM Core;
    2. TLB 中没有转换缓存,则到Table walk 保存在 DDR/IRAM 的 translation table;
    3. 在遍历 page table 过程中:
      1. Translation entry 不存在,则报一个 translation fault;
      2. 若页表项存在,检查物理地址对应的page 是否在内存中,若在返回地址;
      3. 若页表项对应物理地址不在内存中,此时产生 page fault, 需要从 disk 中将page 交换到内存
    4. ARM Core 根据返回地址访问地址时,会根据translation table 中指定的 attribute 来确定如何使用 cache 以及 访问权限检查
    5. 最后访问具体地址

    Translation table entry 格式

    所谓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

    地址转换流程 

    arm32:

    • 输入一个虚拟地址:
    • TTBR0 中在高14-N ~ 31 决定 translation base 地址
    • 根据虚拟地址的 bit 10:31-N 和 translation table 的地址决定这个地址在translation table 中偏移量
    • 找到对应的 translation entry

    内存属性

    ARMV8 的 内存属性分为两大类: normal + device

    1. Normal

    主要用于配置内存相关属性,处理器可以对 normal 属性内存 re-order, repeat 和 merge access

    1. device

    如其名,是用于外设寄存器配置,分为:

    • Device-nGnRnE: no gathering, no re-order, no early ack, 等同于 Strongly ordered 内存
    • Device-nGnRE: no gathering, no re-order, early ack, 因为会有 early ack 可能会带来一些时序上问题,需要作特殊处理
    • Device-nGRE:no gathering,这个限制又少了一层,增加了 re-order
    • Device-GRE: 最少限制

    PageTable

    上图是根据 linux 虚拟地址映射的index 架构绘制的ARM64 版本,linux 使用如下几个宏来描述页表信息

    pgd

    ARM64 使用 低48 bit 来进行描述地址,PGDIR_SHIFT = 39,PTRS_PER_PGD = (1 << 9)

    1. #ifndef pgd_index
    2. /* Must be a compile-time constant, so implement it as a macro */
    3. #define pgd_index(a) (((a) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
    4. #endif
    5. static inline pgd_t *pgd_offset_pgd(pgd_t *pgd, unsigned long address)
    6. {
    7. return (pgd + pgd_index(address));
    8. };
    9. #ifndef pgd_offset
    10. #define pgd_offset(mm, address) pgd_offset_pgd((mm)->pgd, (address))
    11. #endif

    pgd_offset : mm->pgd 指向 pgd_t 的数组,它加上 index 即可所引导 va 对应 pgd entry

    pgd_index: arm64 使用 va 中 bit[47:39] 这9 个bit 来描述 pgd_index

    p4d

    1. /* include/asm-generic/pgtable-nop4d.h */
    2. static inline p4d_t *p4d_offset(pgd_t *pgd, unsigned long address)
    3. {
    4. return (p4d_t *)pgd;
    5. }

    pud

    ARM64 使用 VA 的 bit[38:30] 这个 9 个 bit 来描述 pud_index, PTRS_PER_PUD = (1 << 9);

    1. #define __phys_to_virt(x) ((unsigned long)((x) - PHYS_OFFSET) | PAGE_OFFSET)
    2. #define __va(x) ((void *)__phys_to_virt((phys_addr_t)(x)))
    3. /* include/linux/pgtable.h */
    4. static inline unsigned long p4d_page_vaddr(p4d_t p4d)
    5. {
    6. return (unsigned long)__va(p4d_page_paddr(p4d));
    7. }
    8. #ifndef pud_index
    9. static inline unsigned long pud_index(unsigned long address)
    10. {
    11. return (address >> PUD_SHIFT) & (PTRS_PER_PUD - 1);
    12. }
    13. #define pud_index pud_index
    14. #endif
    15. #ifndef pud_offset
    16. static inline pud_t *pud_offset(p4d_t *p4d, unsigned long address)
    17. {
    18. return (pud_t *)p4d_page_vaddr(*p4d) + pud_index(address);
    19. }
    20. #define pud_offset pud_offset
    21. #endif

     

    根据 pud 找到指向 pmd 的数组,然后根据 pmd_index 找到索引,最终组合找到 pmd entry.

    pte

    ARM64 使用 va 的 bit[11:0] 来作为pte index 索引,PTRS_PER_PTE = (1 << 12);

    1. /* include/linux/pgtable.h */
    2. static inline unsigned long pte_index(unsigned long address)
    3. {
    4. return (address >> PAGE_SHIFT) & (PTRS_PER_PTE - 1);
    5. }
    6. #define pte_index pte_index
    7. #ifndef pte_offset_kernel
    8. static inline pte_t *pte_offset_kernel(pmd_t *pmd, unsigned long address)
    9. {
    10. return (pte_t *)pmd_page_vaddr(*pmd) + pte_index(address);
    11. }
    12. #define pte_offset_kernel pte_offset_kernel
    13. #endif
    14. #define pte_offset_map(dir, address) pte_offset_kernel((dir), (address))
    15. #define pte_unmap(pte) ((void)(pte)) /* NOP */

    初始化阶段的 MMU 映射

    pagetable 定义地址

    1. /* arch/arm64/kernel/vmlinux.lds.S */
    2. . = ALIGN(PAGE_SIZE);
    3. init_pg_dir = .;
    4. . += INIT_DIR_SIZE;
    5. init_pg_end = .;
    6. idmap_pg_dir = .;
    7. . += IDMAP_DIR_SIZE;
    8. idmap_pg_end = .;
    9. swapper_pg_dir = .;
    10. . += PAGE_SIZE

    其中 init_pg_dir 是 pgtable 地址

    idmap_pg_dir 是 identity map 地址

    swapper_pg_dir 是页表映射地址

    创建页表

    1. /* arch/arm64/kernel/head.S */
    2. SYM_CODE_START(primary_entry)
    3. bl preserve_boot_args
    4. bl el2_setup // Drop to EL1, w0=cpu_boot_mode
    5. adrp x23, __PHYS_OFFSET
    6. and x23, x23, MIN_KIMG_ALIGN - 1 // KASLR offset, defaults to 0
    7. bl set_cpu_boot_mode_flag
    8. bl __create_page_tables
    9. bl __cpu_setup // initialise processor
    10. b __primary_switch
    11. SYM_CODE_END(primary_entry)

    初始化阶段在 prmary_entry 中创建 page table.

    1. SYM_FUNC_START_LOCAL(__create_page_tables)
    2. mov x28, lr
    3. adrp x0, init_pg_dir
    4. adrp x1, init_pg_end
    5. sub x1, x1, x0
    6. bl __inval_dcache_area
    7. /*
    8. * Clear the init page tables.
    9. */
    10. adrp x0, init_pg_dir
    11. adrp x1, init_pg_end
    12. sub x1, x1, x0
    13. 1: stp xzr, xzr, [x0], #16
    14. stp xzr, xzr, [x0], #16
    15. stp xzr, xzr, [x0], #16
    16. stp xzr, xzr, [x0], #16
    17. subs x1, x1, #64
    18. b.ne 1b
    19. mov x7, SWAPPER_MM_MMUFLAGS
    20. /*
    21. * Create the identity mapping.
    22. */
    23. adrp x0, idmap_pg_dir
    24. adrp x3, __idmap_text_start // __pa(__idmap_text_start)
    25. #if (VA_BITS < 48)
    26. #else
    27. /*
    28. * If VA_BITS == 48, we don't have to configure an additional
    29. * translation level, but the top-level table has more entries.
    30. */
    31. mov x4, #1 << (PHYS_MASK_SHIFT - PGDIR_SHIFT)
    32. str_l x4, idmap_ptrs_per_pgd, x5
    33. #endif
    34. 1:
    35. ldr_l x4, idmap_ptrs_per_pgd
    36. mov x5, x3 // __pa(__idmap_text_start)
    37. adr_l x6, __idmap_text_end // __pa(__idmap_text_end)
    38. map_memory x0, x1, x3, x6, x7, x3, x4, x10, x11, x12, x13, x14

    进行页表映射是通过 map_memory 汇编实现的。

    1. .macro map_memory, tbl, rtbl, vstart, vend, flags, phys, pgds, istart, iend, tmp, count, sv
    2. sub \vend, \vend, #1
    3. add \rtbl, \tbl, #PAGE_SIZE
    4. mov \sv, \rtbl
    5. mov \count, #0
    6. compute_indices \vstart, \vend, #PGDIR_SHIFT, \pgds, \istart, \iend, \count
    7. populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
    8. mov \tbl, \sv
    9. mov \sv, \rtbl
    10. #if SWAPPER_PGTABLE_LEVELS > 3
    11. compute_indices \vstart, \vend, #PUD_SHIFT, #PTRS_PER_PUD, \istart, \iend, \count
    12. populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
    13. mov \tbl, \sv
    14. mov \sv, \rtbl
    15. #endif
    16. #if SWAPPER_PGTABLE_LEVELS > 2
    17. compute_indices \vstart, \vend, #SWAPPER_TABLE_SHIFT, #PTRS_PER_PMD, \istart, \iend, \count
    18. populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
    19. mov \tbl, \sv
    20. #endif
    21. compute_indices \vstart, \vend, #SWAPPER_BLOCK_SHIFT, #PTRS_PER_PTE, \istart, \iend, \count
    22. bic \count, \phys, #SWAPPER_BLOCK_SIZE - 1
    23. populate_entries \tbl, \count, \istart, \iend, \flags, #SWAPPER_BLOCK_SIZE, \tmp
    24. .endm

    在分析 map_memory 之前,我们分析下 compute_indices,它是用来计算 vstart 和 vend 对应的 pgtable level 的 index 的,两者之差保存在 count 中;

    populate_entries 最终建立指向下一级的映射或者 last level 映射

    1. .macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
    2. .Lpe\@: phys_to_pte \tmp1, \rtbl
    3. orr \tmp1, \tmp1, \flags // tmp1 = table entry
    4. str \tmp1, [\tbl, \index, lsl #3]
    5. add \rtbl, \rtbl, \inc // rtbl = pa next level
    6. add \index, \index, #1
    7. cmp \index, \eindex
    8. b.ls .Lpe\@
    9. .endm
    • 获取 rtbl 的 pte, 或上 flags保存在 tmp1
    • 将 tmp1 保存在 pgtable 对应 index << 3 的位置处
    • 将 rtbl 指向下一 pg level, index + PGSIZE

    Cache 属性更改

    对于内存的 memory, 其内存属性支持: memory-normal 和 memory-non-cached,其使用函数是:

    1. /* arch/arm64/include/asm/pgtable.h */
    2. #define pgprot_writecombine(prot) \
    3. __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_NC) | PTE_PXN | PTE_UXN)

    对于 device 类型的属性有:

    1. /* arch/arm64/include/asm/pgtable.h */
    2. #define pgprot_noncached(prot) \
    3. __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRnE) | PTE_PXN | PTE_UXN)
    4. #define pgprot_device(prot) \
    5. __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_DEVICE_nGnRE) | PTE_PXN | PTE_UXN)
    6. #define pgprot_tagged(prot) \
    7. __pgprot_modify(prot, PTE_ATTRINDX_MASK, PTE_ATTRINDX(MT_NORMAL_TAGGED))
    8. #define pgprot_mhp pgprot_tagged

    对于 device 类型的,有几种属性:

    device-nGnRnE : 这个类似 armv7 的 strongly-ordered

    device-nGnRE: 支持 gathering, non-re-order, 但是会有 early-ack 功能。

  • 相关阅读:
    并发-Java中的线程池
    (十二)Mybatis的缓存机制
    震惊 !!!DOM还能这么用,让我们跟随小编一起去看看吧 !
    详细介绍如何使用YOLOV8和KerasCV进行高效物体检测
    SpringBoot 06: springboot中使用redis
    【从零开始学习 SystemVerilog】11.3、SystemVerilog 断言—— Concurrent Assertions(并发断言)
    springcloudalibaba架构(23):RocketMQ普通消息和顺序消息
    JVM教程
    mongodb基本操作及使用
    力扣------路径总和 III
  • 原文地址:https://blog.csdn.net/kakaBack/article/details/126689768