目录
3.3 页表
3.3.1 数据结构
3.3.2 特定于PTE的信息
3.3.3 页表项的创建和操作
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_SIZE(1UL << PAGE_SHIFT)
一个页大小
其中#define PAGE_SHIFT 12
则一个页大小2的12次方,即4K。
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)
计算给定地址 address在页上层目录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)
上面两个含义一样,该PTE是否指向一个有效的物理页。
#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成员也表示物理状态。
二者区别:
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