文章目录
1 物理内存的管理
1.1 页表
页表用于记录虚拟地址到物理地址的一一映射关系。每一个页表项除了存放物理页帧的指针,多余的比特位还记录页的访问权限、状态等附加信息。最原始的页表实现思路就是有多少个物理页帧就直接定义多少个页表项,这导致会耗费大量的内存空间来存储这些页表项,然后其中可能有很多根本就不会用到。层次化的页表用于支持对大地址空间的快速、高效的管理。
早期Linux内核把页表分为2级,后来扩展到4级,4.11以后版本把页表扩展到5级,这5级分别为:
- PGD(Page Global Directory)——页全局目录
- P4D(Page 4th Directory)——页四级目录
- PUD(Page Upper Directory)——页上层目录
- PMD(Page Middle Directory)——页中间目录
- PTE(Page Table)——直接页表
这里以4级页表为例进行一些简单的内核源码分析,且依然以arm64架构为例。
虚拟地址分解
4级页表将一个虚拟地址分解成5个部分,其中4个表项用于选择页,1个索引表示页内偏移,如下图:
内核中有关于上面各个部分的类似XXX_SHIFT
、XXX_SIZE
、XXX_MASK
的定义,如:
/* PAGE_SHIFT determines the page size */
#define PAGE_SHIFT CONFIG_ARM64_PAGE_SHIFT // 内页偏移的位数
#define PAGE_SIZE (_AC(1, UL) << PAGE_SHIFT) // 一个页的大小(即一个页下有多少个地址),一个PTE项一个页
#define PAGE_MASK (~(PAGE_SIZE-1)) // 位掩码用于提取各分量
#define PMD_SIZE (_AC(1, UL) << PMD_SHIFT) // 一个PMD项下有多少个地址
#define PMD_MASK (~(PMD_SIZE-1))
#define PUD_SIZE (_AC(1, UL) << PUD_SHIFT)
#define PUD_MASK (~(PUD_SIZE-1))
#define PGDIR_SIZE (_AC(1, UL) << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE-1))
个人对于用位掩码用于提取各分量还是有疑惑的。将一个虚拟地址vaddr
和某个表项的位掩码做与运算得到的并不是地址中该表项所占位置的值。如vaddr & PMD_MASK
得到的并不是PMD位置的值。要提取PMD位置的值应该用pmd_index()
函数。
此外还有PTRS_PER_PGD
指定了页全局目录中项的数目,PTRS_PER_PUD
对应于页上层目录中项的数目,PTRS_PER_PMD
对应于页中间目录中项的数目,PTRS_PER_PTE
则是直接页表中项的数目。
内核还定义了4个数据结构(定义在page.h中)来表示页表项的结构。只不过在arm64架构下他们都是unsigned long long
。
typedef u64 pteval_t;
typedef u64 pmdval_t;
typedef u64 pudval_t;
typedef u64 pgdval_t;
/* These are used to make use of C type-checking.. */
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;
使用struct而不是基本类型,以确保页表项的内容只能由相关的辅助函数处理,而决不能直接访问。
页表分析
内核源码还提供这些函数以方便进行页表的分析,以快速查找到虚拟地址对应的页表项:
可以简单分析一下xxx_index
和xxx_offset
函数。以pgd为例,pgd_index(addr)
提取出该地址的bit位中PGD所占位置的值,借助pgd_index(addr)
,函数pgd_offset()
可直接得出该地址在PGD目录下的下一级表项的虚拟空间地址(即PUD目录的地址)。之后再下级目录项的查找函数如pud_offset
需要先找到目录所在物理地址再转换回虚拟地址,这种转换的调用__va()
是采用线性映射。(可以理解为,页表项所在地址的映射不应依赖于页表本身,所以必须用线性映射)
/* to find an entry in a page-table-directory */
#define pgd_index(addr) (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pgd_offset_raw(pgd, addr) ((pgd) + pgd_index(addr))
#define pgd_offset(mm, addr) (pgd_offset_raw((mm)->pgd, (addr)))
#define pud_index(addr) (((addr) >> PUD_SHIFT) & (PTRS_PER_PUD - 1))
#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_index(addr) (((addr) >> PMD_SHIFT) & (PTRS_PER_PMD - 1))
#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))))