建设网站成都wordpress数据库密码错误
TOC
实验内容
- 背景:
- 在 xv6 中,fork()系统调用会将父进程的所有用户空间内存复制到子进程中。如果父进程很大,复制操作可能会花费很长时间。
- 在子进程中,fork()通常会紧接着 exec(),这会丢弃复制的内存,通常不会使用大部分内存。
- 解决方案:
- 延迟分配
- 写时复制
- COW fork() 为子进程创建一个页表,用户内存的 PTE 指向父进程的物理页。COW fork() 将父进程和子进程中的所有用户 PTE 标记为只读。当任一进程尝试写入这些 COW 页面时,CPU 将引发页面故障。内核页面故障处理程序检测到这种情况,为故障进程分配一页物理内存,将原始页面复制到新页面,并在故障进程的相关 PTE 中修改为指向新页面,这次 PTE 标记为可写。当页面故障处理程序返回时,用户进程将能够写入其页面的副本。
- 具体任务
- 在 xv6 内核中实现copy-on-write fork。如果你修改后的内核能够成功执行 cowtest和usertests -q程序,那么你就完成了任务。
实现逻辑
修改内容
kernel/riscv.h
中添加PTE_COW
标志位
#define PTE_COW (1L << 8) //自定义COW标志位 lab5
kernel/kalloc.c
添加页映射计数数组、相关函数(修改时使用锁)
// lab5
struct {struct spinlock lock; // 引用计数专用锁int count[(PHYSTOP - KERNBASE) / PGSIZE]; // 引用计数数组
}kref;
// 增加引用计数
void kref_incr(uint64 pa) {acquire(&kref.lock);int idx = (pa - KERNBASE) / PGSIZE;kref.count[idx]++;release(&kref.lock);
}
// 减少引用计数
void kref_decr(uint64 pa) {int idx = (pa - KERNBASE) / PGSIZE;acquire(&kref.lock);if (kref.count[idx] <= 0) panic("kref_decr: invalid refcount");kref.count[idx]--;release(&kref.lock);
}
// 查询引用次数
int kref_count(uint64 pa) {acquire(&kref.lock);int idx = (pa - KERNBASE) / PGSIZE;release(&kref.lock);return kref.count[idx];
}
kernel/kalloc.c
中修改初始化、分配页和释放页函数
void
kinit()
{// lab5 初始化计数数组for (int i = 0; i < (PHYSTOP - KERNBASE) / PGSIZE; i++) kref.count[i] = 0;initlock(&kref.lock, "kref");initlock(&kmem.lock, "kmem");freerange(end, (void*)PHYSTOP);
}void
kfree(void *pa)
{struct run *r;if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)panic("kfree");// lab5acquire(&kmem.lock);if ( kref_count((uint64)pa) > 1) {kref_decr((uint64)pa); // 引用计数减 1release(&kmem.lock);return;}acquire(&kref.lock);kref.count[((uint64)pa - KERNBASE) / PGSIZE] = 0; // 清零后释放 release(&kref.lock);// 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;// lab5acquire(&kref.lock);kref.count[((uint64)r - KERNBASE) / PGSIZE] = 1; // 初始化为 1 release(&kref.lock);}release(&kmem.lock);if(r)memset((char*)r, 5, PGSIZE); // fill with junkreturn (void*)r;
}
- 只有当页的映射数为1时才能释放- 分配页时增加数组计数
kernel/vm.c
中添加写时分配的实现形式
int cow_alloc(pagetable_t pagetable, uint64 va) {va = PGROUNDDOWN(va); // 对齐到页边界// 检查va大小,在walk之前,否则无法应对恶意输入地址if(va >= MAXVA)return -1;pte_t *pte = walk(pagetable, va, 0);if (!pte || !(*pte & PTE_V) || !(*pte & PTE_COW)) {return -1; // 非法地址或非 COW 页}uint64 pa = PTE2PA(*pte); // 原物理地址// 如果引用计数为 1,直接恢复写权限if (kref_count(pa) == 1) { *pte |= PTE_W;*pte &= ~PTE_COW;sfence_vma(); // 刷新 TLBreturn 0;}// 分配新页并复制数据char *new_pa = kalloc();if (!new_pa) return -1; // 内存不足memmove(new_pa, (char*)pa, PGSIZE); // 复制原页内容// 更新页表项:新页可写,清除 COW 标志uint64 flags = (PTE_FLAGS(*pte) | PTE_W ) & ~PTE_COW;*pte = PA2PTE(new_pa) | flags;kfree((void*)pa); // 减少原页的引用计数sfence_vma(); // 刷新 TLBreturn 0;
}
kernel/vm.c
中的修改
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");// lab5 只考虑可写页面标记cow,否则会使不可写的页在cow处理后变为可写!if(*pte & ~PTE_W){*pte = (*pte & ~PTE_W) | PTE_COW; // 清除父进程的写权限,标记为 COW 页}pa = PTE2PA(*pte); // 获取物理地址flags = PTE_FLAGS(*pte);kref_incr(pa); //增加引用计数// 子进程页表继承只读 + COW 标志,映射到同一物理页if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){kref_decr(pa);goto err;} }return 0;err:uvmunmap(new, 0, i / PGSIZE, 1);return -1;
}int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{uint64 n, va0, pa0;pte_t *pte;while(len > 0){va0 = PGROUNDDOWN(dstva);if(va0 >= MAXVA)return -1;pte = walk(pagetable, va0, 0);// lab5去掉pte_w检查,进行常规检查if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 ) return -1;// 禁止写入代码页(PTE_X)if (*pte & PTE_X) {return -1;}// lab5 if(*pte & PTE_COW){// cow页uint64 pa = PTE2PA(*pte); // 对应的物理页地址if(kref_count(pa) > 1){// 该物理页被多次映射,需重新分配if( cow_alloc(pagetable, va0) < 0 ){return -1;}// 获取新的ptepte = walk(pagetable, va0, 0);if( pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || (*pte & PTE_W) == 0){return -1;}sfence_vma(); // 刷新TLB}else{// 该物理页只有一次映射*pte |= PTE_W;*pte &= ~PTE_COW;sfence_vma(); // 刷新TLB}} else {// 非 COW 页:检查是否可写if ((*pte & PTE_W) == 0) {return -1; // 目标页不可写,返回错误}}pa0 = PTE2PA(*pte);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;
}
kernel/trap.c
中添加陷入处理
void
usertrap(void)
{int which_dev = 0;if((r_sstatus() & SSTATUS_SPP) != 0)panic("usertrap: not from user mode");// send interrupts and exceptions to kerneltrap(),// since we're now in the kernel.w_stvec((uint64)kernelvec);struct proc *p = myproc();// save user program counter.p->trapframe->epc = r_sepc();if(r_scause() == 8){// system callif(killed(p))exit(-1);// sepc points to the ecall instruction,// but we want to return to the next instruction.p->trapframe->epc += 4;// an interrupt will change sepc, scause, and sstatus,// so enable only now that we're done with those registers.intr_on();syscall();} else if( r_scause() == 15 ){ //lab5 页写入错误 // 写时错误uint64 va = r_stval(); // 获取触发错误的虚拟地址// 检查虚拟地址是否合法(在用户地址空间内)if (va >= p->sz || va < PGSIZE) {printf("usertrap: invalid va %p\n", va);setkilled(p);}// 处理 COW 错误(分配新页并更新页表)if (cow_alloc(p->pagetable, va) < 0) { // 调用 COW 处理函数printf("usertrap: cow_alloc failed\n");setkilled(p);}} else if((which_dev = devintr()) != 0){// ok} else {printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());setkilled(p);}if(killed(p))exit(-1);// give up the CPU if this is a timer interrupt.if(which_dev == 2)yield();usertrapret();
}
测试结果
$cowtest
simple: ok
simple: ok
three: ok
three: ok
three: ok
file: ok
ALL COW TESTS PASSED
$usertests -q
...
test sbrklast: OK
test sbrk8000: OK
test badarg: OK
ALL TESTS PASSED