内存初始化之页表基本操作
- 内存管理
- 2023-06-18
- 162热度
- 0评论
页表级数
如何确定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