https://pdos.csail.mit.edu/6.S081/2020/labs/pgtbl.html
代码更改如下:
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);
}
代码更改如下:
在文件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;
}
还是按照题目的步骤来:
在文件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
https://github.com/aerfalwl/mit-xv6-labs-2020.git