通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的 get_pte 函数是设置页表项环节中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。本练习需要补全 get_pte 函数 in kern/mm/pmm.c,实现其功能。请仔细查看和理解 get_pte 函数中的注释。 请在实验报告中简要说明你的设计实现过程。请回答如下问题:
- 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中每个组成部分的含义以及对 ucore 而言的潜在用处。
- 如果 ucore 执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
get_pte函数作用是返回pte页表项虚地址。
提供三个参数:
- pgdir: PDT的内核虚拟基地址;
- la: 需要映射的线性地址;
- create: 一个逻辑值,用来决定是否为PT分配一个页面;
他分两个情况:
- 一个是页表目录存储的值有构建映射,直接转换地址就行了;
- 一个是页表目录存储的值没有构建映射,我们需要创建一个page,给他们构建映射(赋值);
这里我们需要知道一个常识,我以前一直不懂:
- 页表目录和页表中,20位存储的都是基地址,是 物理地址 ;如果要访问 表 ,还需要经行物理-虚拟转换;
那我们可以看看具体操作了,如果索引正常,那么直接访问就行了:
一个指针指向页表目录中的索引uintptr *pte = &pgdir[PDX(la)]
返回操作就是一步一步来了:
return &((pte_t *)KADDR(PDE_ADDR(*pdx)))[PTX(la)];
& | ((uintptr_t * ) | KADDR | (PDE_ADDR | (*pte))) | [PTX(la)] |
---|---|---|---|---|---|
读取PD中的值 | |||||
读取值中的前20位 |
|||||
转换为PT虚地址 | |||||
取地址 | 转换为指针 指向PT的基地址 |
线性地址中间10位 PT序号 |
最麻烦的就是没有对应索引了,对应检查措施:
if (!(*pdx & PTE_P)) {
struct Page *p;
if (!create || (p = alloc_page()) == NULL) {
return NULL;
}
!(*pdx & PTE_P)
与操作,判断末尾是否为1;
!create || (p = alloc_page()) == NULL
看看是否需要创建、创建page是否成功;
我觉得后面才是难点。
set_page_ref(p, 1);
uintptr_t pa = page2pa(p);
memset(KADDR(pa), 0, PGSIZE);
*pdx = pa | PTE_P | PTE_W | PTE_U;
看看组织结构图吧,page其实是在LA中分配的。
上述第二行,获得了page管理的物理地址:有一说一我不知道他有啥用。
实际上只是 Page 这个结构体所在的地址而已 故而需要 通过使用 page2pa() 将 Page 这个结构体的地址 转换成真正的物理页地址的线性地址 然后需要注意的是 无论是 * 或memset 都是对虚拟地址进行操作的所以需要将 真正的物理页地址再转换成 内核虚拟地址。
参考以下这个:
https://www.cnblogs.com/kongj/p/12850111.html#%E7%BB%83%E4%B9%A02%EF%BC%9A%E5%AE%9E%E7%8E%B0%E5%AF%BB%E6%89%BE%E8%99%9A%E6%8B%9F%E5%9C%B0%E5%9D%80%E5%AF%B9%E5%BA%94%E7%9A%84%E9%A1%B5%E8%A1%A8%E9%A1%B9
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
assert(pgdir != NULL);
struct Page *struct_page_vp; // virtual address of struct page
uint32_t pdx = PDX(la), ptx = PTX(la); // index of PDE, PTE
pde_t *pdep, *ptep;
pte_t *page_pa; // physical address of page
pdep = pgdir + pdx;
ptep = (pte_t *)KADDR(PDE_ADDR(*pdep)) + ptx;
// if PDE exists
if (test_bit(0, pdep)) {
return ptep;
}
/* if PDE not exsits, allocate one page for PT and create corresponding PDE */
if ((!test_bit(0, pdep)) && create) {
struct_page_vp = alloc_page(); // allocate page for PT
assert(struct_page_vp != NULL); // allocate successfully
set_page_ref(struct_page_vp, 1); // set reference count
page_pa = (pte_t *)page2pa(struct_page_vp); // convert virtual address to physical address
ptep = KADDR(page_pa + ptx); // virtual address of PTE
*pdep = (PADDR(ptep)) | PTE_P | PTE_U | PTE_W; // set PDE
memset(ptep, 0, PGSIZE); // clear PTE content
return ptep;
}
return NULL;
从这里可以看出来,page2pa,其实可以获得这个page的物理地址,而不是上述的page管理的地址。这样理解就好多了。
页目录项:
- bit 0(P): resent 位,若该位为 1 ,则 PDE 存在,否则不存在。
- bit 1(R/W): read/write 位,若该位为 0 ,则只读,否则可写。
- bit 2(U/S): user/supervisor位。
- bit 3(PWT): page-level write-through,若该位为1则开启页层次的写回机制。
- bit 4(PCD): page-level cache disable,若该位为1,则禁止页层次的缓存。
- bit 5(A): accessed 位,若该位为1,表示这项曾在地址翻译的过程中被访问。
- bit 6: 该位忽略。
- bit 7(PS): 这个位用来确定 32 位分页的页大小,当该位为 1 且 CR4 的 PSE 位为 1 时,页大小为4M,否则为4K。
- bit 11:8: 这几位忽略。
- bit 32:12: 页表的PPN(页对齐的物理地址)。
页表项:
页表项除了第 7 , 8 位与 PDE 不同,其余位作用均相同。
- bit 7(PAT): 如果支持 PAT 分页,间接决定这项访问的 4 K 页的内存类型;如果不支持,这位保留(必须为 0 )。
- bit 8(G): global 位。当 CR4 的 PGE 位为 1 时,若该位为 1 ,翻译是全局的;否则,忽略该位。
其中被忽略的位可以被操作系统用于实现各种功能;和权限相关的位可以用来增强ucore的内存保护机制;access 位可以用来实现内存页交换算法。