ARM64页表映射

1.ARMv8-A架构

  1. 基于ARMv8-A架构的处理器最大可以支持到48根地址线,也就是寻址2的48次方的虚拟地址空间,即虚拟地址空间范围为0x0000_0000_0000_0000~0x0000_FFFF_FFFF_FFFF,共256TB。
  2. 一个是从0x0000_0000_0000_0000到0x0000_FFFF_FFFF_FFFF,另外一个是从0xFFFF_0000_0000_0000到0xFFFF_FFFF_FFFF_FFFF。
  3. 基于ARMv8-A架构的处理器支持的页面大小可以是4KB、16KB或者64KB。映射的层级可以是3级或者4级(地址范围是不一样的)

下面是页面大小为4KB,地址宽度为48位,4级映射的内存分布图:

ARM64页表映射_第1张图片

 下面是页面大小为4KB,地址宽度为48位,3级映射的内存分布图:

ARM64页表映射_第2张图片

 2.示例4KB大小页面,48位地址宽度,4级映射

ARM64页表映射_第3张图片

  1. 如果输入的虚拟地址最高位bit[63]为1,那么这个地址是用于内核空间的,页表的基地址寄存器用TTBR1_EL1(Translation Table Base Register 1)。如果bit[63]等于0,那么这个虚拟地址属于用户空间,页表基地址寄存器用TTBR0。
  2. TTBRx寄存器保存了第0级页表的基地址(L0 Table base address, Linux内核中称为PGD), L0页表中有512个表项(Table Descriptor),以虚拟地址的bit[47:39]作为索引值在L0页表中查找相应的表项。每个表项的内容含有下一级页表的基地址,即L1页表(Linux内核中称为PUD)的基地址。
  3. PUD页表中有512个表项,以虚拟地址的bit[38:30]为索引值在PUD表中查找相应的表项,每个表项的内容含有下一级页表的基地址,即L2页表(Linux内核中称为PMD)的基地址。
  4. PMD页表中有512个表项,以虚拟地址的bit[29:21]为索引值在PMD表中查找相应的表项,每个表项的内容含有下一级页表的基地址,即L3页表(Linux内核中称为PTE)的基地址。
  5. 在PTE页表中,以虚拟地址的bit[20:12]为索引值在PTE表中查找相应的表项,每个PTE表项中含有最终的物理地址的bit[47:12],和虚拟地址中bit[11:0]合并成最终的物理地址,完成地址翻译过程。

3.示例39位有效位,4KB大小页面,3级页表

ARM64页表映射_第4张图片

  • 当CONFIG_PGTABLE_LEVELS=4时:pgd-->pud-->pmd-->pte;
  • 当CONFIG_PGTABLE_LEVELS=3时,没有PUD页表:pgd(pud)-->pmd-->pte;

4.pgd_offset_k函数

通过pgd_offset_k()宏来得到具体的PGD页面目录项的表项。首先通过init_mm数据结构的pgd成员来获取PGD页表的基地址,然后通过pgd_index()来计算PGD页表中的偏移量offset。

/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr)     pgd_offset(&init_mm, addr)

#define pgd_offset(mm, addr)      ((mm)->pgd+pgd_index(addr))

/* to find an entry in a page-table-directory */
#define pgd index(addr)     (((addr) >> PGDIR SHIFT) & (PTRS PER PGD - 1))
  • 通过计算可以得到PGDIR_SHIFT等于39, PUD_SHIFT等于30, PMD_SHIFT等于21。
  • 每级页表的页表项数目分别用PTRS_PER_PGD、PTRS_PER_PUD、PTRS_PER_PMD和PTRS_PER_PTE来表示,都等于512。
  • PGDIR_SIZE宏表示一个PGD页表项能覆盖的内存范围大小为512GB。PUD_SIZE等于1GB, PMD_SIZE等于2MB, PAGE_SIZE等于4KB。

5.create_mapping函数

通过init_mm数据结构的pgd成员来获取, swapper_pg_dir全局变量指向PGD页表基地址。

        [arch/arm64/kernel/vmlinux.lds.S]

       idmap_pg_dir = .;
       . += IDMAP_DIR_SIZE;
       swapper_pg_dir = .;
       . += SWAPPER_DIR_SIZE;

       [arch/arm64/include/asm/page.h]

       #define SWAPPER_PGTABLE_LEVELS  (CONFIG_ARM64_PGTABLE_LEVELS - 1)
       #define SWAPPER DIR SIZE     (SWAPPER PGTABLE LEVELS * PAGE SIZE)

假设CONFIG_ARM64_PGTABLE_LEVELS定义为4,那么SWAPPER_DIR_SIZE大小就等于3个PAGE_SIZE的大小。从vmlinux.lds.S链接文件可以看到,PGD页表的大小定义为3个PAGE_SIZE。swapper_pg_dir的起始地址由vmlinux.lds.S链接文件计算得来。

6. alloc_init_pte()配置PTE页表项

      static void alloc_init_pte(pmd_t *pmd, unsigned long addr,
                              unsigned long end, unsigned long pfn,
                              pgprot_t prot,
                              void *(*alloc)(unsigned long size))
      {
            pte_t *pte;

            if (pmd_none(*pmd) || pmd_sect(*pmd)) {
                pte = alloc(PTRS_PER_PTE * sizeof(pte_t));
                if (pmd_sect(*pmd))
                      split_pmd(pmd, pte);
                __pmd_populate(pmd, __pa(pte), PMD_TYPE_TABLE);
                flush_tlb_all();
            }
            BUG_ON(pmd_bad(*pmd));

            pte = pte_offset_kernel(pmd, addr);
            do {
                set_pte(pte, pfn_pte(pfn, prot));
                pfn++;
            } while (pte++, addr += PAGE_SIZE, addr ! = end);
      }
  • 首先判断PMD表项的内容是否为空?如果为空,说明下一级页表不存在,需要动态分配512个页表项,然后通过__pmd_populate()函数来设置PMD页表项。
  • 通过pte_offset_kernel()宏来索引到相应的PTE页表项。索引值可以通过pte_index()来计算,最终会使用虚拟地址bit[20:12]来做索引值。
  • 接下来以PAGE_SIZE即4KB大小为步长,通过while循环来设置PTE页表项。将pteval写入pteptr所指的页表项中:
#define set_pte(pteptr, pteval) (*(pteptr) = pteval)
  1. PTE_DIRTY:CPU在写操作时会设置该标志位,表示对应页面被写过,为脏页。
  2. PTE_YOUNG:CPU访问该页时会设置该标志位。在页面换出时,如果该标志位置位了,说明该页刚被访问过,页面是young的,不适合把该页换出,同时清除该标志位。
  3. PTE_PRESENT:表示页在内存中。
     

参考文档:

Linux内核ARM64页表映射

ARMv8 MMU及Linux页表映射

你可能感兴趣的:(linux内核内存管理,linux)