Lab 2 - Exercise4~5
逻辑地址,线性地址,物理地址
在Lab2 Exercise 2 中,我们提到了这三种地址的关系:
逻辑地址由段选择子和段内偏移组成
线性地址(虚拟地址)是逻辑地址进行段翻译后得到的
物理地址是逻辑地址经过段翻译和页翻译后得到的
要注意到逻辑地址不是每个mmu都有的,只是因为x86为了兼容性才有的。
我觉得虚拟地址的出现最大的作用是解决了内存碎片的问题,如下图:
(图来自https://www.zhihu.com/question/290504400)

提到虚拟地址必然会想到内存分页,内存分页则解决了内存交换的效率低的问题(不用整段代码从硬盘中读出读入)
为了使用虚拟地址空间,我们要使用代码管理虚拟地址和物理页之间的映射关系,也就是Exercise 4 当中要我们实现的几个函数。
Exercise 4
继续实现 kern/pmap.c中的几个函数:
- pgdir_walk()
- boot_map_region()
- page_lookup()
- page_remove()
- page_insert()
从mem_init()调用的Check_page()进行测试。在继续之前,您应该确保它报告成功。
在Exercise 1 中我们提到 mem_init() 一直执行到 page_init() 完成了内存层面初始化页面以及页面的基本操作(包括删除和分配)
然后执行
check_page_free_list(1); //检查page_free_list上的页面是否合理。
check_page_alloc(); //检查物理页面分配器(page_alloc()、page_free()和page_init())。
check_page(); //check page_insert, page_remove, &c
check_kern_pgdir(); //检查初始页目录是否已正确设置
lcr3(PADDR(kern_pgdir)); //将目前正在使用的页目录(在entry.S中设置的)切换为kern_pgdir(在mem_init中设置的)
check_page_free_list(0); //检查page_free_list上的页面是否合理。
//在entry.S中我们已经设置了cr0以开启分页,这里是配置我们关心的其他标志
cr0 = rcr0();
cr0 |= CR0_PE|CR0_PG|CR0_AM|CR0_WP|CR0_NE|CR0_MP;
cr0 &= ~(CR0_TS|CR0_EM);
lcr0(cr0);
check_page_installed_pgdir(); //使用已安装的kern_pgdir检查page_insert, page_remove, &c
check_page_free_list(1),check_page_alloc();都是在检查Exercise 1 中的函数实现是否正确。
check_page()开始检查- pgdir_walk(),boot_map_region(),page_lookup(),page_remove(),page_insert()这几个函数,也就是本练习要求我们实现的函数,我们可以通过查看check_page如何调用这些函数,从而理解这几个函数的作用。
接下来我们进入check_page()
//一些变量的定义,可忽略
struct PageInfo *pp, *pp0, *pp1, *pp2;
struct PageInfo *fl;
pte_t *ptep的地址, *ptep1;
void *va;
int i;
extern pde_t entry_pgdir[];
// 首先调用page_alloc()分配了三个物理页并返回页的虚拟地址给pp0,pp1,pp2
//assert的作用是现计算expression表达式,如果其为假(0),就打引一条错误信息,并终止执行.
pp0 = pp1 = pp2 = 0;
assert((pp0 = page_alloc(0)));
assert((pp1 = page_alloc(0)));
assert((pp2 = page_alloc(0)));
//意义不明,理解不能
assert(pp0);
assert(pp1 && pp1 != pp0);
assert(pp2 && pp2 != pp1 && pp2 != pp0);
//暂时偷走其余的空闲页面
fl = page_free_list;
page_free_list = 0;
//因为目前空闲页链表page_free_list被设置为0,也就是没有空闲页,因此调用page_alloc分配页将失败
assert(!page_alloc(0));
// there is no page allocated at address 0
//首次出现了要实现的函数之一page_lookup(),并且传入了参数页目录,地址0x0,和一个无符号32位int类型指针ptep的地址
//根据虚拟地址0x0寻找对应的页,结果应该是找不到,所以返回NULL
assert(page_lookup(kern_pgdir, (void *) 0x0, &ptep) == NULL);
page_lookup的注释是 Return the page mapped at virtual address 'va',也就是他的作用是根据虚拟地址(查找)返回和va具有映射关系的page。
继续执行
// there is no free memory, so we can't allocate a page table
//这句将0x0和pp1建立映射为什么会失败,不明白。
assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0);
page_insert传入kern_pgdir页目录,pp1(一页),0x0,PTE_W(0x002,全局定义)参数
根据page_insert的注释
函数目的是把建立虚拟地址和物理页的映射关系。
继续执行
// free pp0 and try again: pp0 should be used for page table
//释放pp0再尝试一遍
//建立映射成功,页目录的第一项是pp0(为什么不是pp1)
page_free(pp0);
assert(page_insert(kern_pgdir, pp1, 0x0, PTE_W) == 0);
//页目录入口是pp0
assert(PTE_ADDR(kern_pgdir[0]) == page2pa(pp0));
//根据虚拟地址0x0和页目录寻找某一页的物理地址————对应pp1的物理地址
assert(check_va2pa(kern_pgdir, 0x0) == page2pa(pp1));
assert(pp1->pp_ref == 1);
assert(pp0->pp_ref == 1);
// should be able to map pp2 at PGSIZE because pp0 is already allocated for page table
//应该可以成功建立pp2和虚拟地址PGSIZE(4096)之间的映射,因为pp0已经被分配做页表
assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0);
assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
assert(pp2->pp_ref == 1);
// should be no free memory
assert(!page_alloc(0));
// should be able to map pp2 at PGSIZE because it's already there
//可以把pp2和PGSIZE的映射关系拿掉,再重新建立。
assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W) == 0);
assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
assert(pp2->pp_ref == 1);
// pp2 should NOT be on the free list
// 如果页的引用计数代码没有处理好,这里就会报错
assert(!page_alloc(0));
继续执行
// check that pgdir_walk returns a pointer to the pte
ptep = (pte_t *) KADDR(PTE_ADDR(kern_pgdir[PDX(PGSIZE)]));
assert(pgdir_walk(kern_pgdir, (void*)PGSIZE, 0) == ptep+PTX(PGSIZE));
// should be able to change permissions too.
assert(page_insert(kern_pgdir, pp2, (void*) PGSIZE, PTE_W|PTE_U) == 0);
assert(check_va2pa(kern_pgdir, PGSIZE) == page2pa(pp2));
assert(pp2->pp_ref == 1);
assert(*pgdir_walk(kern_pgdir, (void*) PGSIZE, 0) & PTE_U);
assert(kern_pgdir[0] & PTE_U);
pgdir_walk()函数首次出现,他传入了三个参数:指向页目录的指针,一个虚拟地址PGSIZE,还有一个标志位0
根据注释pgdir_walk()根据虚拟地址找到对应的pte(page table entry),有就返回,没有如果标识位create为1就创建,否则NULL。
page_lookup根据虚拟地址返回页的地址就是借助pgdir_walk()先找到页所在的页表入口地址。
虽然还没看到page_remove,但是我懒。
根据注释和别人的文章总结一下:
-
pgdir_walk() 根据虚拟地址找到对应的pte(page table entry)
-
boot_map_region() 这个函数将va 开始的size大小虚拟内存和pa 开始的size大小的物理内存做映射,写入页表项中。查表动作可以借助刚刚完成的pgdir_walk函数
-
page_lookup() 根据虚拟地址(查找)返回和va具有映射关系的page。
-
page_remove() pg_remove删除虚拟内存va与对应的物理页的映射关系。简单来看只需要调用pg_lookup,把找到的pte(保存在第三个参数中)处的内容清0就可以啦,但是注释提醒到还要考虑到问题:
(1)物理页的ref要减一;
(2)如果该物理页ref变为0,释放它;
(3)还要删除TLB,TLB—translation lookaside buffer, 基本就相当于页表中的缓存,用来加快查表速度的。现在,map不存在了,如果这个map被缓存了,自然也要删掉。对于(2)(3)都不用担心,给出的函数已经帮我们封装好了 -
page_insert() 把虚拟地址的情况映射到物理页面信息pp中。
要实现上面几个函数,还要借助几个关于地址转换的函数,要理清他们的作用和关系:

接下来我们就可实现这几个函数了,具体实现看代码实现和测试部分
Exercise 5
在mem_init()里,填写调用check_page()之后缺失的代码。并且通过check_kern_pgdir()和check_page_installed_pgdir()检查。
实际上就是利用Exercise 4中的函数来实现指定物理地址和虚拟地址之间的映射。
做法:在在mem_init()里,check_page()之后补充三个boot_map_region函数
具体实现看代码实现和测试部分
代码实现和测试结果
代码实现:
pmap.c
用以上两个其中一个pmap.c 替换掉 /lab/kern/pmap.c
然后在/lab 目录下运行 ./grade-lab2 或者 make grade
可以得到以下结果

总结:
对于lab2,Exercise 1 在物理地址上内核代码的后面的kern_pgdir(页目录)的后面去取一片空间用于储存pages,用pages+0代表04k的物理空间,pages+1代表4k8k的物理空间,
pages+n代表n4k~n4k+4k的物理空间
pages(用于管理物理地址)——我们实现的几个函数(用于建立pages和kern_pgdir之间的映射关系)——kern_pgdir(页目录,通过这个可以将物理地址转化为虚拟地址)
在Exercise 4 中,为了使用虚拟地址空间,我们要使用代码管理虚拟地址和物理页之间的映射关系,也就是使用Exercise 4 当中要我们实现的几个函数,去管理kern_pgdir和 pages 之间的关系。
Exercise5中,我们使用 exercise4 中的那几个函数来实现具体物理地址和具体虚拟地址之间的映射。
(对于Exercise 4 中的函数,我还只是了解大意,并未理解代码。在做lab 3的时候,应该要回头来看Exercise 4 中的几个函数的代码。)

浙公网安备 33010602011771号