MIT6.S081 Lab 5 COW
Lab 5 COW
思路
修改uvmcopy()建立与父进程相同的映射,记得清楚PTE_W
修改usertrap()识别页面错误,如果错误发生在COW页面上,分配页面副本,修改映射,权限设置可写
确保PTE不被引用时再释放。给每个物理页一个ref count。当被kalloc()初始化分配时设为1。当fork建立一个子进程共享页面时ref+1,当一个进程从页表丢弃该PTE时ref-1。只有当ref==0时才kfree()。
可以把每个页面的count保存在固定大小的数组内?你必须就如何索引数组以及如何选择数组大小制定一个方案。例如,你可以用页面的物理地址除以 4096 来为数组建立索引,并赋予数组与 kalloc.c 中 kinit() 放在空闲列表中的任何页面的最高物理地址相等的元素数。
修改 copyout() 以在遇到 COW 页面时使用与页面故障相同的方案
提示
- 通过 "懒页面分配 "实验,你可能已经熟悉了许多与写时复制相关的 xv6 内核代码。但是,您不应该将本实验建立在您的懒分配解决方案的基础上;相反,请按照上面的指示,从一个全新的 xv6 拷贝开始。
- 记录每个 PTE 是否为 COW 映射的方法可能很有用。为此,可以使用 RISC-V PTE 中的 RSW(为软件保留)位。
- usertests 探索了 cowtest 没有测试的场景,因此不要忘记检查两者的所有测试是否都通过。
- 在 kernel/riscv.h 的末尾有一些有用的宏和页表标志定义。
- 如果发生 COW 页故障,且没有可用内存,则应杀死进程。
实验
建立相同映射
定义COW位:#define PTE_COW (1L << 8) // 1-> is a COW page
fork调用uvmcopy复制父进程,修改ucmcopy,flags设置PTE_COW清除PTE_W,mappages建立映射时选择与父进程相同的物理地址。最后修改父进程自己的pte权限,调用incrementref(pa)增加物理页的引用
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){
// check
if((pte = walk(old, i, 0)) == 0)
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
panic("uvmcopy: page not present");
// set flags
flags = PTE_FLAGS(*pte);
flags |= PTE_COW; // is a COW page
flags &= ~PTE_W; // unwritable
// map
pa = PTE2PA(*pte);
if(mappages(new, i, PGSIZE, pa, flags) != 0){ // map in the same pa
goto err;
}
// if map success set father proc's flags
// it is also ok to set before, but need to restore when map failed
*pte |= PTE_COW;
*pte &= ~PTE_W;
if(incrementref(pa) < 0) // ref++
panic("increment ref failed");
}
return 0;
err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}
reference count
定义数组,由于rsicv的地址都是从KERNBASE开始定义的,所以数组索引如下:
int ref[32768] = {0}; // reference count array
#define INDEX(p) ((p-KERNBASE)/4096)
kalloc中初始化为1
void *
kalloc(void)
{
struct run *r;
acquire(&kmem.lock);
r = kmem.freelist;
if(r){
kmem.freelist = r->next;
uint64 index = INDEX((uint64)r);
if(ref[index] != 0)
panic("kalloc panic");
ref[index] = 1;
}
release(&kmem.lock);
if(r){
memset((char*)r, 5, PGSIZE); // fill with junk
}
return (void*)r;
}
increamentref是对外提供的接口,负责增加pa物理页的引用数
int incrementref(uint64 pa){
if(pa > KERNBASE && pa < PHYSTOP){
if(ref[INDEX(pa)] < 65535){ // max ref count by myself
acquire(&kmem.lock);
ref[INDEX(pa)]++;
release(&kmem.lock);
return 0;
}
else
return -1;
}
else{
return -1;
}
}
解除映射调用kfree,kfree使ref-1,当且仅当ref==1时才进行释放
void
kfree(void *pa)
{
struct run *r;
if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree");
// check ref, ref--. then if ref == 0, go down dofree
acquire(&kmem.lock);
uint64 index = INDEX((uint64)pa);
if(ref[index] < 1)
panic("kfree panic");
ref[index]--;
if(ref[index] > 0){
release(&kmem.lock);
return;
}
release(&kmem.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);
}
由于启动xv6时会调用freerange初始化,此时还没调用kfree设置引用数为1,因此kfree会报错,修改freerange,先将ref[]每个元素设为1再调用kfree释放
void
freerange(void *pa_start, void *pa_end)
{
char *p;
p = (char*)PGROUNDUP((uint64)pa_start);
for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE){
ref[INDEX((uint64)p)] = 1;
kfree(p);
}
}
页面异常处理
写入异常的cause代码号
问题1:我必须知道写入页面引发异常的编号才能在usertrap中处理
查阅RISC-V手册,确定异常编号为15
4.1.8 Supervisor Cause Register (scause)
获取引发页面异常的映射
问题2:我需要获取异常页面的va和pa才能复制pa页的副本并重建映射
可以使用stval寄存器
手册 4.1.9 Supervisor Trap Value (stval) Register
If stval is written with a nonzero value when a misaligned load or store causes an access-fault or
page-fault exception, then stval will contain the virtual address of the portion of the access that
caused the fault.
也就是说stval寄存器会保存异常页面的虚拟地址va,通过walk(va)得到pte,通过PTE2PA得到pa
综上设计如下:识别页面故障,获取va,传入处理函数处理
else if(r_scause() == 15){
// store/AMO page fault
if(p->killed)
exit(-1);
uint64 fault_va = r_stval();
if(handle_store_pagefault(p->pagetable, PGROUNDDOWN(fault_va)) < 0)
p->killed = 1;
}
处理页面异常函数:
处理函数中,kalloc分配新的一页,memmove复制旧物理页内容到新分配的物理页中。使用PTE_FLAGS(*pte)获取flag,清除COW位修改为可写。直接用新pa和修改号的flag组合成新的pte。最后调用kfree使旧物理页的ref-1
int handle_store_pagefault(pagetable_t pagetable, uint64 va){
uint64 pa;
uint64 newpa;
pte_t *pte;
if((va % PGSIZE) != 0)
panic("uvmunmap: not aligned");
if((pte = walk(pagetable, va, 0)) == 0) // walk will check va
panic("uvmcopy: pte should exist");
if((*pte & PTE_V) == 0)
panic("uvmcopy: page not present");
if((*pte & PTE_U) == 0)
panic("uvmcopy: not a user page");
pa = PTE2PA(*pte);
// alloc new page and copy
if((newpa = (uint64)kalloc()) == 0){
printf("no mem for a new page\n");
return -1;
}
memmove((void*)newpa, (void*)pa, PGSIZE);
//set PTE_W, clear PTE_COW, save flags
uint flags = PTE_FLAGS(*pte);
flags |= PTE_W;
flags &= ~PTE_COW;
*pte = PA2PTE(newpa) | flags; // modify pte directly
kfree((void*)pa); // kfree will ref-- then free the pa when ref == 0
return 0;
}
一些犯下的错误:
(uint64*)newpa = kalloc(); (wrong)
newpa = (uint64)kalloc(); (wright)
uvmunmap(pagetable, va, 1, 0);引起了panic: uvmunmap: not aligned
在传入handle_store_pagefault时对va进行向下对齐
copyout
usertests中copyout出错,引发错误的原因:
copyout使用walkaddr获取va0的物理地址,然后写入数据。如果要写入的页是cow fork页,就会引发了页面异常,处理函数分配新的页、修改pte,然而旧的pa对应的页依旧是不可写状态
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
uint64 n, va0, pa0;
while(len > 0){
va0 = PGROUNDDOWN(dstva);
pa0 = walkaddr(pagetable, va0); // get dstva's physical adress
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;
}
解决方法:如果发现不可写则调用handle_store_pagefault,随后更新pa0
回过头来看为了防止用户使用copyout写入一些不可写的页,handle_store_pagefault应该增加PTE_COW检测
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);
if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0)
return -1;
if((*pte & PTE_W) == 0) // dst is unwritable
if(handle_store_pagefault(pagetable, va0) < 0)
return -1;
pa0 = PTE2PA(*pte);
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;
}
全部测试通过:

浙公网安备 33010602011771号