内存初始化之页表基本操作

页表级数

如何确定page table level?确定了VABITS和PAGES size之后,页表级数也可确定,根据内核的配置如下:

config PGTABLE_LEVELS
    int
    default 2 if ARM64_16K_PAGES && ARM64_VA_BITS_36
    default 2 if ARM64_64K_PAGES && ARM64_VA_BITS_42
    default 3 if ARM64_64K_PAGES && (ARM64_VA_BITS_48 || ARM64_VA_BITS_52)
    default 3 if ARM64_4K_PAGES && ARM64_VA_BITS_39 
    default 3 if ARM64_16K_PAGES && ARM64_VA_BITS_47
    default 4 if !ARM64_64K_PAGES && ARM64_VA_BITS_48

另外在代码头文件中还有宏定义确定
arch/arm64/include/asm/pgtable-hwdef.h

#define ARM64_HW_PGTABLE_LEVELS(va_bits) (((va_bits) - 4) / (PAGE_SHIFT - 3)) 
Va_bits虚拟地址位宽,PAGE_SHIFT就是page size。

页表表项大小

#define ARM64_HW_PGTABLE_LEVEL_SHIFT(n) ((PAGE_SHIFT - 3) * (4 - (n)) + 3)

#if CONFIG_PGTABLE_LEVELS > 2
#define PMD_SHIFT       ARM64_HW_PGTABLE_LEVEL_SHIFT(2)
#define PMD_SIZE        (_AC(1, UL) << PMD_SHIFT)
#define PMD_MASK        (~(PMD_SIZE-1))
#define PTRS_PER_PMD        PTRS_PER_PTE
#endif

#if CONFIG_PGTABLE_LEVELS > 3
#define PUD_SHIFT       ARM64_HW_PGTABLE_LEVEL_SHIFT(1)
#define PUD_SIZE        (_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK        (~(PUD_SIZE-1))
#define PTRS_PER_PUD        PTRS_PER_PTE
#endif

#define PGDIR_SHIFT     ARM64_HW_PGTABLE_LEVEL_SHIFT(4 - CONFIG_PGTABLE_LEVELS)
#define PGDIR_SIZE      (_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK      (~(PGDIR_SIZE-1))
#define PTRS_PER_PGD        (1 << (VA_BITS - PGDIR_SHIFT))
  • XXX_SHIFT:各级页表索引在虚拟地址中的偏移,如下图。
  • XXX_SIZE:各级页表表项描述的地址空间大小(一个条目),如arm64 PMD_SIZE 为2MB(2^21)。
  • XXX_MASK:各级页表屏蔽位掩码。
  • PTRS_PER_XXX:各级页表存放的表项个数 一般2^8=512。

CONFIG_ARM64_VA_BITS_39=y
CONFIG_ARM64_VA_BITS=39
CONFIG_ARM64_PA_BITS_48=y
CONFIG_ARM64_PA_BITS=48
CONFIG_ARM64_PAGE_SHIFT=12

PMD_SHIFT=21    //虚拟地址右移21位得到PMD表项entry地址,指向PTE表的。
PUD_SHIFT=30    //虚拟地址右移30位得到PUD表项entry地址,指向PMD表。
PGDIR_SHIFT=30  //PUD=PGD
PTRS_PER_PGD=512
PTRS_PER_PTE=512
PTRS_PER_PMD=512
PTRS_PER_PUM=512

/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT      CONFIG_ARM64_PAGE_SHIFT
#define PAGE_SIZE       (_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK       (~(PAGE_SIZE-1))


CONFIG_ARM64_CONT_PTE_SHIFT=4
 //一个PTE(Page Table Entry)可以表示2^4=16个连续的物理页面,如果page size = 4K,那么一个PTE表示连续物理空间大小微16*4KB=64KB。

#define CONT_PTE_SHIFT      (CONFIG_ARM64_CONT_PTE_SHIFT + PAGE_SHIFT)  //=16
//PTE映射的连续物理页面大小,2^16=64KB。
#define CONT_PTES       (1 << (CONT_PTE_SHIFT - PAGE_SHIFT))
//一个连续物理页面需要PTE的数量,如上面64KB,需要16个PTE,实际也是64KB/4KB=16,每个PTE映射4KB大小。相当于给PTE再分个组,16个PTE组成一组,对应一段映射范围。

#define CONT_PTE_SIZE       (CONT_PTES * PAGE_SIZE)
//连续物理页面需要PTE映射的空间大小,4KB*16=64KB
#define CONT_PTE_MASK       (~(CONT_PTE_SIZE - 1))



#define CONT_PMD_SHIFT      (CONFIG_ARM64_CONT_PMD_SHIFT + PMD_SHIFT) //4+21=25
#define CONT_PMDS       (1 << (CONT_PMD_SHIFT - PMD_SHIFT)) //2^4=16
#define CONT_PMD_SIZE       (CONT_PMDS * PMD_SIZE) 
#define CONT_PMD_MASK       (~(CONT_PMD_SIZE - 1))

其他页表相关定义

表项数据类型定义

typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 p4dval_t;
typedef u64 pgdval_t;

typedef struct { pteval_t pte; } pte_t;
typedef struct { pmdval_t pmd; } pmd_t;
typedef struct { pudval_t pud; } pud_t;
typedef struct { pgdval_t pgd; } pgd_t;
ypedef struct { pteval_t pgprot; } pgprot_t;

获取表项索引值

#define pgd_index(addr)        (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pud_index(addr)        (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
#define pmd_index(addr)        (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#define pte_index(addr)        (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

获取表项地址

#define pgd_offset(mm, addr)   (pgd_offset_raw((mm)->pgd, (addr)))
#define pgd_offset_k(addr) pgd_offset(&init_mm, addr)
#define pud_offset_phys(dir, addr) (pgd_page_paddr(*(dir)) + pud_index(addr) * sizeof(pud_t))
#define pud_offset(dir, addr)      ((pud_t *)__va(pud_offset_phys((dir), (addr))))
#define pmd_offset_phys(dir, addr) (pud_page_paddr(*(dir)) + pmd_index(addr) * sizeof(pmd_t))
#define pmd_offset(dir, addr)      ((pmd_t *)__va(pmd_offset_phys((dir), (addr))))
#define pte_offset_phys(dir,addr)  (pmd_page_paddr(READ_ONCE(*(dir))) + pte_index(addr) * sizeof(pte_t))
#define pte_offset_kernel(dir,addr)    ((pte_t *)__va(pte_offset_phys((dir), (addr))))

通过虚拟地址,获取表项的地址(虚拟地址),用这些函数拿到表项中具体的地址用于填充等操作。

表项状态判断

#define xxx_none(pud)       (!pud_val(pud))   //判断是否为空表项,指向的下一级页表没分配
#define xxx_bad(pud)        (!pud_table(pud)) //判断是否为坏表项
#define xxx_present(pud)    pte_present(pud_pte(pud))//判断表项是否存在

表项设置

pte_wrprotect  设置为写保护
pte_mkwrite    设置为可写
pte_mkclean    清除脏标志
pte_mkdirty    设置脏标志
pte_mkyoung   设置为访问标志
pte_mkold     清除访问标志
set_pte        设置pte到ptep 
pte_pfn        页表项目中取出页帧号
pfn_pte        页帧号和标志组合成页表项

页目录/页表分配和释放

Xxx_alloc 页表分配,如分配页全局目录
Eg: pte_alloc

Xxx_free 页表释放,
Eg: pte_free