页表牛逼
实现 Copy-on-write fork
- 添加宏定义 PTE_COW,使用 PTE 的 RSW 最低有效位,来标识是否是 COW 页,只用于可写页
kernel/riscv.h1
| #define PTE_COW (1L << 8)
|
- 添加 reference count 标识一个物理页面被几个用户页表指向,初始化,增减
提示使用 kenel/kalloc.c 里 kinit()
里 freerange()
的范围,通过调试 end = 0x80041c50,PHYSTOP = 0x88000000,相减除以 4096 得 0x7fbe
但是在 make qemu 时,过 usertests -q
时,最后会显示丢失一些页,但是 make CPUS=1 qemu-gdb 时又显示通过,可能子啊多核时会出现一些问题,很怪
将 0x7fbe 改为 0x7fc0 就没有问题,不是很懂,如果有了解的师傅可以告诉我🐎
笔者把它放到 kmem 里,增减时用 kmem.lock 锁
kernel/kalloc.c1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| struct { struct spinlock lock; struct run *freelist; uint refers[0x7fc0]; } kmem;
void kinit() { initlock(&kmem.lock, "kmem"); acquire(&kmem.lock); for(int i = 0; i < sizeof(kmem.refers) / sizeof(kmem.refers[0]); ++i) { kmem.refers[i]++; } release(&kmem.lock); freerange(end, (void*)PHYSTOP); }
void kfree(void *pa) { struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) panic("kfree");
krefDecre(pa); if(!kref(pa)) { memset(pa, 1, PGSIZE);
r = (struct run*)pa; acquire(&kmem.lock); r->next = kmem.freelist; kmem.freelist = r; release(&kmem.lock); } }
void * kalloc(void) { struct run *r;
acquire(&kmem.lock); r = kmem.freelist; if(r) kmem.freelist = r->next; release(&kmem.lock);
if(r) { krefIncre((void*)r); memset((char*)r, 5, PGSIZE); }
return (void*)r; }
uint kref(void *pa) { return kmem.refers[(pa - (void*)end)/4096]; }
void krefIncre(void *pa) { acquire(&kmem.lock); kmem.refers[(pa - (void*)end)/4096]++; release(&kmem.lock); }
void krefDecre(void *pa) { acquire(&kmem.lock); kmem.refers[(pa - (void*)end)/4096]--; release(&kmem.lock); }
|
- 修改
uvmcopy
,在复制时将子进程页表直接指向父进程页表对应的物理地址
因为在调用 fork
时,复制内存就是直接调用 uvmcopy
,修改这个就行
当遇到可写的页时,取消 PTE_W,添加 PTE_COW
kernel/vm.c1 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 31 32 33
| int uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) { pte_t *pte; uint64 pa, i; uint flags;
for(i = 0; i < sz; i += PGSIZE){ if((pte = walk(old, i, 0)) == 0) panic("uvmcopy: pte should exist"); if((*pte & PTE_V) == 0) panic("uvmcopy: page not present"); pa = PTE2PA(*pte); flags = PTE_FLAGS(*pte);
if(flags & PTE_W) { flags = (flags & ~PTE_W) | PTE_COW; } if(mappages(new, i, PGSIZE, pa, flags) != 0){ goto err; } if(flags & PTE_COW) { *pte = PA2PTE(pa) | flags; }
krefIncre((void*)pa); } return 0;
err: uvmunmap(new, 0, i / PGSIZE, 1); return -1; }
|
怎么有人总是把 & 和 | 的功能写反
- 修改 kernel/trap.c 的
usertrap
和 kernel/vm.c 的 copyout
,添加遇到 COW 页的情况
注意虚拟地址要小于 MAXVA,否则在 walk
时会直接出现 panic
kernel/vm.c1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| int copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) { uint64 n, va0, pa0; pte_t *pte = 0;
while(len > 0){ va0 = PGROUNDDOWN(dstva); if(dstva < MAXVA) { pte = walk(pagetable, dstva, 0); } if(pte && (*pte & PTE_COW)) { if(copyCOW(pte)) { return -1; } } pa0 = walkaddr(pagetable, va0); if(pa0 == 0) return -1; n = PGSIZE - (dstva - va0); if(n > len) n = len; memmove((void *)(pa0 + (dstva - va0)), src, n);
len -= n; src += n; dstva = va0 + PGSIZE; } return 0; }
int copyCOW(pte_t *pte) { uint64 pa = PTE2PA(*pte); uint flags = (PTE_FLAGS(*pte) & ~PTE_COW) | PTE_W; *pte = PA2PTE(pa) | flags;
if(kref((void*)pa) != 1) { char *mem = kalloc(); if(mem == 0) { return -1; } else { memmove(mem, (char*)pa, PGSIZE); *pte = PA2PTE(mem) | flags; krefDecre((void*)pa); } } return 0; }
|
kernel/trap.c1 2 3 4 5
| if(r_scause() == 15 && r_stval() < MAXVA && (*(pte = walk(p->pagetable, r_stval(), 0)) & PTE_COW)) { if(copyCOW(pte)) { setkilled(p); }
|
最后在 kernel/defs.h 里添加一些函数声明即可
Optional challenge exercise
测量你的 COW 实现减少了多少字节的复制和多少页物理内存的分配
#todo