6.S081-2021-Lab3|6.S081-2021-Lab3 Pgtbl学习笔记

Speed up system calls 根据hints查看kernel/proc.c中的函数proc_pagetable

// kernel/proc.c // Create a user page table for a given process, // with no user memory, but with trampoline pages. pagetable_t proc_pagetable(struct proc *p) { // map the trampoline code (for system call return) // at the highest user virtual address. // only the supervisor uses it, on the way // to/from user space, so not PTE_U. if(mappages(pagetable, TRAMPOLINE, PGSIZE, (uint64)trampoline, PTE_R | PTE_X) < 0){ uvmfree(pagetable, 0); return 0; }// map the trapframe just below TRAMPOLINE, for trampoline.S. if(mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0){ uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } .... }

6.S081-2021-Lab3|6.S081-2021-Lab3 Pgtbl学习笔记
文章图片

结合代码以及手册,USYSCALL页面的位置在heap之前,trapfram之后
only read 对应赋予PTE_R | PTE_U
// kernel/proc.c ... // map the trapframe just below TRAMPOLINE, for trampoline.S. if(mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe), PTE_R | PTE_W) < 0){ uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } // map the usyscall just below TRAPFRAME if (mappages(pagetable, USYSCALL, PGSIZE, (uint64) (p->usyscall), PTE_R | PTE_U) < 0) { uvmunmap(pagetable, USYSCALL, 1, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmfree(pagetable, 0); return 0; } return pagetable;

分配和初始化页面
  • Don't forget to allocate and initialize the page in allocproc().
allocate函数中参考empty user page的方式依葫芦画瓢为usyscall分配空间
同时要记得将pid返回到用户态,这样才能在用户态直接使用pid
kernel/proc.hstruct proc中添加 struct usyscall* usyscall
.... // Allocate a USYSCALL page. if((p->usyscall = (struct usyscall *)kalloc()) == 0){ freeproc(p); release(&p->lock); return 0; } p->usyscall->pid = p->pid; // An empty user page table. p->pagetable = proc_pagetable(p); if(p->pagetable == 0){ freeproc(p); release(&p->lock); return 0; } ...

解除映射的部分有两个函数要修改
  • Make sure to free the page in freeproc().
这里一定要释放,不然后面的test过不了
在函数proc_freepagetable添加代码
// Free a process's page table, and free the // physical memory it refers to. void proc_freepagetable(pagetable_t pagetable, uint64 sz) { // free USYSCALL uvmunmap(pagetable, USYSCALL, 1, 0); uvmunmap(pagetable, TRAMPOLINE, 1, 0); uvmunmap(pagetable, TRAPFRAME, 1, 0); uvmfree(pagetable, sz); }

在函数freeproc添加代码
static void freeproc(struct proc *p) { if(p->trapframe) kfree((void*)p->trapframe); p->trapframe = 0; if (p->usyscall) kfree((void *) p->usyscall); if(p->pagetable) proc_freepagetable(p->pagetable, p->sz); ... }

Print a page table 在exec.c文件的return argc语句前插入if(p->pid==1) vmprint(p->pagetable)
参考freewalk
void freewalk(pagetable_t pagetable) { // 一张页表由512个页表项(PTE)组成 for(int i = 0; i < 512; i++){ // 取出当前页表项 pte_t pte = pagetable[i]; // 当该条目有效,但却无法读、写、执行的时候 // 说明这是一条指向子页面的PTE,递归遍历子页面 if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){ // this PTE points to a lower-level page table. uint64 child = PTE2PA(pte); freewalk((pagetable_t)child); pagetable[i] = 0; } else if(pte & PTE_V){ panic("freewalk: leaf"); } } kfree((void*)pagetable); }

我们的vmprint基本就是copy freewalk只不过不需要free页面
照着格式来
kernel/vm.c编写函数vmprint,别忘了在kernel/defs.h中声明
void backtrace(pagetable_t pagetable, int level) { 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); for (int j=0; j <= level; j++) { printf(".."); if ((j+1) <= level) { printf(" "); } } printf("%d: pte %p pa %p\n", i, pte, child); backtrace((pagetable_t)child, level+1); } else if (pte & PTE_V) { uint64 child = PTE2PA(pte); printf(".. .. ..%d: pte %p pa %p\n", i, pte, child); } } }void vmprint(pagetable_t pagetable) { printf("page table %p\n", pagetable); backtrace(pagetable, 0); }

Detecting which pages have been accessed 这个实验需要我们判断页面是否被访问过,为此需要添加一个标志位PTE_A来标识页面是否被访问过
  • You'll need to define PTE_A, the access bit, in kernel/riscv.h. Consult the RISC-V manual to determine its value.
根据提示查阅手册
6.S081-2021-Lab3|6.S081-2021-Lab3 Pgtbl学习笔记
文章图片

【6.S081-2021-Lab3|6.S081-2021-Lab3 Pgtbl学习笔记】kernel/riscv.h中添加
#define PTE_A (1L << 6) // access bit
  • You'll need to parse arguments using argaddr() and argint()
  • First, it takes the starting virtual address of the first user page to check. Second, it takes the number of pages to check. Finally, it takes a user address to a buffer to store the results into a bitmask (a datastructure that uses one bit per page and where the first page corresponds to the least significant bit)
根据这两个提示我们可以猜测出三个参数的类型应该分别为uint64,int,uint64,分别是第一个需要检查的用户页的虚拟地址,要检查的页表数,以及输出结果的用户态地址(因为内核态跟用户态的空间是不互通的,所以要借助 copyout 将 bitmask 传给用户态程序)
根据参数可以知道我们是要查看n个页的,问题是只给了第一个用户页的虚拟地址,之后的n个页怎么查看。其实只需要每次循环加上页表大小PGSIZE就能到下一个页了
同时我们来看一下walk,它的作用是根据虚拟地址返回页表中对应的PTE
pte_t * walk(pagetable_t pagetable, uint64 va, int alloc) { if(va >= MAXVA) panic("walk"); for(int level = 2; level > 0; level--) { // 主要注意这里,请结合下图理解 pte_t *pte = &pagetable[PX(level, va)]; if(*pte & PTE_V) { pagetable = (pagetable_t)PTE2PA(*pte); } else { if(!alloc || (pagetable = (pde_t*)kalloc()) == 0) return 0; memset(pagetable, 0, PGSIZE); *pte = PA2PTE(pagetable) | PTE_V; } } return &pagetable[PX(0, va)]; }

6.S081-2021-Lab3|6.S081-2021-Lab3 Pgtbl学习笔记
文章图片

#define PGSHIFT 12// 对应图中的offset #define PXSHIFT(level)(PGSHIFT+(9*(level))) //L2 L1 L0 都是9位,+9*level是为了定位到对应的level上 #define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) //获得L2或L1,L0的bits

明白了walk的功能就可以完成这一部分的任务了
  • 通过虚拟地址VA确定PTE(walk来完成)
  • 检查PTE的标志位PTE_A是否为1,1表示被访问过
    • 记得将PTE_A清0,以防影响再次调用sys_pgaccess时影响判断
  • VA+=PGSIZE得到下一个页面的虚拟地址
#ifdef LAB_PGTBL uint64 sys_pgaccess(void) { uint64 va; int page_num; uint64 user_bitmask_addr; if(argaddr(0, &va) < 0) return -1; if(argint(1, &page_num) < 0) return -1; if(argaddr(2, &user_bitmask_addr) < 0) return -1; // 32是看test得出来的 // It's okay to set an upper limit on the number of pages that can be scanned. if(page_num < 0 || page_num > 32) return -1; uint32 bitmask = 0; pte_t *pte; struct proc *p = myproc(); for(int i = 0; i < page_num; i++){ if(va >= MAXVA) return -1; // alloc不为0,walk就会为找不到对应pte地址的va申请一个页 // 显然不存在,就表示没有被访问过 // 我们无需创建页,这不是当前函数的职责 pte = walk(p->pagetable, va, 0); if(pte == 0) return -1; if(*pte & PTE_A){ bitmask |= (1 << i); // Be sure to clear PTE_A after checking if it is set. *pte &= (~PTE_A); } va += PGSIZE; }if(copyout(p->pagetable, user_bitmask_addr, (char*)&bitmask, sizeof(bitmask)) < 0) return -1; return 0; } #endif

    推荐阅读