MIT6.s081 2021 Lab Copy on-write

news2024/11/28 6:37:53

Implement copy-on write

背景

xv6 使用 fork() 系统调用创建子进程时,需要将父进程的地址空间进行 深拷贝 ,即将页表和实际物理空间同时进行拷贝,以实现父进程和子进程地址空间的独立性。但很多时候,如 shell 程序,fork() 通常与 exec() 搭配使用,首先使用 fork() 创建子进程,随后在子进程中使用 exec() 将指定的程序加载到当前地址空间,这样在 fork() 中进行的地址空间拷贝就白白浪费了。

本实现要求实现一个写时复制(copy-on write)的 fork() 系统调用。具体来说,在进行虚拟内存拷贝时,不直接进行物理内存的拷贝,只是将父进程的页表复制给子进程,这样子进程和父进程的每个虚拟页面都指向了同一个物理页面,当子进程需要对某个虚拟页面进行写入时,为了保证父进程和子进程之间的独立性,子进程此时将进行物理内存的分配和拷贝,再进行写入。

实现方案

根据提示,可以将上述的写时复制的思路用 异常 的方式来实现。

首先可以利用页表项的 flags 中的 RSW 位来表示页表项是否为 COW 页,以便后续的异常处理。

修改 uvmcopy() ,将物理页面的分配操作去除,只是进行页表的拷贝,并将父进程和子进程的对应页表项的 PTE_W 置 0(以便在对 COW 页进行写入时陷入内核)、PTE_COW 置 1。

修改 usertrap(),当陷入内核时,内核通过查看 scause 寄存器(见下图)以及页表项的 PTE_W 和 PTE_COW 位,识别到陷入原因是发生在 COW 页上的 store page fault(寄存器值为 15)时,进行对应的异常处理:使用 kalloc() 为其分配物理页面,并将其页表项指向的物理地址数据拷贝到新分配的物理地址下,实现物理内存的拷贝。此时由于页表映射发生了改变,需要插入新的页表项,并删除旧的页表项。在处理了 COW 异常之后,该页面将不再是一个 COW 页,因此需要将 PTE_W 置 1、PTE_COW 置 0。

请添加图片描述

为了后续实现的方便,可以将 COW 页的判断和 COW 页的异常处理分别封装为两个函数:

int iscowpage(pagetable_t pgtbl, uint64 va) {
    if (va >= MAXVA) return 0;
    pte_t *pte = walk(pgtbl, va, 0);
    if (pte == 0) return 0;
    if ((*pte & PTE_V) == 0) return 0;
    if ((*pte & PTE_U) == 0) return 0;
    return *pte & PTE_COW;
}

int cowfault(pagetable_t pagetable, uint64 va) {
    uint64 va0 = PGROUNDDOWN(va);
    pte_t* pte;
    if((pte = walk(pagetable, va0, 0)) == 0) return -1;

    uint64 flags = PTE_FLAGS(*pte);
    uint64 pa0 = PTE2PA(*pte);

    flags &= (~PTE_COW); // clear COW bit
    flags |= PTE_W;      // set write bit

    uint64 mem;
    if ((mem = (uint64)kalloc()) == 0) return -1;
    memmove((void *)mem, (void *)pa0, PGSIZE);

    // remove old PTE
    uvmunmap(pagetable, va0, 1, 1);

    // install new PTE
    if(mappages(pagetable, va0, PGSIZE, mem, flags) < 0){
        kfree((void *)mem);
        return -1;
    }
    return 0;
}

此外,还需要为每个物理页面引入 引用计数(reference count) ,页面创建时计数为 1,每次添加或移除指向该物理地址的页表项都增加或减少引用计数,当引用计数为 0 时释放该物理页面。这里有一个实现的技巧:将引用计数的减少放到 kfree() 中,在 kfree() 中根据引用计数的大小决定是否释放物理页面。

最后,也是很容易忽视的一点,修改 copyout() 以实现对 COW 页的支持。刚开始看到这个提示的时候我很疑惑,前面的工作貌似已经足够实现 COW 了,为什么还要修改 copyout?原来 xv6 对 COW 页进行写时复制都是基于 store page fault,即当尝试写入一个 PTE_W 为 0 的页面时触发异常,导致陷入内核,再由内核进行 COW 页面的异常处理,其中陷入内核的操作是由硬件自动来完成的,具体来说,是在虚实地址转换阶段由 MMU 来完成的。而 copyout() 是运行在内核态下的函数,其地址转换是由内核中的函数 walk() 来实现的,因而不会自动触发异常并交由异常处理程序来处理,而需要手动来完成。由于前面已经将 COW 页的判断和处理封装成了函数,因此对 copyout() 的修改很简单:

if (iscowpage(pagetable, va0)) {
    cowfault(pagetable, va0);
}

代码

diff --git a/kernel/defs.h b/kernel/defs.h
index 3564db4..f5a9d8d 100644
--- a/kernel/defs.h
+++ b/kernel/defs.h
@@ -63,6 +63,7 @@ void            ramdiskrw(struct buf*);
 void*           kalloc(void);
 void            kfree(void *);
 void            kinit(void);
+void            incrfcount(void*);
 
 // log.c
 void            initlog(int, struct superblock*);
@@ -145,6 +146,8 @@ void            trapinit(void);
 void            trapinithart(void);
 extern struct spinlock tickslock;
 void            usertrapret(void);
+int             iscowpage(pagetable_t, uint64);
+int             cowfault(pagetable_t, uint64);
 
 // uart.c
 void            uartinit(void);
@@ -170,6 +173,7 @@ uint64          walkaddr(pagetable_t, uint64);
 int             copyout(pagetable_t, uint64, char *, uint64);
 int             copyin(pagetable_t, char *, uint64, uint64);
 int             copyinstr(pagetable_t, char *, uint64, uint64);
+pte_t*          walk(pagetable_t, uint64, int);
 
 // plic.c
 void            plicinit(void);
diff --git a/kernel/kalloc.c b/kernel/kalloc.c
index fa6a0ac..5872b85 100644
--- a/kernel/kalloc.c
+++ b/kernel/kalloc.c
@@ -14,6 +14,11 @@ void freerange(void *pa_start, void *pa_end);
 extern char end[]; // first address after kernel.
                    // defined by kernel.ld.
 
+#define PA2RFIDX(pa) ((((uint64)pa) - KERNBASE) / PGSIZE)
+
+int rfcount[(PHYSTOP - KERNBASE) / PGSIZE];
+struct spinlock rflock;
+
 struct run {
   struct run *next;
 };
@@ -27,6 +32,7 @@ void
 kinit()
 {
   initlock(&kmem.lock, "kmem");
+  initlock(&rflock, "rflock");
   freerange(end, (void*)PHYSTOP);
 }
 
@@ -51,15 +57,17 @@ kfree(void *pa)
   if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
     panic("kfree");
 
-  // 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);
+  acquire(&rflock);
+  if(--rfcount[PA2RFIDX(pa)] <= 0){
+    memset(pa, 1, PGSIZE);
+    // Fill with junk to catch dangling refs.
+    r = (struct run*)pa;
+    acquire(&kmem.lock);
+    r->next = kmem.freelist;
+    kmem.freelist = r;
+    release(&kmem.lock);
+  }
+  release(&rflock);
 }
 
 // Allocate one 4096-byte page of physical memory.
@@ -76,7 +84,15 @@ kalloc(void)
     kmem.freelist = r->next;
   release(&kmem.lock);
 
-  if(r)
+  if(r) {
     memset((char*)r, 5, PGSIZE); // fill with junk
+    rfcount[PA2RFIDX(r)] = 1;
+  }
   return (void*)r;
 }
+
+void incrfcount(void* pa){
+  acquire(&rflock);
+  ++rfcount[PA2RFIDX(pa)];
+  release(&rflock);
+}
\ No newline at end of file
diff --git a/kernel/riscv.h b/kernel/riscv.h
index 1691faf..a6ba9e7 100644
--- a/kernel/riscv.h
+++ b/kernel/riscv.h
@@ -343,6 +343,8 @@ sfence_vma()
 #define PTE_W (1L << 2)
 #define PTE_X (1L << 3)
 #define PTE_U (1L << 4) // 1 -> user can access
+#define PTE_COW (1L << 8) // 1 -> is a COW page
+
 
 // shift a physical address to the right place for a PTE.
 #define PA2PTE(pa) ((((uint64)pa) >> 12) << 10)
diff --git a/kernel/trap.c b/kernel/trap.c
index a63249e..0fb7687 100644
--- a/kernel/trap.c
+++ b/kernel/trap.c
@@ -29,6 +29,42 @@ trapinithart(void)
   w_stvec((uint64)kernelvec);
 }
 
+
+int iscowpage(pagetable_t pgtbl, uint64 va) {
+  if (va >= MAXVA) return 0;
+  pte_t *pte = walk(pgtbl, va, 0);
+  if (pte == 0) return 0;
+  if ((*pte & PTE_V) == 0) return 0;
+  if ((*pte & PTE_U) == 0) return 0;
+  return *pte & PTE_COW;
+}
+
+int cowfault(pagetable_t pagetable, uint64 va) {
+  uint64 va0 = PGROUNDDOWN(va);
+  pte_t* pte;
+  if((pte = walk(pagetable, va0, 0)) == 0) return -1;
+  
+  uint64 flags = PTE_FLAGS(*pte);
+  uint64 pa0 = PTE2PA(*pte);
+
+  flags &= (~PTE_COW); // clear COW bit
+  flags |= PTE_W;      // set write bit
+
+  uint64 mem;
+  if ((mem = (uint64)kalloc()) == 0) return -1;
+  memmove((void *)mem, (void *)pa0, PGSIZE);
+
+  // remove old PTE
+  uvmunmap(pagetable, va0, 1, 1);
+  
+  // install new PTE
+  if(mappages(pagetable, va0, PGSIZE, mem, flags) < 0){
+    kfree((void *)mem);
+    return -1;
+  }
+  return 0;
+}
+
 //
 // handle an interrupt, exception, or system call from user space.
 // called from trampoline.S
@@ -67,7 +103,12 @@ usertrap(void)
     syscall();
   } else if((which_dev = devintr()) != 0){
     // ok
-  } else {
+  } else if (r_scause() == 15 && iscowpage(p->pagetable, r_stval())) {
+    if (cowfault(p->pagetable, r_stval()) < 0) {
+      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;
diff --git a/kernel/vm.c b/kernel/vm.c
index d5a12a0..df0ddde 100644
--- a/kernel/vm.c
+++ b/kernel/vm.c
@@ -303,22 +303,20 @@ uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
   pte_t *pte;
   uint64 pa, i;
   uint flags;
-  char *mem;
 
   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");
+    *pte &= ~PTE_W;   // set write bit
+    *pte |= PTE_COW;  // clear COW bit
     pa = PTE2PA(*pte);
     flags = PTE_FLAGS(*pte);
-    if((mem = kalloc()) == 0)
-      goto err;
-    memmove(mem, (char*)pa, PGSIZE);
-    if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
-      kfree(mem);
+    if(mappages(new, i, PGSIZE, pa, flags) != 0){
       goto err;
     }
+    incrfcount((void*)pa); // increment reference count to pa
   }
   return 0;
 
@@ -350,6 +348,9 @@ copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
 
   while(len > 0){
     va0 = PGROUNDDOWN(dstva);
+    if (iscowpage(pagetable, va0)) {
+      cowfault(pagetable, va0);
+    }
     pa0 = walkaddr(pagetable, va0);
     if(pa0 == 0)
       return -1;
diff --git a/time.txt b/time.txt
new file mode 100644
index 0000000..209e3ef
--- /dev/null
+++ b/time.txt
@@ -0,0 +1 @@
+20

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2034857.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

时间线编辑特效SDK解决方案,高效的接入服务

在数字时代的浪潮中&#xff0c;短视频以其独特的魅力迅速成为连接世界的新语言&#xff0c;而创意与技术的深度融合&#xff0c;则是这场视觉盛宴背后的核心驱动力。美摄科技&#xff0c;作为短视频技术领域的佼佼者&#xff0c;近期隆重推出了其革命性的时间线编辑特效SDK解决…

踩坑【已解决】:Redis配置主从复制踩到的坑,主机不显示从机的连接信息,但是从机显示主机信息

关于Redis配置主从复制踩到的坑&#xff01;&#xff01;&#xff01; 设置单机集群的时候&#xff0c;两台从机都显示连接到主机&#xff0c;但是主机显示连接到的从机数量为0: 原因&#xff1a; 在master的配置文件中我们配置了密码的信息&#xff0c;但是我们在从配置文件…

gitee 使用安装教程

一、下载方式 1.官网下载https://git-scm.com/downloads 2.淘宝镜像下载https://registry.npmmirror.com/binary.html?pathgit-for-windows/ 2.1安装 1.点击刚刚下载的安装包&#xff0c;然后点击next 2.根据自己的情况选择&#xff0c;一般默认就可以了 3. 点击next 4.点…

PS DRAM接口的函数式编程模型(一)

针对PS DRAM接口的操作启动&#xff0c;确实需要遵循一系列精心设计的步骤来确保DRAM控制器&#xff08;DDRC&#xff09;和DRAM模块能够正确初始化和配置。以下步骤详细说明了这些操作&#xff0c;并指出了Vivado Design Suite通常如何提供这些编程支持。 DDR时钟初始化&…

1DCNN-2DResNet并行故障诊断模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Python轴承故障诊断入门教学-CSDN博客 Python轴承故障诊断 (13)基于故障信号特征提取的超强机器学习识别模型-CSDN博客 Python轴承故障诊断 (14)高创新故障识别模型-CSDN…

机器学习入门(六):分类模型评估方法

目录 1. 数据集划分 1.1 为什么要划分数据集? 1.2 数据集划分的方法 1.3 留出法(简单交叉验证) 1.4 交叉验证法 1.5 留一法 1.6 自助法 2. 分类算法的评估标准 2.1 分类算法的评估 2.2 SKlearn中模型评估API介绍 3. 小结 前言 掌握分类模型评估方法对于数据科学家…

测试流程规范--准入准出规则

简介&#xff1a; 为了加强测试部软件测试的质量控制及与测试相关部门、人员更好理解测试各阶段的准入/准出条件而建立的准入/准出规范。 一&#xff0c;目的 为了加强测试部软件测试的质量控制及与测试相关部门、人员更好理解测试各阶段的准入/准出条件而建立的准入/准出规范…

【python】在Python中操作MongoDB的详细用法教程与实战案例分享

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

《黑神话:悟空 性能测试工具》Steam已免费开放下载

《黑神话&#xff1a;悟空》将于8月20日上午10点正式解锁&#xff0c;届时大家就能体验到这款期待三年的国产3A之光了。为了方便大家游玩&#xff0c;官方提前上线了性能测试工具&#xff0c;目前《黑神话&#xff1a;悟空 性能测试工具》已在Steam商店开放免费下载。 据Steam评…

Qt | QSQLite内存数据库增删改查

点击上方"蓝字"关注我们 01、演示 参数随便设置 查询 修改 右键菜单是重点 手动提交,点击Submit All

2024下半年软考报考攻略:从报名到拿证全流程,看这一篇就够了!

下半年软考本月就要开启报名了&#xff0c;有一些同学可能是第一次参加&#xff0c;还不清楚具体流程。今天小编就来为大家总结软考从报名到拿证的全过程指南&#xff0c;有需要的可以收藏这篇攻略哦&#xff01; 一、报名流程 1、登录网站 登录中国计算机技术职业资格网&…

C++结构体指针强制转换以处理电力系统IEC103报文

前言 最近依旧是开发规约解析工具的103篇&#xff0c;已经完成了通用分类服务部分的解析&#xff0c;现在着手开始搞扰动数据传输&#xff0c;也就是故障录波的传输。 在103故障录波&#xff08;扰动数据&#xff09;的报文中&#xff0c;数据是一个数据集一个数据集地存放&a…

如何有效优化无线双模蓝牙模块的通讯距离?

许多客户在使用无线双模蓝牙模块时发现传输距离达不到预期要求。影响无线蓝牙模块通讯距离的因素有很多&#xff0c;以下是美迅物联网MesoonRF归纳总结的一些可能的原因以及如何改善&#xff1a; 1.优化天线设计 天线是影响无线双模蓝牙模块通讯距离的关键因素之一&#xff0c;…

IPC进程间通信

信号 信号是一种终端机制&#xff0c;程序运行到一半的时候接收到了某种通知&#xff0c;程序就会立刻中断运行&#xff0c;转而去处理通知。 登记信号 一个进程只会接收默认的几个信号 如果想要让一个进程接收特定信号的话&#xff0c;必须提前在该进程中登记一下想要接收…

一次了解所有功能!超详细【Stable Diffusion界面】大揭秘!

对于AI绘画的初学者而言&#xff0c;一看到SD的UI界面肯定是一脸懵&#xff0c;因为有太多陌生词汇&#xff0c;什么大模型、什么提示词、什么什么采样迭代&#xff0c;和传统的画图方式完全不在一个层面上&#xff0c;学习起来就无从下手&#xff5e; 今天小元老师就给大家详…

运维高级内容--lvs按权重值轮询调度

创建5台主机(一些配置是基于实验一的基础)&#xff1a; 客户端client 172.25.254.200路由器route 172.25.254.100 192.168.0.100 &#xff08;需要eth0、eth1两个网关&#xff09;LVS 192.168.0.50webserver1 192.168.0.10webserver2 192.168.0.20 1.LVS主机&#xff1a; vim…

DataGrip安装与MySQL连接

DataGrip安装 官网&#xff1a;DataGrip: The Cross-Platform IDE for Databases & SQL by JetBrains 点击页面中的【Download】进入下载界面&#xff0c;如下图所示&#xff1a; 根据自己的电脑系统选择不同的版本【windows/macOS/linux】&#xff0c;点击【.exe】可选择…

MySQL(一)——初识数据库(概念、数据类型、基本表库操作)

文章目录 初识数据库数据库相关基本概念数据库的分类 MySQL数据库数据模型基本操作库操作查看数据库创建数据库删除数据库选中数据库 数据类型数值类型字符串类型日期类型 表操作查看所有表查看表结构创建表删除表 其他操作查看警告信息查看编码集 这是我们 MySQL 学习的第一程…

GD32E503实现串口中断收发功能

如有技术问题及技术需求请加作者微信! 源码下载链接:代码下载 亲测可用实现GD32E503库函数串口数据收发功能: #include "gd32e50x.h" #include "gd32e503v_eval.h" #include "systick.h" #include <stdio.h> #include "user_uart…

JVM知识总结(性能调优)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 性能调优 何时进行JVM调优&#xff1f; 遇到以下情况&#xff0c…