xv6 lab5 cow
21年好像没有懒分配,所以20年的lab6就成了21年的lab5
# cow实现 主要思想是增加一个标志位,一个引用标记。在中断处理时,进行懒复制,在write出错时再进行实际分配处理。
首先改uvmcopy,改原来标志位,将新的页也映射之前页的物理地址。
//kernel/riscv.h
//增加cow定义
define PTE_COW (1L << 8)
//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");
pa = PTE2PA(*pte);
// 清除父进程的 PTE_W 标志位,设置 PTE_COW 标志位表示是一个懒复制页(多个进程引用同个物理页)
*pte = (*pte & ~PTE_W) | PTE_COW;
flags = PTE_FLAGS(*pte);
// 将父进程的物理页直接 map 到子进程 (懒复制)
// 权限设置和父进程一致(不可写,PTE_COW)
if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
goto err;
}
// 将物理页的引用次数增加 1
krefpage((void*)pa);
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
判断中断,当是写中断,并且页可用且COW标志位有值时就是COW模式。且要保证没有超出原来的大小。
//kernel/trap.c
extern pte_t* walk(pagetable_t, uint64, int);
//中断判断
void
usertrap(void)
{
...
} else if((which_dev = devintr()) != 0){
// ok
} else if((r_scause() == 15) && uvmcheckcowpage(r_stval())) { // copy-on-write
if(uvmcowcopy(r_stval()) == -1){ // 如果内存不足,则杀死进程
p->killed = 1;
}
} else {
printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
printf(" sepc=%p stval=%p\n", r_sepc(), r_stval());
p->killed = 1;
}
...
}
// 这里没有放在vm中,因为vm中没有声明 proc 不想再整import,就放在这里了。记得walk要声明一下
// 检查一个地址指向的页是否是懒复制页
int uvmcheckcowpage(uint64 va) {
pte_t *pte;
struct proc *p = myproc();
return va < p->sz // 在进程内存范围内
&& ((pte = walk(p->pagetable, va, 0))!=0)
&& (*pte & PTE_V) // 页表项存在
&& (*pte & PTE_COW); // 页是一个懒复制页
}
// 实复制一个懒复制页,并重新映射为可写
int uvmcowcopy(uint64 va) {
pte_t *pte;
struct proc *p = myproc();
if((pte = walk(p->pagetable, va, 0)) == 0)
panic("uvmcowcopy: walk");
// 调用 kalloc.c 中的 kcopy_n_deref 方法,复制页
// (如果懒复制页的引用已经为 1,则不需要重新分配和复制内存页,只需清除 PTE_COW 标记并标记 PTE_W 即可)
uint64 pa = PTE2PA(*pte);
uint64 new = (uint64)kcopy_n_deref((void*)pa); // 将一个懒复制的页引用变为一个实复制的页
if(new == 0)
return -1;
// 重新映射为可写,并清除 PTE_COW 标记
uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;
uvmunmap(p->pagetable, PGROUNDDOWN(va), 1, 0);
if(mappages(p->pagetable, va, 1, new, flags) == -1) {
panic("uvmcowcopy: mappages");
}
return 0;
}
在分配时要注意引用,如果引用为0了,就可以free了。如果不为0,就跳过。记得加锁,这种多线程没有锁是不可能的。
//kernel/kalloc.c
// 用于访问物理页引用计数数组
#define PA2PGREF_ID(p) (((p)-KERNBASE)/PGSIZE)
#define PGREF_MAX_ENTRIES PA2PGREF_ID(PHYSTOP)
struct spinlock pgreflock; // 用于 pageref 数组的锁,防止竞态条件引起内存泄漏
int pageref[PGREF_MAX_ENTRIES + 1]; // 从 KERNBASE 开始到 PHYSTOP 之间的每个物理页的
#define PA2PGREF(p) pageref[PA2PGREF_ID((uint64)(p))]
void
kfree(void *pa)
{
struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");
acquire(&pgreflock);
if(--PA2PGREF(pa) <= 0) {
// 当页面的引用计数小于等于 0 的时候,释放页面
// Fill with junk to catch dangling refs.
// pa will be memset multiple times if race-condition occurred.
memset(pa, 1, PGSIZE);
r = (struct run*)pa;
acquire(&kmem.lock);
r->next = kmem.freelist;
kmem.freelist = r;
release(&kmem.lock);
}
release(&pgreflock);
}
void *
kalloc(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
if(r)
kmem.freelist = r->next;
release(&kmem.lock);
if(r)
{
memset((char*)r, 5, PGSIZE); // fill with junk
PA2PGREF(r) = 1;
}
return (void*)r;
}
void *kcopy_n_deref(void *pa) {
acquire(&pgreflock);
if(PA2PGREF(pa) <= 1) { // 只有 1 个引用,无需复制
release(&pgreflock);
return pa;
}
// 分配新的内存页,并复制旧页中的数据到新页
uint64 newpa = (uint64)kalloc();
if(newpa == 0) {
release(&pgreflock);
return 0; // out of memory
}
memmove((void*)newpa, (void*)pa, PGSIZE);
// 旧页的引用减 1
PA2PGREF(pa)--;
release(&pgreflock);
return (void*)newpa;
}
// 为 pa 的引用计数增加 1
void krefpage(void *pa) {
acquire(&pgreflock);
PA2PGREF(pa)++;
release(&pgreflock);
}
一点心得
这里测试会报错,只要不是panic不运行就可以了。可能会出现进程的错误,如果看到了,就看看自己的代码有哪里错了,比如,if块中的内容有两行以上,忘了加大括号。或者r_scause() 写成r_sstatus()。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!