lab5
21年后的lab删除了该lab
内存分配
- 急分配:当进程请求内存时,操作系统立即为其分配内存。
懒分配:当进程请求内存时,操作系统不会立即为其分配内存,而是等待内存被释放后再为其分配。
COPY-ON-WRITE(COW) —FORK 牛叉:当一个进程创建另一个进程的副本时,新进程会开始时共享父进程的内存页。只有在这些页被写入时,才会复制这些页。
Demand page:当进程请求一个不存在的页面时,操作系统会将其加载到内存中。
least-recently-used(LRU):LRU是一种页面替换算法,它根据页面最近被访问的时间来决定哪个页面应该被替换。
PTE_A 的作用:可用于时钟算法
memory-mapped-file:mmap系统调用
Eliminate allocation from sbrk() (easy)
老师已经在lecture上讲了,所以完成的比较轻松。
首先我们修改sys_sbrk()
函数,只修改sz
大小,将内存分配交给中断!
1 2 3 4 5 6 7 8 9 10 11
| uint64 sys_sbrk(void) { int addr; int n; if(argint(0, &n) < 0) return -1; addr = myproc()->sz; myproc()->sz = myproc()->sz + n; return addr; }
|
我们修改中断函数,在中断时,为r_stval
寄存器(存储要访问内存的地址)分配页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| else if(r_scause() == 13 || r_scause() == 15) { uint64 va = r_stval(); char *mem ; va = PGROUNDDOWN(va); printf("page fault: %p\n",va); if((mem = kalloc()) == 0) { p->killed = 1; }else{ memset(mem, 0, PGSIZE); if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) { kfree(mem); p->killed = 1; } } }
|
由于该内存可能未经使用,而我们已经修改了sz
,当释放堆内存时,由于是逐页释放的,当访问到未使用的页(未分配内存),检测到有效位PTE_V未设置,触发panic,因此我们需要修改uvmunmap
。
1 2 3 4 5 6 7 8
| void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) { ... if((*pte & PTE_V) == 0) continue; ... }
|
Lazytests and Usertests
在sys_sbrk
我们需要处理当n为负数的情况,参考growproc()
,当空出一页时要及时释放该页。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint64 sys_sbrk(void) { int addr; int n; struct proc *p = myproc(); if(argint(0, &n) < 0) return -1; addr = p->sz; if(n >= 0){ p->sz = p->sz + n; return addr; }else if( addr + n > 0){ p->sz = uvmdealloc(p->pagetable, addr, p->sz + n); } else { return -1; }
|
在trap.c
里,添加对va
的限制(限制在堆区)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| else if(r_scause() == 13 || r_scause() == 15) { uint64 va = r_stval(); char *mem ; va = PGROUNDDOWN(va); if(va > p->sz ) { p->killed = 1; } else if (va >= p->sz || va <= PGROUNDDOWN(p->trapframe->sp)) { p->killed = 1; }else if((mem = kalloc()) == 0) { p->killed = 1; }else{ memset(mem, 0, PGSIZE); if(mappages(p->pagetable, va, PGSIZE, (uint64)mem, PTE_W|PTE_X|PTE_R|PTE_U) != 0) { kfree(mem); p->killed = 1; } } }
|
修改vm.c
的uvmunmap
和uvmcopy
,防止在folk()
中出现panic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| void uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) { ... if((pte = walk(pagetable, a, 0)) == 0) continue; if((*pte & PTE_V) == 0) continue; ... }
int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { ... if((pte = walk(old, i, 0)) == 0) continue; if((*pte & PTE_V) == 0) continue; ... }
|
在read
和write
等系统调用中,可能会用到未实际分配内存的地址。
此时由于进入了内核,无法触发用户中断,因此我们要让该地址所在的页已分配才能正常执行这些系统调用。显然,一个个修改系统调用是不现实的。
我们知道系统调用实际是通过argaddr
、argint
间接获得参数,且地址传入都通过argaddr
函数。
因此我们可以在argaddr
检测该地址所在的页是否被分配,如果未分配,则先分配再进入系统调用执行阶段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| int argaddr(int n, uint64 *ip) { *ip = argraw(n); struct proc* p = myproc(); if(walkaddr(p->pagetable, *ip) == 0) { if(PGROUNDUP(p->trapframe->sp) <= *ip && *ip < p->sz) { char* pa = kalloc(); if(pa == 0) return -1; memset(pa, 0, PGSIZE);
if(mappages(p->pagetable, PGROUNDDOWN(*ip), PGSIZE, (uint64)pa, PTE_R | PTE_W | PTE_X | PTE_U) != 0) { kfree(pa); return -1; } } else { return -1; } } return 0; }
|