目录
本专栏文章将有70篇左右,欢迎+关注,订阅后续文章。
3.3 页表
页表作用:
包含将虚拟地址转换为物理地址的信息。
内核总是使用四级页表。所以如果一个体系架构只支持两级页表,则还需模拟第三和第四级页表。
四级页表:
PGD(Page Global Directory):页全局目录
PUD(Page Upper Directory):页上层目录
PMD(Page Middle Directory):页中间目录
PTE(Page Table Entry) :页表项
如何将一个虚拟地址找到对应物理地址?(即页表查询过程)
1. 读取页表基址寄存器 (PTBR) 值,其中存放PGD页表的物理地址。
2. 根据虚拟地址算出PGD index,再结合PGD物理地址,得到PUD物理地址。
3. 根据虚拟地址算出PUD index,再结合PUD物理地址,得到PMD物理地址。
4. 根据虚拟地址算出PMD index,再结合PMD物理地址,得到PTE物理地址。
不同的体系架构中用不同寄存器作为基址寄存器 (PTBR)
x86:CR3(Control Register 3)
ARM:TTBR(Translation Table Base Register)
3.3.1 数据结构
一个虚拟地址使用unsigned long定义。
对于所有体系架构都成立:
sizeof(void *) = sizeof(unsigned long)
一个虚拟地址分为5部分:
#define PAGE_SHIFT 12
则一个页大小2的12次方,即4K。
#define PAGE_SIZE (1UL << PAGE_SHIFT)
即一个页大小。
PTRS_PER_PGD:页全局目录中表项数
PTRS_PER_PUD:页上层目录中表项数
PTRS_PER_PMD:页中间目录中表项数
PTRS_PER_PTE:页表项中表项数
对于只支持两级页表的体系:
PTRS_PER_PMD=1
PTRS_PER_PUD=1
一个页表项的数据结构,pgd和pte为例:
typedef struct {
unsigned long pgd;
} pgd_t;
typedef struct {
unsigned long pte;
} pte_t;
将 pte_t 转换为 unsigned long:
#define pgd_val(x) ((x).pgd)
#define pte_val(x) ((x).pte)
将 unsigned long 转换为 pte_t:
#define __pgd(x) ((pgd_t) { (x) } )
#define __pte(x) ((pte_t) { (x) } )
计算一个给定虚拟地址的PMD index:
pmd_index(unsigned long addr)
计算一个给定地址虚拟地址在页上层目录PUD指向的页中间目录(PMD)的索引值
pmd = pmd_offset(pud_t *pud, unsigned long addr)
将一个给定地址返回按页对齐后地址:
addr = PAGE_ALIGN(addr);
如PAGE_ALIGN(6000) ,则返回8192。
3.3.2 特定于PTE的信息
一个PTE页表项条目:
除了指示物理内存页的位置,还利用多余bit实现如下信息。
ARM为例:
#define L_PTE_VALID (_AT(pteval_t, 1)
该页表条目是有效的。
#define L_PTE_PRESENT (_AT(pteval_t, 1)
虚拟页已加载到物理内存中。
#define L_PTE_FILE (_AT(pteval_t, 1)
该页表项对应的物理页为磁盘文件映射页。
#define L_PTE_DIRTY (_AT(pteval_t, 1)
该页表项指向的物理页有脏数据,待写回磁盘。
#define L_PTE_RDONLY (_AT(pteval_t, 1)
该页表项指向的物理页是只读的,如程序的代码段页面。
#define L_PTE_USER (_AT(pteval_t, 1)
该页面只可被用户进程访问。不能被内核访问。
#define L_PTE_XN (_AT(pteval_t, 1)
即eXecute Never,该页面不允许执行指令。
如堆栈,数据段页面。
#define L_PTE_SHARED (_AT(pteval_t, 1)
该页表项指向的物理页可被多个进程共享。
#define L_PTE_NONE (_AT(pteval_t, 1)
该页表项无效或不存在。
注意:之前说过struct page表示一个物理页,其内部 flag 成员表示的是物理页的状态信息。
flag成员与PTE项的状态对比:
二者区别:
PTE关注虚拟内存层面的标志信息。给MMU和虚拟内存管理系统使用的。
而struct page中flags关注物理内存层面。给内存管理子系统使用。
二者关联:
二者标志信息可能需要互相同步,赋值。
MMU进行虚拟地址转换物理地址时,将读取PTE中标志信息。
ARM中实现有:
pte_t pte_modify(pte_t pte, pgprot_t newprot)
修改pte页表项对应bit,完成对应标志更改。
#define pte_present(pte) (pte_val(pte) & L_PTE_PRESENT)
检查一个页表项指向的物理页是否存在且有效。
#define pte_dirty(pte) (pte_val(pte) & L_PTE_DIRTY)
检查一个页表项指向的物理页是否有脏数据。
#define pte_write(pte) (!(pte_val(pte) & L_PTE_RDONLY))
检查该页表项指向的物理页是否可写。
应用层malloc时,内核如何分配内存?
1. 内核在虚拟地址空间分配一块虚拟内存。
2. 创建页表项PTE,标记页表项为空或者无效,并设置页表项为保护状态。
3. 当应用程序首次写虚拟内存时,因为页表项为保护状态,所以会触发page fault。
4. page fault中断处理中,通过页置换或页换出等算法找到可用物理页,并将该物理页映射到引起错误的虚拟地址。
5. 最后更新页表项,包括设置PTE_PRESENT,也可能设置PTE_DIRTY(页已写入)。
3.3.3 页表项的操作
pte_t pte = mk_pte(struct page *page, pgprot_t pgprot)
定义一个页表项PTE,用于指向page物理页,并设置相应属性pgprot。
pud_t *pud_alloc(struct mm_struct *mm, pgd_t *pgd, unsigned long address)
在PGD中分配一个PUD条目。
pmd_t *pmd_alloc(struct mm_struct *mm, pud_t *pud, unsigned long address)
在PUD中分配一个PMD条目。
int __pte_alloc(struct mm_struct *mm, struct vm_area_struct *vma,
pmd_t *pmd, unsigned long address)
在给定虚拟内存区域(VMA)的虚拟地址address分配一个新的页表项(PTE)。
常用于创建新的VMA或扩展现有VMA。
void pte_free(struct mm_struct *mm, pgtable_t pte)
释放页表条目PTE。