LAB2_Part 2 Virtual Memory
前言
记录一下自己的学习过程
实验内容翻译:
https://gitee.com/cherrydance/mit6.828
该翻译仅供参考
练习2
如果您还没有这样做,请查阅Intel 80386参考手册的第5章和第6章。仔细阅读关于页转换和基于页的保护的部分(5.2和6.4)。我们建议您也浏览一下关于分段的部分;尽管JOS使用分页硬件进行虚拟内存和保护,但在x86上无法禁用段转换和基于段的保护,因此您需要对其有基本的了解。
自行学习理解。
练习3
在设置虚拟内存时,尽管GDB只能通过虚拟地址访问QEMU的内存,但能够检查物理内存通常非常有用。请查看实验工具指南中的QEMU监视器命令,特别是xp命令,它允许您检查物理内存。要访问QEMU监视器,在终端中按下Ctrl-a c(相同的绑定将返回到串行控制台)。
使用QEMU监视器中的xp命令和GDB中的x命令来检查相应的物理地址和虚拟地址的内存,并确保您看到相同的数据。
我们打过补丁的QEMU版本提供了info pg命令,这也可能很有用:它显示当前页表的紧凑但详细的表示,包括所有映射的内存范围、权限和标志。标准的QEMU还提供了info mem命令,显示虚拟地址范围的映射概况以及相应的权限。
先运行make qemu-gdb和make gdb,在gdb窗口按c运行再ctrl + c停止,使用x命令查看0xf0100000处的内容,之后按c继续运行。换到qemu窗口使用ctrl+a c。使用xp查看地址0x00100000的内容。如下:
我们可以看到两处的内容一致,因此可以确定虚拟地址0xf0100000映射到了物理地址0x00100000。
问题1:假设以下的 JOS 内核代码是正确的,那么变量 x 应该具有什么类型,是 uintptr_t 还是 physaddr_t?
mystery_t x;
char* value = return_a_pointer();
*value = 10;
x = (mystery_t) value;
变量x是uintptr_t类型的。因为第三行的代码 *value = 10;
对value值进行解引用。而解引用对物理地址是没有意义的,同样最后一行将value赋值给x,x和value应当是一种类型。
练习4
在文件 kern/pmap.c 中,您需要实现以下函数的代码:
pgdir_walk()
boot_map_region()
page_lookup()
page_remove()
page_insert()
check_page() 函数是从 mem_init() 中调用的,用于测试您的页表管理例程。在继续之前,您应该确保它报告成功。
与part1一样,根据注释写代码。先看pgdir_walk函数。注释太长就不列出来了,大致就是给你一个指向页目录表的指针,返回va对应的页表项的指针。在mmu.h中有地址的解析方法。
PDX得到页表项在页目录中的位置,PTX得到页在页表中的位置。
页表和页目录的各种flags。
实现代码如下,思路在注释中描述:
pde_t *pgdir_entry = pgdir + PDX(va);
//得到页表页在页目录中的位置
if(!(*pgdir_entry & PTE_P)){//该页不存在
if(!create){//是否创建
return NULL;
}else{
struct PageInfo *temp = page_alloc(1);//创建页
if(!temp){
return NULL;
}
*pgdir_entry = (page2pa(temp) | PTE_P | PTE_U | PTE_W);
temp->pp_ref++;
}
}
return (pte_t *)KADDR((PTE_ADDR(*pgdir_entry))) + PTX(va);
boot_map_region函数
其作用是将[va,va+size)映射到[pa,pa+size),代码如下:
pte_t *pgtable_entry;
for(;va < va + size; va += PGSIZE, pa += PGSIZE){
pgtable_entry = pgdir_walk(pgdir, (void *)va, 1);
//得到对应的页表项地址
*pgtable_entry = (pa | perm | PTE_P);
//设置页表项的值,这里要知道pa的最后12位是为0的,
//所以|运算不会影响这个物理地址的使用
}
page_lookup函数
作用是返回虚拟地址va对应的页面。
pte_t *pgtable_entry = pgdir_walk(pgdir, va, 0);//获取页表项地址
if(!pgtable_entry || !(*pgtable_entry & PTE_P)){//页目录项或页表项为空
return NULL;
}
if(pte_store){//pte_store不为0则存储页表项
*pte_store = pgtable_entry;
}
//返回va对应的页面
return pa2page(PTE_ADDR(*pgtable_entry));
page_remove函数
作用是取消虚拟地址va映射的物理页。代码如下:
pte_t *pte_store;
struct PageInfo *page = page_lookup(pgdir, va, &pte_store);//获取页面
if(!page){//如果页面不存在则不操作
return;
}
page_decref(page);//该函数会将pp->ref--并判断是否为0,为0则释放该页面
*pte_store = 0;//对应的页表项设置为0
tlb_invalidate(pgdir, va);
page_insert函数
其作用是将物理页面pp映射到虚拟地址va。
pte_t *pgdir_entry = pgdir_walk(pgdir, va, 1);//获取页表项地址
if(!pgdir_entry){//pgdir_walk的create参数为1,此时返回NULL
return -E_NO_MEM;//说明不够分配了
}
pp->pp_ref++;//页面引用计数加1
if(*pgdir_entry & PTE_P){//如果这个页表项已经映射了一个页面直接remove
page_remove(pgdir, va);
tlb_invalidate(pgdir, va);
}
*pgdir_entry = (page2pa(pp) | perm | PTE_P);//设置页表项内容
*(pgdir + PDX(va)) |= perm;//页目录的权限永远大于等于页表项
return 0;
全部函数完成后,make qemu查看结果:
如图,check_page() success。
总结
lab2的part2完结。这里的关键点就在于能不能把页目录,页表,页目录项,页表项搞明白。