Lab5 Copy-on-write fork

页表牛逼

实现 Copy-on-write fork

  1. 添加宏定义 PTE_COW,使用 PTE 的 RSW 最低有效位,来标识是否是 COW 页,只用于可写页
kernel/riscv.h
1
#define PTE_COW (1L << 8)
  1. 添加 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.c
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
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)) {
// Fill with junk to catch dangling refs.
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); // fill with junk
}

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);
}
  1. 修改 uvmcopy,在复制时将子进程页表直接指向父进程页表对应的物理地址

因为在调用 fork 时,复制内存就是直接调用 uvmcopy,修改这个就行

当遇到可写的页时,取消 PTE_W,添加 PTE_COW

kernel/vm.c
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
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;
}

怎么有人总是把 & 和 | 的功能写反

  1. 修改 kernel/trap.c 的 usertrap 和 kernel/vm.c 的 copyout,添加遇到 COW 页的情况

注意虚拟地址要小于 MAXVA,否则在 walk 时会直接出现 panic

kernel/vm.c
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
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.c
1
2
3
4
5
if(r_scause() == 15 && r_stval() < MAXVA && (*(pte = walk(p->pagetable, r_stval(), 0)) & PTE_COW)) {
// store COW page fault
if(copyCOW(pte)) {
setkilled(p);
}

最后在 kernel/defs.h 里添加一些函数声明即可

Optional challenge exercise

测量你的 COW 实现减少了多少字节的复制和多少页物理内存的分配

#todo

作者

Humoooor

发布于

2022-11-12

更新于

2024-01-04

许可协议

评论