代码在github上。总体来说如果理解了COW机制的话,这个实验的完成也没有很复杂。

这一个实验是要完成COW(copy on write)fork。在原始的XV6中,fork函数是通过直接对进程的地址空间完整地复制一份来实现的。但是,拷贝整个地址空间是十分耗时的,并且在很多情况下,程序立即调用exec函数来替换掉地址空间,导致fork做了很多无用功。即使不调用exec函数,父进程和子进程的代码段等只读段也是可以共享的,从而达到节省内存空间的目的。同时COW也可以将地址空间拷贝的耗时进行延迟分散,提高操作系统的效率。

首先就是要对fork函数进行修改,使其不对地址空间进行拷贝。fork函数会调用uvmcopy进行拷贝,因此只需要修改uvmcopy函数就可以了:删去uvmcopy中的kalloc函数,将父子进程页面的页表项都设置为不可写,并设置COW标志位(在页表项中保留了2位给操作系统,这里用的是第8位#define PTE_COW (1L << 8)

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); *pte = ((*pte) & (~PTE_W)) | PTE_COW; // set parent's page unwritable
// printf("c: %p %p %p\n", i, ((flags & (~PTE_W)) | PTE_COW), *pte);
// map child's page with page unwritable
if(mappages(new, i, PGSIZE, (uint64)pa, (flags & (~PTE_W)) | PTE_COW) != 0){
goto err;
}
refcnt_incr(pa, 1);
}
return 0; err:
uvmunmap(new, 0, i / PGSIZE, 1);
return -1;
}

之后设置一个数组用于保存内存页面的引用计数,由于会涉及到并行的问题,因此也需要设置一个锁,同时定义了一些辅助函数:

struct {
struct spinlock lock;
uint counter[(PHYSTOP - KERNBASE) / PGSIZE];
} refcnt; inline
uint64
pgindex(uint64 pa){
return (pa - KERNBASE) / PGSIZE;
} inline
void
acquire_refcnt(){
acquire(&refcnt.lock);
} inline
void
release_refcnt(){
release(&refcnt.lock);
} void
refcnt_setter(uint64 pa, int n){
refcnt.counter[pgindex((uint64)pa)] = n;
} inline
uint
refcnt_getter(uint64 pa){
return refcnt.counter[pgindex(pa)];
} void
refcnt_incr(uint64 pa, int n){
acquire(&refcnt.lock);
refcnt.counter[pgindex(pa)] += n;
release(&refcnt.lock);
}

修改kfree函数,使其只有在引用计数为1的时候释放页面,其他时候就只减少引用计数:

void
kfree(void *pa)
{
struct run *r; // page with refcnt > 1 should not be freed
acquire_refcnt();
if(refcnt.counter[pgindex((uint64)pa)] > 1){
refcnt.counter[pgindex((uint64)pa)] -= 1;
release_refcnt();
return;
} if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
panic("kfree"); // Fill with junk to catch dangling refs.
memset(pa, 1, PGSIZE);
refcnt.counter[pgindex((uint64)pa)] = 0;
release_refcnt(); r = (struct run*)pa; acquire(&kmem.lock);
r->next = kmem.freelist;
kmem.freelist = r;
release(&kmem.lock);
}

修改kalloc函数,使其在分配页面时将引用计数也设置为1:这里注意要判断r是否为0,kalloc实现时没有当r==0时就返回。

void *
kalloc(void)
{
...
if(r)
memset((char*)r, 5, PGSIZE); // fill with junk if(r)
refcnt_incr((uint64)r, 1); // set refcnt to 1
return (void*)r;
}

usertrap中加入判断语句,这里只需要处理scause==15的情况,因为13是页面读错误,而COW是不会引起读错误的。

void
usertrap(void)
{
...
} else if(r_scause() == 15){
// page write fault
uint64 va = r_stval();
if(cowcopy(va) == -1){
p->killed = 1;
}
} else if((which_dev = devintr()) != 0){
...
}

cowcopy函数中先判断COW标志位,当该页面是COW页面时,就可以根据引用计数来进行处理。如果计数大于1,那么就需要通过kalloc申请一个新页面,然后拷贝内容,之后对该页面进行映射,映射的时候清除COW标志位,设置PTE_W标志位;而如果引用计数等于1,那么就不需要申请新页面,只需要对这个页面的标志位进行修改就可以了:


int
cowcopy(uint64 va){
va = PGROUNDDOWN(va);
pagetable_t p = myproc()->pagetable;
pte_t* pte = walk(p, va, 0);
uint64 pa = PTE2PA(*pte);
uint flags = PTE_FLAGS(*pte); if(!(flags & PTE_COW)){
printf("not cow\n");
return -2; // not cow page
} acquire_refcnt();
uint ref = refcnt_getter(pa);
if(ref > 1){
// ref > 1, alloc a new page
char* mem = kalloc_nolock();
if(mem == 0)
goto bad;
memmove(mem, (char*)pa, PGSIZE);
if(mappages(p, va, PGSIZE, (uint64)mem, (flags & (~PTE_COW)) | PTE_W) != 0){
kfree(mem);
goto bad;
}
refcnt_setter(pa, ref - 1);
}else{
// ref = 1, use this page directly
*pte = ((*pte) & (~PTE_COW)) | PTE_W;
}
release_refcnt();
return 0; bad:
release_refcnt();
return -1;
}

在对引用计数进行读写时注意锁的设置。在mappages函数中会触发一个remap的panic,这里只要注释掉就行了,因为COW就是要对页面进行重新映射的。

XV6学习(9)Lab cow: Copy-on-write fork的更多相关文章

  1. xv6学习笔记(4) : 进程调度

    xv6学习笔记(4) : 进程 xv6所有程序都是单进程.单线程程序.要明白这个概念才好继续往下看 1. XV6中进程相关的数据结构 在XV6中,与进程有关的数据结构如下 // Per-process ...

  2. XV6学习笔记(2) :内存管理

    XV6学习笔记(2) :内存管理 在学习笔记1中,完成了对于pc启动和加载的过程.目前已经可以开始在c语言代码中运行了,而当前已经开启了分页模式,不过是两个4mb的大的内存页,而没有开启小的内存页.接 ...

  3. xv6学习笔记(3):中断处理和系统调用

    xv6学习笔记(3):中断处理和系统调用 1. tvinit函数 这个函数位于main函数内 表明了就是设置idt表 void tvinit(void) { int i; for(i = 0; i & ...

  4. xv6学习笔记(5) : 锁与管道与多cpu

    xv6学习笔记(5) : 锁与管道与多cpu 1. xv6锁结构 1. xv6操作系统要求在内核临界区操作时中断必须关闭. 如果此时中断开启,那么可能会出现以下死锁情况: 进程A在内核态运行并拿下了p ...

  5. XV6学习笔记(1) : 启动与加载

    XV6学习笔记(1) 1. 启动与加载 首先我们先来分析pc的启动.其实这个都是老生常谈了,但是还是很重要的(也不知道面试官考不考这玩意), 1. 启动的第一件事-bios 首先启动的第一件事就是运行 ...

  6. XV6学习(2)Lab syscall

    实验的代码放在了Github上. 第二个实验是Lab: system calls. 这个实验主要就是自己实现几个简单的系统调用并添加到XV6中. XV6系统调用 添加系统调用主要有以下几步: 在use ...

  7. XV6学习(1) Lab util

    正在学习MIT的6.S081,把做的实验写一写吧. 实验的代码放在了Github上. 第一个实验是Lab util,算是一个热身的实验,没有涉及到系统的底层,就是使用系统调用来完成几个用户模式的小程序 ...

  8. XV6学习(16)Lab net: Network stack

    最后一个实验了,代码在Github上. 这一个实验其实挺简单的,就是要实现网卡的e1000_transmit和e1000_recv函数.不过看以前的实验好像还要实现上层socket相关的代码,今年就只 ...

  9. XV6学习(11)Lab thread: Multithreading

    代码放在github上. 这一次实验感觉挺简单的,特别是后面两个小实验.主要就是对多线程和锁进行一个学习. Uthread: switching between threads 这一个实验是要实现一个 ...

随机推荐

  1. MM-合作伙伴确定过程

    第一步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义合作伙伴角色. 第二步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义每个科目组适合的合作伙伴角色. 第三步: ...

  2. CentOS安装TensorFlow

    1.升级python 系统自带的python是2.6,不能用,升级到2.7,方法见:http://www.cnblogs.com/stAr-1/p/9055980.html 2.升级python带来的 ...

  3. Spark MLlib中KMeans聚类算法的解析和应用

    聚类算法是机器学习中的一种无监督学习算法,它在数据科学领域应用场景很广泛,比如基于用户购买行为.兴趣等来构建推荐系统. 核心思想可以理解为,在给定的数据集中(数据集中的每个元素有可被观察的n个属性), ...

  4. Beta冲刺——第六天

    这个作业属于哪个课程 https://edu.cnblogs.com/campus/fzzcxy/2018SE1 这个作业要求在哪里 https://edu.cnblogs.com/campus/fz ...

  5. 聊聊并发,进程通信方式,go协程简单应用场景

    开篇提问 知道并发,并行,线程,协程概念吗?或者知道大概含义吗? 有线程为什么还要有协程?区别是什么? 『进程』通信方式知道几种?有没有超过3种? golang『协程』通信方式推荐? 使用并发的目的是 ...

  6. 使用ImmutableMap简化语句

    项目实战 最近接了一个出行权益的需求,回调的状态有十几种,需要转换为进行中,取消,已完成几种状态进行订单状态的展示,使用ImmutableMap可以简化语句,替代使用if-else 语句或者switc ...

  7. 让微信小程序开发如鱼得水

      关于微信小程序开发一直想写一篇相关的文章总结和记录下,结果拖延症犯了迟迟没有下笔:这不最近天气不错,于是找一个空闲的下午将这篇文章输出下(好像跟天气没啥关系),那我们就开始吧! 注意:本文默认开发 ...

  8. Solon rpc 之 SocketD 协议 - 消息订阅模式

    Solon rpc 之 SocketD 协议系列 Solon rpc 之 SocketD 协议 - 概述 Solon rpc 之 SocketD 协议 - 消息上报模式 Solon rpc 之 Soc ...

  9. 新来的运维这样用HDFS,CIO都懵了···

    摘要:本文主要研究了HDFS文件系统的读写流程以及基于MRS在windows客户端下读写HDFS文件的实现. HDFS(Hadoop分布式文件系统)是Apache Hadoop项目的一个子项目. HD ...

  10. 【剑指 Offer】08.二叉树的下一个节点

    题目描述 给定一颗二叉树和其中的一个节点,找出中序遍历序列的下一个节点.树中的节点除了有两个分别指向左右节点的指针,还有一个指向父节点的指针. Java public class Solution08 ...