MIT6.s081 lab pagetable
lab pagetable
1、print a pagetable
void vmprint_helper(pagetable_t pagetable, int depth) {
static char* indent[] = {
"",
"..",
".. ..",
".. .. .."
};
if (depth <= 0 || depth >= 4) {
panic("vmprint_helper: depth not in {1, 2, 3}");
}
for (int i = 0; i < 512; i++) {
pte_t pte = pagetable[i];
if (pte & PTE_V) {
printf("%s%d: pte %p pa %p\n", indent[depth], i, pte, PTE2PA(pte));
if ((pte & (PTE_R|PTE_W|PTE_X)) == 0) {
uint64 child = PTE2PA(pte);
vmprint_helper((pagetable_t)child, depth+1);
}
}
}
}
void vmprint(pagetable_t pagetable) {
printf("page table %p\n", pagetable);
vmprint_helper(pagetable, 1);
}
2、A kernel page table per process
0.记得把所有新添加的函数的定义写到defs.h中
1.在proc.h的struct proc中添加kpagetable
2.模仿kvminit写一个创建每个进程的内核页表的函数ukvminit
// vm.c
// 为每个进程创建一个内核页表
pagetable_t
ukvminit()
{
pagetable_t kpagetable = (pagetable_t) kalloc();
if(kpagetable == 0)
return kpagetable;
memset(kpagetable, 0, PGSIZE);
// uart registers
ukvmmap(kpagetable, UART0, UART0, PGSIZE, PTE_R | PTE_W);
// virtio mmio disk interface
ukvmmap(kpagetable, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W);
// CLINT
ukvmmap(kpagetable, CLINT, CLINT, 0x10000, PTE_R | PTE_W);
// PLIC
ukvmmap(kpagetable, PLIC, PLIC, 0x400000, PTE_R | PTE_W);
// map kernel text executable and read-only.
ukvmmap(kpagetable, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X);
// map kernel data and the physical RAM we'll make use of.
ukvmmap(kpagetable, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W);
// map the trampoline for trap entry/exit to
// the highest virtual address in the kernel.
ukvmmap(kpagetable, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X);
return kpagetable;
}
// 为每个进程的内核页表添加mapping
void
ukvmmap(pagetable_t kpagetable, uint64 va, uint64 pa, uint64 sz, int perm)
{
if(mappages(kpagetable, va, sz, pa, perm) != 0)
panic("ukvmmap");
}
3.修改allocproc函数,并将内核栈mapping到每个进程的内核页表中
// proc.h
static struct proc*
allocproc(void)
{
...
// An empty user page table.
p->pagetable = proc_pagetable(p);
if(p->pagetable == 0){
freeproc(p);
release(&p->lock);
return 0;
}
// 分配一个内核页表
p->kpagetable = ukvminit();
if (p->kpagetable == 0) {
freeproc(p);
release(&p->lock);
return 0;
}
// 将每个进程的kernel stack添加mapping到内核页表中
// 此处的pa无需分配内存,原因是在全局内核页表映射内核栈时已经分配了,所以物理地址可以通过全局内核页表直接计算
uint64 va = KSTACK((int)(p - proc));
pte_t pa = kvmpa(va);
memset((void *)pa, 0, PGSIZE);
ukvmmap(p->kpagetable, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
p->kstack = va;
...
}
4.修改scheduler代码
//proc.c
void
scheduler(void)
{
...
// Switch to chosen process. It is the process's job
// to release its lock and then reacquire it
// before jumping back to us.
p->state = RUNNING;
c->proc = p;
// 切换到要运行的进程的内核页表并刷新TLB
w_satp(MAKE_SATP(p->kpagetable));
sfence_vma();
// 将进程cpu的上下文切换成当前进程的
swtch(&c->context, &p->context);
// 切换回全局的内核页表
kvminithart();
...
}
5.释放内核页表
对比释放进程的用户页表可以发现,用户页表在释放时需要清理对应的物理内存,而释放内核页表时不需要
// proc.c
static void
freeproc(struct proc *p)
{
...
p->state = UNUSED;
if (p->kstack) {
p->kstack = 0;
}
if (p->kpagetable) {
freeprockvm(p);
p->kpagetable = 0;
}
}
//vm.c
// 清除进程p的内核页表的mapping
void
freeprockvm(struct proc* p)
{
pagetable_t kpagetable = p->kpagetable;
ukvmunmap(kpagetable, p->kstack, PGSIZE/PGSIZE);
ukvmunmap(kpagetable, TRAMPOLINE, PGSIZE/PGSIZE);
ukvmunmap(kpagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE);
ukvmunmap(kpagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE);
ukvmunmap(kpagetable, PLIC, 0x400000/PGSIZE);
ukvmunmap(kpagetable, CLINT, 0x10000/PGSIZE);
ukvmunmap(kpagetable, VIRTIO0, PGSIZE/PGSIZE);
ukvmunmap(kpagetable, UART0, PGSIZE/PGSIZE);
ufreewalk(kpagetable);
}
// 给每个进程的内核页表解除mapping
void
ukvmunmap(pagetable_t pagetable, uint64 va, uint64 npages)
{
uint64 a;
pte_t *pte;
if((va % PGSIZE) != 0)
panic("ukvmunmap: not aligned");
for(a = va; a < va + npages*PGSIZE; a += PGSIZE){
if((pte = walk(pagetable, a, 0)) == 0)
goto clean;
if((*pte & PTE_V) == 0)
goto clean;
if(PTE_FLAGS(*pte) == PTE_V)
panic("ukvmunmap: not a leaf");
clean:
*pte = 0;
}
}
// 将3级PTES全部置成0并且回收他们的内存
void
ufreewalk(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);
ufreewalk((pagetable_t)child);
pagetable[i] = 0;
}
pagetable[i] = 0;
}
kfree((void*)pagetable);
}
6.注意在vm.c中导入spinlock.h和proc.h(spinlock在前),因为在freeprockvm的形参中定义了struct proc
3、Simplify copyin/copyinstr
1.替换copyin和copyinstr
2.写两个函数用来复制用户页表的mapping到内核页表中
// 复制old页表mapping从虚拟地址start到end至new页表中
// 返回0表示成功,-1表示失败
int
pagecopy(pagetable_t old, pagetable_t new, uint64 start, uint64 end)
{
pte_t *pte;
uint64 pa, i;
uint flags;
start = PGROUNDUP(start);
for (i = start; i < end; i += PGSIZE) {
if ((pte = walk(old, i, 0)) == 0)
panic("pagecopy: pte should exist");
if ((*pte & PTE_V) == 0)
panic("pagecopy: page not present");
pa = PTE2PA(*pte);
flags = PTE_FLAGS(*pte) & (~PTE_U);// 把PTE_U去除
if (umappages(new, i, PGSIZE, pa, flags) != 0) {
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
}
return 0;
}
// 复制mapping
int
umappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
{
uint64 a, last;
pte_t *pte;
a = PGROUNDDOWN(va);
last = PGROUNDDOWN(va + size - 1);
for(;;) {
if ((pte = walk(pagetable, a, 1)) == 0)
return -1;
*pte = PA2PTE(pa) | perm | PTE_V;
if (a == last)
break;
a += PGSIZE;
pa += PGSIZE;
}
return 0;
}
3.修改fork、exec、growproc、userinit
// proc.c
int
fork(void)
{
...
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
// 复制用户内存mapping到子进程的内核页表
if (pagecopy(np->pagetable, np->kpagetable, 0, np->sz) != 0) {
freeproc(np);
release(&np->lock);
return -1;
}
...
}
int
growproc(int n)
{
...
if(n > 0){
if(sz + n >= PLIC || (sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
return -1;
}
// 扩大时更新
if (pagecopy(p->pagetable, p->kpagetable, p->sz, sz) != 0)
return -1;
} else if(n < 0){
sz = uvmdealloc(p->pagetable, sz, sz + n);
// 缩小时更新
if (sz != p->sz) {
uvmunmap(p->kpagetable, PGROUNDUP(sz), (PGROUNDUP(p->sz) - PGROUNDUP(sz)) / PGSIZE, 0);
}
}
p->sz = sz;
return 0;
}
void
userinit(void)
{
...
// allocate one user page and copy init's instructions
// and data into it.
uvminit(p->pagetable, initcode, sizeof(initcode));
p->sz = PGSIZE;
// 复制用户页表的用户内存mapping到内核页表
pagecopy(p->pagetable, p->kpagetable, 0, p->sz);
...
}
// exec.c
int
exec(char *path, char **argv)
{
...
// Commit to the user image.
oldpagetable = p->pagetable;
p->pagetable = pagetable;
p->sz = sz;
p->trapframe->epc = elf.entry; // initial program counter = main
p->trapframe->sp = sp; // initial stack pointer
proc_freepagetable(oldpagetable, oldsz);
// 将用户页表的用户内存空间的mapping复制到内核页表中
if (pagecopy(p->pagetable, p->kpagetable, 0, p->sz) != 0) {
goto bad;
}
ukvminithart(p->kpagetable);
...
}
4、评测
合集:
mit6.s081
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通