实验链接
https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html
Print a page table
代码更改如下:
-
kernel/defs.h
// vm.c void vmprint(pagetable_t pagetable);
-
kernel/exec.c
// 函数exec的return argc之前 if (p->pid == 1) { vmprint(p->pagetable); }
-
kernel/vm.c
void dotprinter(int level, int indexid) { int dotcnt = 3 - level; for (int j = 0; j < dotcnt - 1; j++) { printf(".. "); } printf("..%d: "); } void vmprinthelper(pagetable_t pagetable, int level) { if (level < 0) { return; } for (int i = 0; i < 512; i++) { pte_t pte = pagetable[i]; if ((pte & PTE_V) == 0) // invalid pte continue; dotprinter(level, i); // printf(" pte %p pa %p useraccessable: %d \n", pte, PTE2PA(pte), (pte & PTE_U)); printf("pte %p pa %p\n", pte, PTE2PA(pte)); vmprinthelper((pagetable_t)PTE2PA(pte), level - 1); } } void vmprint(pagetable_t pagetable) { printf("page table %p\n", pagetable); vmprinthelper(pagetable, 2); }
A kernel page table per process
代码更改如下:
-
在文件kernel/proc.h中,为结构体proc添加如下字段
pagetable_t kernel_pagetable;
-
在文件kernel/vm.c中,添加如下函数;
// 注意添加头文件,否则编译失败 #include "spinlock.h" #include "proc.h" pagetable_t kvmbuild(void) { pagetable_t pagetable = (pagetable_t) kalloc(); memset(pagetable, 0, PGSIZE); // uart registers mappages(pagetable, UART0, PGSIZE, UART0, PTE_R | PTE_W); // virtio mmio disk interface mappages(pagetable, VIRTIO0, PGSIZE, VIRTIO0, PTE_R | PTE_W); // CLINT mappages(pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W); // PLIC mappages(pagetable, PLIC, 0x400000, PLIC, PTE_R | PTE_W); // map kernel text executable and read-only. mappages(pagetable, KERNBASE, (uint64)etext-KERNBASE, KERNBASE, PTE_R | PTE_X); // map kernel data and the physical RAM we'll make use of. mappages(pagetable, (uint64)etext, PHYSTOP-(uint64)etext, (uint64)etext, PTE_R | PTE_W); // map the trampoline for trap entry/exit to // the highest virtual address in the kernel. mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X); return pagetable; }
-
在文件kernel/proc.c中,做如下更改,主要作用是为进程创建内核页表,并建立内核页表与kstack的映射。
void procinit(void) { struct proc *p; initlock(&pid_lock, "nextpid"); for(p = proc; p < &proc[NPROC]; p++) { initlock(&p->lock, "proc"); // 注释如下内容,因为此时全局的内核页表已经不需要kstack // kstack需在process私有的内核页表中进行映射 // Allocate a page for the process's kernel stack. // Map it high in memory, followed by an invalid // guard page. // char *pa = kalloc(); // if(pa == 0) // panic("kalloc"); // uint64 va = KSTACK((int) (p - proc)); // kvmmap(va, (uint64)pa, PGSIZE, PTE_R | PTE_W); // p->kstack = va; } // kvminithart(); } static struct proc* allocproc(void) { struct proc *p; for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == UNUSED) { goto found; } else { release(&p->lock); } } return 0; found: p->pid = allocpid(); // Allocate a trapframe page. if((p->trapframe = (struct trapframe *)kalloc()) == 0){ release(&p->lock); return 0; } // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; } // 以下为新添加内容 // 为process创建私有的内核页表 p->kernel_pagetable = kvmbuild(); // 为内核页表建立与kstack的映射 char *pa = kalloc(); if(pa == 0) panic("kalloc"); uint64 va = KSTACK((int) (p - proc)); mappages(p->kernel_pagetable, va, PGSIZE, (uint64)pa, PTE_R | PTE_W); p->kstack = va; // 结束,注意,上边添加的代码必须在下面代码之前 // 因为下边的代码会用到p->kstack的值,而在此之前,我们 // 必须对其进行赋值 // Set up new context to start executing at forkret, // which returns to user space. memset(&p->context, 0, sizeof(p->context)); p->context.ra = (uint64)forkret; p->context.sp = p->kstack + PGSIZE; return p; }
-
在文件kernel/proc.c中,对函数scheduler进行如下更改:
void scheduler(void) { struct proc *p; struct cpu *c = mycpu(); c->proc = 0; for(;;) { // Avoid deadlock by ensuring that devices can interrupt. intr_on(); int found = 0; for(p = proc; p < &proc[NPROC]; p++) { acquire(&p->lock); if(p->state == RUNNABLE) { // 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; // begin +++++++++, // 切换页表地址为该进程私有页表的地址 w_satp(MAKE_SATP(p->kernel_pagetable)); sfence_vma(); // end ----------- swtch(&c->context, &p->context); // Process is done running for now. // It should have changed its p->state before coming back. // begin +++++++++ // 切换为全局的kernel页表地址 kvminithart(); // use kernel page table // end ----------- c->proc = 0; found = 1; } release(&p->lock); } #if !defined (LAB_FS) if(found == 0) { intr_on(); asm volatile("wfi"); } #else ; #endif } }
-
在文件kernel/proc.c的函数freeproc函数中,增加如下内容
// begin +++++ extern char etext[]; // kernel.ld sets this to end of kernel code. // end------- static void freeproc(struct proc *p) { if(p->trapframe) kfree((void*)p->trapframe); p->trapframe = 0; // begin ++++++ // 取消kstack映射,并销毁kstack对应的物理内存 if (p->kstack) uvmunmap(p->kernel_pagetable, p->kstack, 1, 1); p->kstack = 0; // 释放私有页表 if(p->kernel_pagetable) proc_free_kernel_pagetable(p->kernel_pagetable); p->kernel_pagetable = 0; // end --------- if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); p->pagetable = 0; p->sz = 0; p->pid = 0; p->parent = 0; p->name[0] = 0; p->chan = 0; p->killed = 0; p->xstate = 0; p->state = UNUSED; } // 新增函数,用于取消映射和销毁页表 void proc_free_kernel_pagetable(pagetable_t pagetable) { uvmunmap(pagetable, UART0, 1, 0); uvmunmap(pagetable, VIRTIO0, 1, 0); uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 0); uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0); uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0); uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); freewalk(pagetable); }
-
在kernel/def.h中,增加我们新添加的函数
+void proc_free_kernel_pagetable(pagetable_t); +void freewalk(pagetable_t); +pagetable_t kvmbuild(void);
-
在kernel/vm.c中,更改以下函数(注意,该函数的更改在作业提示中并未给出)
uint64 kvmpa(uint64 va) { uint64 off = va % PGSIZE; pte_t *pte; uint64 pa; // 将walk函数的第一个参数改为进程私有的内核页表 struct proc* p = myproc(); pte = walk(p->kernel_pagetable, va, 0); if(pte == 0) panic("kvmpa"); if((*pte & PTE_V) == 0) panic("kvmpa"); pa = PTE2PA(*pte); return pa+off; }
Simplify
还是按照题目的步骤来:
-
在文件kernel/vm.c中,更改函数
copyin
和copyinstr
。int copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) { return copyin_new(pagetable, dst, srcva, len); } // Copy a null-terminated string from user to kernel. // Copy bytes to dst from virtual address srcva in a given page table, // until a '\0', or max. // Return 0 on success, -1 on error. int copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) { return copyinstr_new(pagetable, dst, srcva, max); }
-
在文件kernel/defs.h中,添加如下内容
// vmcopyin.c int copyin_new(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len); int copyinstr_new(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max);
-
在文件kernel/vm.c中,添加用户态pagetable拷贝至kernel态pagetable的函数。
void uvm2kvm(pagetable_t pagetable, pagetable_t kpagetable, uint64 old_size, uint64 new_size) { if (new_size < old_size) panic("new size lower than old size"); if (PGROUNDUP(new_size) >= PLIC) panic("new size too big"); uint64 begin = PGROUNDUP(old_size); uint64 end = PGROUNDUP(new_size); // printf("begin: %x, end: %x\n", begin, end); for (uint64 va = begin; va < end; va += PGSIZE) { pte_t* pte = walk(pagetable, va, 0); if (pte == 0) panic("user page table not found"); pte_t* kpte = walk(kpagetable, va, 1); if (kpte == 0) panic("kernel page table not found"); *kpte = (*pte) & (~PTE_U); } }
-
在文件kernel/defs.h中添加步骤三函数的声明
void uvm2kvm(pagetable_t, pagetable_t, uint64, uint64);
-
在kernel/proc.c中的函数
userinit
中添加映射p->state = RUNNABLE; // begin ++++++++ uvm2kvm(p->pagetable, p->kernel_pagetable, 0, p->sz); // end ++++++++ release(&p->lock);
-
在kernel/proc.c中的函数fork中添加映射
// increment reference counts on open file descriptors. for(i = 0; i < NOFILE; i++) if(p->ofile[i]) np->ofile[i] = filedup(p->ofile[i]); np->cwd = idup(p->cwd); // begin ++++++++ uvm2kvm(np->pagetable, np->kernel_pagetable, 0, np->sz); // end ++++++++ safestrcpy(np->name, p->name, sizeof(p->name));
-
在kernel/exec.c中的函数exec中添加映射
..... // Load program into memory. for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){ if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph)) goto bad; if(ph.type != ELF_PROG_LOAD) continue; if(ph.memsz < ph.filesz) goto bad; if(ph.vaddr + ph.memsz < ph.vaddr) goto bad; uint64 sz1; if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0) goto bad; sz = sz1; if(ph.vaddr % PGSIZE != 0) goto bad; if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0) goto bad; // begin ++++++++ if (sz1 >= PLIC) goto bad; // end ++++++++ } ..... if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0) goto bad; // begin ++++++++ // 因为exec函数会重新装载新进程,因此我们需要重新映射其内核的pagetable uvmunmap(p->kernel_pagetable, 0, PGROUNDUP(oldsz)/PGSIZE, 0); // 将新的pagetable拷贝至kernel的pagetable uvm2kvm(pagetable, p->kernel_pagetable, 0, sz); // end ++++++++
-
在kernel/proc.c中的函数growproc中添加映射(也就是sys_sbrk函数)
int growproc(int n) { uint sz; struct proc *p = myproc(); sz = p->sz; if(n > 0) { if((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) { return -1; } // begin ++++++++ uvm2kvm(p->pagetable, p->kernel_pagetable, sz - n, sz); // end ++++++++ } else if(n < 0) { sz = uvmdealloc(p->pagetable, sz, sz + n); // begin ++++++++ uvmunmap(p->kernel_pagetable, PGROUNDUP(sz), (-n)/PGSIZE, 0); // end ++++++++ } p->sz = sz; return 0; }
-
修改kernel/vm.c文件中为每个进程创建内核页表的函数时,注意将Client的映射注释掉。(因为client的映射地址为0x10000, 而PLIC的映射地址为0x400000,即client低于PLIC的地址,而进程的pagetable拷贝至内核时的pagetable时,会导致原本0x10000多次被映射)
// CLINT // mappages(pagetable, CLINT, 0x10000, CLINT, PTE_R | PTE_W);
-
在kernel/proc.c文件中,修改释放kernel_pagetable的函数.(将client相关的代码也删掉,同时释放低地址空间的映射)
void proc_free_kernel_pagetable(pagetable_t pagetable, uint64 sz) { uvmunmap(pagetable, UART0, 1, 0); uvmunmap(pagetable, VIRTIO0, 1, 0); // uvmunmap(pagetable, CLINT, 0x10000/PGSIZE, 0); uvmunmap(pagetable, PLIC, 0x400000/PGSIZE, 0); uvmunmap(pagetable, KERNBASE, ((uint64)etext-KERNBASE)/PGSIZE, 0); uvmunmap(pagetable, (uint64)etext, (PHYSTOP-(uint64)etext)/PGSIZE, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); // begin++++++++ uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 0); // end++++++++++ freewalk(pagetable); }
实验结果
提交实验
$ git commit -m "lab page tabls"
$ make handin
查看结果
登录网站https://6828.scripts.mit.edu/2020/handin.py/student
,可以看到提交的结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hdax6MS0-1658384528631)()]
参考链接
https://mit-public-courses-cn-translatio.gitbook.io/mit6-s081
https://blog.csdn.net/u012419550/article/details/114701482
https://blog.csdn.net/u013577996/article/details/109582932
https://blog.csdn.net/rocketeerLi/article/details/121524760
github地址
https://github.com/aerfalwl/mit-xv6-labs-2020.git