MIT 6.s081 lab3.2–kpgtbl per process
No.1 写在前面的话
说实话,一开始看到hard就有些畏难,不过慢慢啃还是解决了;只是因为个人技术不够所以中途参看了些文章的答案(-.-||),有种脏了宝物的感觉,但收获确实很多。【mit的人做这些实验会有我这么费劲吗?】
No.2 实验开始
实际上,阅读一开始的实验概述时我完全没看懂,好在后面有告诉我现在的任务是什么,我将它放在这里:
你的第一项工作是修改内核来让每一个进程在内核中执行时使用它自己的内核页表的副本。修改[struct proc]来为每一个进程维护一个内核页表,修改调度程序使得切换进程时也切换内核页表。对于这个步骤,每个进程的内核页表都应当与现有的的全局内核页表完全一致。
大致明白了,也就是说目标是复制一个进程的页表,将其添加到内核的页表里,如果进程在内核运行,只需运行它的副本就好,切换进程时也要及时替换进程内核页表,似乎挺容易【NO】。
那么根据提示,首先在proc.h中的结构体末尾添加内核页表的字段:
pagetable_t k_pagetable; // Kernel page table
接下来查看kvminit,因为不是原地修改,所以先无脑复制过来。通过阅读代码可以得知,它的作用是直接将设备,trap,以及内核的数据等直接映射到物理地址上,而其中的kvmmap则负责了映射的功能,只不过在这里它只是一个过渡用函数,真正的映射在mappages。
似乎是因为内核页表只有一页,所以给kvmmap传参时只需要传入具体的信息就好,但由于我们需要给进程创建内核页表,并且有很多进程,所以不能这样做,必须要每分配一次内核页表就要传入它的页表给kvmmap,模仿如下:
void
ModifyKvmInit(){
pagetable_t k_pagetable;//注意这里是创建进程内核页表
k_pagetable = (pagetable_t) kalloc();
memset(k_pagetable, 0, PGSIZE);
ModifyKvmMap(k_pagetable, UART0, UART0, PGSIZE, PTE_R | PTE_W);
ModifyKvmMap(k_pagetable, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
ModifyKvmMap(k_pagetable, CLINT, CLINT, 0x10000, PTE_R | PTE_W);
ModifyKvmMap(k_pagetable, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
ModifyKvmMap(k_pagetable, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
ModifyKvmMap(k_pagetable, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
ModifyKvmMap(k_pagetable, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
}
void
ModifyKvmMap(pagetable_t k_pagetable, uint64 va, uint64 pa, uint64 sz, int perm)
{
//新页表的映射
if(mappages(k_pagetable, va, sz, pa, perm) != 0)
panic("modifykvmmap");
}
然后要求在allocproc中调用函数,参看一下:
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
模仿可得:
//调用创建的进程内核页表
p->k_pagetable = ModifyKvmInit(p);
if(p->k_pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
但是要注意的是,进程调用初始化页表的函数完成后才会赋值,如果没有返回一个具体的值给p->k_pagetable,是会报错的;而我们在这里模仿kvminit用void类型,所以需要改正,返回一个页表供进程使用:
pagetable_t ModifyKvmInit()
,以及末尾 return k_pagetable;
现在到了内核栈的映射,提示说要部分或全部的迁移【剪切粘贴】到allocproc中,简单了解了一下功能,然后无脑copy到刚刚补充的调用内核页表后(以防万一我只是将这段代码注释掉了);记得替换自己的函数:
ModifyKvmMap(p->k_pagetable, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
至此,三分之一的任务完成,但我们现在似乎也只是做了简单的模仿和copy工作。
继续根据提示,在scheduler()中粘贴w_satp():
c->proc = p;
w_satp(MAKE_SATP(p->k_pagetable));
sfence_vma();
因为之后可能会进行swtch上下文切换导致进程改变,也有kvminithart()
,在这里用的便是真正的内核页表,所以也不需要多做修改。到这里再参看提示,似乎快要完成了,不禁松了口气,但没想到重头戏恰恰就在这里。
No.3 释放页表
先看看freeproc(),至少可以先模仿出这些:
if(p->pagetable)
proc_freepagetable(p->pagetable, p->sz);
//释放进程内核页表
if(p->k_pagetable)
???;
p->pagetable = 0;
p->k_pagetable = 0;
我们需要一种方法释放内核页表,提示说不必释放叶子物理内存页面,以及调试页表的vmprint,很容易的就联想到了vmprint中的freewalk,以及最后的kfree;但是先不急,看一看上面进程页表的释放,或许能有些思路。
类似页表映射,真正利用的函数是uvmunmap,它从TRAMPOLINE开始将一页进程映射进行释放,判断内存对齐,页表条目是否存在,以及判断是否为叶子节点,似乎都与freewalk大同小异。我唯一不理解的就是守护页以下似乎不需要释放,没有体现出来,不过仔细想来,也许是为了便于恢复进程吧。
再明确一下进程普通页表的结构,以及我们所实现的进程内核页表的结构:
那么现在可以根据vmprint,模仿出一个释放页表的函数了:
void
proc_freeKpagetable(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);
proc_freeKpagetable((pagetable_t)child);
pagetable[i] = 0;
}
}
kfree((void*)pagetable);
}
【记得将所有新加入的函数都写到defs.h里!!】
补全上面 proc_freeKpagetable(p->k_pagetable);
到此为止,已经全部完成,提示也没有更多的说明了,欢天喜地qemu测试,看着一个个ok甚是开心,但是它突然报错了: test sbrkfail: panic: modifykvmmap
,以及上面的usertrap(),定睛一看,这不是我的函数吗?咋回事啊?
回头看看我的kvmmap,以及判断条件:
if(mappages(k_pagetable, va, sz, pa, perm) != 0)
panic("modifykvmmap");
}
//mappages:创建页表条目从va到pa
似乎没问题?再往后查阅,也没有毛病,此时我一筹莫展,参看他人的文章才知道,释放页表的第一步是释放内核栈。这是我最迷惑的一点,kfree并没有释放内核栈的代码,而在进程页表中也没有这一步骤,所以内核要释放内核栈时是怎么释放的? 完全不会释放吗?
在这里,我毫无头绪,不知如何解决,因此借助了他人的代码(freeproc):
if(p->trapframe)
kfree((void*)p->trapframe);
p->trapframe = 0;
//删除内核栈【他人文章所得】
if(p->kstack){
pte_t* pte = walk(p->k_pagetable, p->kstack, 0);
if(pte == 0)
panic("feeproc: kstack");
//删除页表项对应物理地址
kfree((void*)PTE2PA(*pte));
}
p->kstack = 0;
//defs.h
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc);
如果还有报错,记得在vm.c头部添加:
//进程内核映射的头文件加入
//和加入顺序也有关系,spinlock在后就不行
#include "spinlock.h"
#include "proc.h"
至此,最后的释放完成,这一步感觉让实验脏了,但这个疑惑以我现在的知识无法解决,便暂且搁置了。【用时两天】