MIT 6.s081 lab3.1–printpgtbl
No.1 写在前面的话
从这个实验开始,难度上升,一开始就得知大名鼎鼎的lab3最难,果不其然,我踩了很多坑,并且因为我的能力有限,只做了前两个实验(第三个完全看不懂orz),话虽如此,不要畏难,开始前进!
No.2 初识页表
提示中的添加函数和语句不再赘述,我们在这里直接先分析freewalk,首先参看注释,上面写道——
// Recursively free page-table pages.
//递归释放页表页面
// All leaf mappings must already have been removed.
//所有叶映射必须已被删除
接下来查看具体代码:
void
freewalk(pagetable_t pagetable)
{
// there are 2^9 = 512 PTEs in a page table.
for(int i = 0; i < 512; i++){
pte_t pte = pagetable[i];
if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
// this PTE points to a lower-level page table.
//最后一层页表中页表项rwx起码有一位置一
//注意运算符优先级!【==后才&&】
//若两者为真,说明不在最后一层
uint64 child = PTE2PA(pte);
//child为其页表条目提取的物理地址
freewalk((pagetable_t)child);
pagetable[i] = 0;
//递归释放
} else if(pte & PTE_V){
//如果满足页表条目有效,为最后页表
//叶映射没有被删除
panic("freewalk: leaf");
}
}
kfree((void*)pagetable);
}
大致意思明白了,判断有效位和rwx位,查看是否是最后一层,不是则递归,如果是则释放。但是因为并不清楚页表条目的物理地址是怎样的,好奇的添加printf打印一下:
if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
uint64 child = PTE2PA(pte);
printf("child is %p\n", child);
结果如图:
通过用gdb打断点和观测,观察页表的分层发现:67000是二级页表,66000是三级,再往后继续执行,似乎页表存放页不是连续的?两页有效的pte之中会存在很多的空条目,真是神奇。
No.3 实现vmprint
那么现在我们已经可以利用for循环递归遍历页表了,初版如下:
void
vmprint(pagetable_t pagetable)
{
for(int i = 0; i < 512; i++){
pte_t pte = pagetable[i];
if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){
uint64 child = PTE2PA(pte);
vmprint((pagetable_t)child);
pagetable[i] = 0;
}
}
kfree((void*)pagetable);
}
只差另一个问题:打印[…]
不得不说,这个问题困扰了我很长时间。一开始,我认为在递归前打印[…]就好,于是在pte_t pte = pagetable[i];
后写了一段printf,结果事与愿违,无限的[…]填满了qemu,让我不知如何是好;于是又返回题目要求,仔细阅读这里:【每个PTE行都有一些“..
”的缩进表明它在树中的深度。】深度……也就是说,每进入一层多打一次[…],然后深度加一继续,自然而然想到for循环,设置变量记录深度;可是题目所给的vmprint只提供页表不负责记录深度,递归时怎么办呢?
那么,就需要让vmprint成为一个过渡函数,根据题目要求,代码如下:
void
vmprint(pagetable_t pagetable){
printf("page table %p\n", pagetable);
Pritbl(pagetable, 1);//设置深度为1,0也可以
}
一切准备就绪,因为题目要求不要打印无效的PTE,因此,分为两个部分,有效和无效,有效则里面进行递归,无效跳过,最终代码如下:
void
Pritbl(pagetable_t pagetable, uint64 level){
for(int i = 0; i < 512; ++i){
pte_t pte = pagetable[i];
//拼接[..]
if(pte & PTE_V)
{
for (int j = 0; j < level; ++j)
{
if(j == 0) printf("..");
else printf(" ..");
}
uint64 child = PTE2PA(pte);
printf("%d: pte %p pa %p\n", i, pte, child);
//如果页表还有深度,递归
if((pte & (PTE_R|PTE_W|PTE_X)) == 0){
Pritbl((pagetable_t)child, level+1);
//为什么用++level,level++结果完全不同,只有level+1可以?
}
//上述步骤都是在页表【存在】的条件下进行
}
if((pte & PTE_V) == 0) continue;
//题目提示不要打印无效pte,无效的pte会无限打印
}
}
void
vmprint(pagetable_t pagetable){
printf("page table %p\n", pagetable);
Pritbl(pagetable, 1);
}
记得将两个函数添加到defs.h。
完美解决。【用时多半天】
No.4 补充知识
因为lecture7是页表实验的解答,在这里我将与vmprint相关的内容放入,以便了解更加深刻,首先是进程页表结构和打印结果:
Q1:trampoline和trapframe位于顶部,为何在在这里位于第一个根页面?且索引为255?
A:不需要更多内存,在此处已经足够。
Q2:数据与文本为什么合并在一页?
A:为了xv6尽可能简单,实际上现在的操作系统会将它们隔离开来。