一些有用的宏和页表标志的定义在
kernel/riscv.h
步骤一:修改fork()为写时复制机制
修改uvmcopy():查找到parent的物理内存,然后令child的pte也指向这块物理内存,而不是分配新的页面。对child和parent都要清除PTE_W(变成只读)
// uvmcopy
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
pte_t *pte;
uint64 pa, i;
uint flags;
// old-parent; new-child
// find pa corresponding to parent'pte
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); // parent's
flags = PTE_FLAGS(*pte); // parent's
// change parent's PTE->only read, not write
flags |= PTE_R;
flags &= ~PTE_W;
// don't need to copy pa
// memmove(mem, (char*)pa, PGSIZE);
// only copy page table, create map
if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
kfree(mem);
goto err;
}
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
into:解决计数和PTE标志位问题
-
page fault时, kernel page fault handler如何确认出错原因是不是由cow引起? ——添加表示COW的PTE位,然后在usertrap()中检测pte的bit 8。如果此位为1,即cow引起的。
#define PTE_COW (1L << 8) // 1->cow fault
- 先在
uvmcopy()中添加代码,设置parent和child的PTE_COW为1
flags |= PTE_COW;
if(mappages(...))
- 然后添加判断是否为cow fault的函数
/* vm.c */
/* 相比原来增加了一些新的特殊情况检测--这里类lab5*/
uint64 cow_check(uint64 va){
// va是传进来的发生page fault的地址--需要页对齐
va = PGROUNDUP(va);
struct proc* p = myproc();
pte_t* pte = walk(p->pagetable, va, 1);
return va < p->sz && (pte != 0) && (*pte & PTE_V) && ((*pte & PTE_COW) != 0);
}
- 关于物理内存的引用计数如何保存?
---使用数组。
如何索引数组:使用页面的物理地址/PGSIZE来索引数组。
如何选择其大小:kalloc.c中kinit()--PHYSTOP
/* kalloc.c */
struct spinlock pgreflock; // 用于 pageref 数组的锁,防止竞态条件引起内存泄漏
#define PA2PGRE_ID(p) (((p)-KERNBASE)/PGSIZE)
#define PGREF_MAX PA2PGREF_ID(PHYSTOP)
int p_refered[PGREF_MAX]; // 引用计数数组
// 获得引用计数
#define PA2PGREF(p) p_refered[PA2PGREF_ID((uint64)(p))]
- 修改kinit来初始化锁
/* kalloc.c */
void kinit()
{
initlock(&pgreflock,"pgref"); // 初始化锁
initlock(&kmem.lock, "kmem");
freerange(end, (void*)PHYSTOP);
}
- kfree: 只有当计数为0时,才能释放这块内存
/* kalloc.c */
// 内存释放的时候调用freewalk,freewalk里又调用了kfree
// 添加一个判断数组计数是否为0
acquire(&pgreflock);
if(--PA2PGREF(pa) <= 0){
// 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);
}
release(&pgreflock);
- 进程在
kalloc的时候会让这块新内存的地址计数变为1;
// kalloc()
if(r){
memset((char*)r, 5, PGSIZE); // fill with junk
PA2PGREF(r) = 1;
}
- 添加两个函数:一个是增加计数;一个是减少计数
void *kcopy_n_deref(void *pa) {
acquire(&pgreflock);
// 只有 1 个引用,无需复制--这里应该对应的是如果是parent页面有page fault时,且childs都有自己的物理页面了
if(PA2PGREF(pa) <= 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);
}
步骤二:实现page fault里的处理函数(分配新内存)
cow_alloc时减少计数
/* 原来在cow_alloc里面进行的分配页面操作和减少计数操作在kcopy_n_deref()中完成,这里是实现了重新映射*/
void cow_alloc(uint64 va)
struct proc* p = myproc();
pte_t* pte;
uint64 pa;
if((pte = walk(p->pagetable, va, 0)) == 0){
panic("cow_alloc: walk\n");
}
pa = PTE2PA(*pte);
// 减少对旧页的映射,计数-1
uint64 new = (uint64)kcopy_n_deref((void*)pa);
if(new == 0){
p->killed = 1;
}
// 将child的PTE从旧页变成新页
// 先取消对原来旧页的映射再映射新页的
// 不用do_free;
uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;
uvmunmap(p->pagetable, PGROUNDDOWN(va), 1, 0);
if(mappages(p->pagetable, va, PGSIZE, new, flags) != 0){
printf("cowalloc: fail to mappages\n");
p->killed = 1;
}
}
cow时要添加计数
// uvmcopy()--增加计数
krefpage((void*)pa);
步骤三:修改usertrap()和copyout()
else if(r_scause() == 13 || r_scause() == 15){
uint64 va = r_stval();
if(cow_check(va)){
cow_alloc(va);
}
}
遇到的bug以及解决方法
(错误记录是之前写lab的时候记下的,可能有一些不准确or解释得不好)
- 可能内存泄漏导致无法启动
发现了uvmcopy里的错误并修改:
flags &= ~PTE_W; // not change pte
flags |= PTE_COW;
修改后
if(*pte & PTE_W){
*pte = (*pte & ~PTE_W) | PTE_COW;
}
flags = PTE_FLAGS(*pte);
-
sbrk failed: cow_alloc的mappages里的映射改为1
-
copyout failed: panic walk-->猜测是因为特殊情况:va >= MAXVA引起的(原来是在walk()之后才检测的话就没起到作用),所以需要先检测位置是否正常再调用walk()查找:
uint64 cow_check(uint64 va){
struct proc* p = myproc();
if(va >= MAXVA) return 0;
pte_t* pte = walk(p->pagetable, va, 0);
return va < p->sz && (pte != 0) && (*pte & PTE_V) && (*pte & PTE_COW); // 1-cow page
}
修改之后成功