MIT 6.S081 2020 Lab6 Copy-on-Write Fork for xv6 个人全流程

news2025/7/21 2:43:18

文章目录

    • 零、写在前面
    • 一、Implement copy-on write
      • 1.1 说明
      • 1.2 实现
        • 1.2.1 延迟复制与释放
        • 1.2.2 写时复制


零、写在前面

可以阅读下 《xv6 book》 的第五章中断和设备驱动

问题

在 xv6 中,fork() 系统调用会将父进程的整个用户空间内存复制到子进程中。**如果父进程占用的内存较大,复制过程会非常耗时。更糟糕的是,这种复制往往是浪费的。**例如,在子进程中调用 fork() 后紧接着执行 exec(),会导致子进程丢弃复制来的内存,很可能其中大部分根本没有被使用。另一方面,如果父子进程都使用了某个页面,并且有一个或两个要写这个页面,那就确实需要进行复制。

解决方案

**写时复制(COW)**的 fork() 目标是推迟为子进程分配和复制物理内存页面,直到它们真的被需要(如果有的话)。

COW 的 fork() 仅为子进程创建一个页表,其中用户内存的页表项(PTE)指向父进程的物理页面。COW 的 fork() 会将父子进程中所有用户页面的页表项标记为不可写(即清除可写标志)。当任一进程尝试写这些 COW 页面时,CPU 会触发一个缺页异常(page fault)。内核的缺页异常处理程序会识别出这是 COW 的情况,为发生异常的进程分配一个新的物理页面,复制原页面的内容,然后修改该进程的页表项指向新页面,并将其标记为可写。异常处理程序返回后,用户进程就可以写这个新复制的页面了。

COW 的 fork() 也让用户内存的物理页面释放变得更复杂。因为一个物理页面可能会被多个进程的页表引用,只有当最后一个引用消失时,该页面才能被释放。

我们发现 COW 和 Lazy alloc 的思想其实是类似的。

记得切换到 cow 分支


一、Implement copy-on write

1.1 说明

你的任务是:在 xv6 内核中实现写时复制的 fork()。当你的内核能够成功执行 cowtestusertests 这两个程序时,说明你完成了任务。

官网推荐的实现步骤:

  1. 修改 uvmcopy(),不要为子进程分配新页面,而是将父进程的物理页面映射到子进程中。清除父子两者对应页表项中的 PTE_W(可写标志)。

  2. 修改 usertrap() 以识别缺页异常。如果在 COW 页面上发生缺页异常,则使用 kalloc() 分配一个新页面,将原页面内容复制过去,并在当前进程的页表中安装新页面,并设置 PTE_W

  3. 确保物理页面只在最后一个引用消失时才释放。你可以为每个物理页面维护一个“引用计数”,记录有多少个用户页表引用了该页面。

    • kalloc() 分配页面时,将引用计数设为 1。
    • fork() 让子进程共享页面时,将引用计数加 1。
    • 每当进程从页表中移除页面时,引用计数减 1。
    • 只有当引用计数为 0 时,kfree() 才能将页面放回空闲列表。
    • 可以用一个定长的整数数组来存储这些引用计数。你需要设计如何为页面选择索引以及数组大小。例如,你可以用页面物理地址除以 4096 作为索引,数组元素个数可以设为 kalloc.ckinit() 放入空闲列表的最高物理地址除以 4096。
  4. 修改 copyout(),当遇到 COW 页面时,采用和处理缺页异常一样的方案进行处理。

官网的一些提示:

  • 懒惰分配实验应该让你对实现 COW 所需的 xv6 代码已有一定了解。但请不要在本实验中基于你之前懒惰分配实验的实现,而是从一个全新的 xv6 拷贝开始。
  • 你可以使用 RISC-V 页表项中的 RSW(软件保留位)来记录每个 PTE 是否为 COW 映射。
  • usertests 测试了一些 cowtest 没有覆盖的情况,所以一定要确保两个测试都能通过。
  • 有用的页表标志宏和定义可以在 kernel/riscv.h 文件末尾找到。
  • 如果在处理 COW 缺页异常时没有可用内存,应该终止对应进程。

1.2 实现

官网给出的步骤其实已经让我们有一个较为清晰的思路了,下面直接实现就行了。

实现逻辑分为两部分:

  • 延迟复制与释放
  • 写时复制
1.2.1 延迟复制与释放
  • 首先在 kalloc.c 中添加一个结构体保存每个页面的引用计数
  • 为了保证并发安全,我们额外添加一个自旋锁
  • 为了方便,提供一个 宏 用于计算引用计数数组索引,这也是官网给出的 页面物理地址除以 4096 作为索引 的方法
// get cnt Array index
#define PA2IDX(p) (((uint64)(p)) / PGSIZE)

struct {
  struct spinlock lock; // spinlock to ensure concurrency safety
  int refCnt[PHYSTOP / PGSIZE]; // page ref cnt array
} pageRef;

在 kinit 中新增一行 pageRef 自旋锁的初始化

void
kinit()
{
  initlock(&kmem.lock, "kmem");
  initlock(&pageRef.lock, "pageRef"); // initialize the lock for pageRef
  freerange(end, (void*)PHYSTOP);
}

在 kalloc 中 对新增页面的引用计数初始化为1

void *
kalloc(void)
{
  // ...

  if(r) {
    memset((char*)r, 5, PGSIZE); // fill with junk
    pageRef.refCnt[PA2IDX(r)] = 1;
  }
  return (void*)r;
}

然后修改 kfree 的逻辑为只有当引用计数减为0 时才释放

// Free the page of physical memory pointed at by v,
// which normally should have been returned by a
// call to kalloc().  (The exception is when
// initializing the allocator; see kinit above.)
void
kfree(void *pa)
{
  struct run *r;

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  acquire(&pageRef.lock); // acquire the lock for pageRef

  // not referred now
  if(--pageRef.refCnt[PA2IDX(pa)] <= 0) {
    memset(pa, 1, PGSIZE);    // fill with junk
    r = (struct run*)pa;

    acquire(&kmem.lock);
    r->next = kmem.freelist;
    kmem.freelist = r;
    release(&kmem.lock);    
  }
  release(&pageRef.lock);
}
  • 我们还要添加新增引用计数的逻辑
  • 以及物理页面复制的逻辑,以便后面调用
  • 记得在defs.h添加声明WWWWW
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
// if clone is needed
void *kTry_pgclone(void *pa) {
    acquire(&pageRef.lock);

    // if it only belongs to me
    if(pageRef.refCnt[PA2IDX(pa)] <= 1) {
        release(&pageRef.lock);
        return pa;
    }

    // apply for a new page
    uint64 newpa = (uint64)kalloc();

    if(newpa == 0) {
        release(&pageRef.lock);
        return 0;
    }

    // copy old data to new one
    memmove((void*)newpa, (void*)pa, PGSIZE);

    // refCnt of old page dec
    pageRef.refCnt[PA2IDX(pa)]--;

    release(&pageRef.lock);
    return (void*)newpa;
}

// inc the refCnt
void kparef_inc(void *pa) {
    acquire(&pageRef.lock);
    pageRef.refCnt[PA2IDX(pa)]++;
    release(&pageRef.lock);
}
  • 然后就可以修改 vm.c 中的uvmcopy的复制操作 改为该物理页面的引用次数+1
  • 以及为子进程新建页表
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);

    if (*pte & PTE_W) {
        *pte = (*pte & ~PTE_W) | PTE_COW;
    }

    flags = PTE_FLAGS(*pte);
    // create pte for son process
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      goto err;
    }
    kparef_inc((void*)pa);
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}
1.2.2 写时复制

即然要延迟写操作,那么在缺页异常的时候我们就需要知道这些页面是否是 写时复制的共享页面,官网也是提醒我们,可以用 PTE 的保留位来标记。

  • 我们为COW 的缺页错误编写一个辅助函数
  • 复制一个新页面
  • 设置为可写,取消 COW标记
  • 取消旧页面映射,添加新映射
  • 记得在 defs.h 中添加声明
    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
int cow_Helper(pagetable_t pagetable, uint64 va)
{
    pte_t *pte;

    // get pte for va
    if((pte = walk(pagetable, va, 0)) == 0)
        panic("uvmcowcopy: walk");

    uint64 pa = PTE2PA(*pte);
    uint64 newpa;
    // clone page
    if((newpa = (uint64)kTry_pgclone((void*)pa)) != 0){
        // set PTE_W and unset PTE_COW
        uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;
        // unmap but do not free 
        uvmunmap(pagetable, PGROUNDDOWN(va), 1, 0);
        // map to new pg
        if(mappages(pagetable, va, 1, newpa, flags) == -1) {
            panic("uvmcowcopy: mappages");
        }
        return 0;
    }
    return -1;
}

  • 然后修改copyout 的逻辑
  • 如果发现是 cow 页面,就调用 cow_Helper() 来处理。
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;
  pte_t *pte;
  struct proc *p = myproc();

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);

    // cow
    if(va0 < p->sz && (pte = walk(pagetable, va0, 0))!=0 && *pte & PTE_V&& *pte & PTE_COW)
        cow_Helper(pagetable,va0);
	// ...
}
  • 以及usertrap 逻辑的修改
  • 针对 我们写时复制的页面错误单独处理
void
usertrap(void)
{
  int which_dev = 0;

  if((r_sstatus() & SSTATUS_SPP) != 0)
    panic("usertrap: not from user mode");

  // send interrupts and exceptions to kerneltrap(),
  // since we're now in the kernel.
  w_stvec((uint64)kernelvec);

  struct proc *p = myproc();
  
  // save user program counter.
  p->trapframe->epc = r_sepc();
  
  if(r_scause() == 8){
    // system call

    if(p->killed)
      exit(-1);

    // sepc points to the ecall instruction,
    // but we want to return to the next instruction.
    p->trapframe->epc += 4;

    // an interrupt will change sstatus &c registers,
    // so don't enable until done with those registers.
    intr_on();

    syscall();
  } else if((which_dev = devintr()) != 0){
    // ok
  } else if (r_scause() == 13 || r_scause() == 15){
    pte_t *pte;
    uint64 va = r_stval();
    // is va valid?
    if (va >= p->sz)
        exit(-1);
    pte = walk(p->pagetable, va, 0);
    // valid and cow ?
    if (pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_COW) == 0)
        exit(-1);
    // copy on write for cow pg
    if (cow_Helper(p->pagetable, va) == -1) {
        exit(-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;
  }

  if(p->killed)
    exit(-1);

  // give up the CPU if this is a timer interrupt.
  if(which_dev == 2)
    yield();

  usertrapret();
}

测试一下:

cowtest:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

usertest:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

官网还是写的太详细了(bushi

p->killed = 1;
}

if(p->killed)
exit(-1);

// give up the CPU if this is a timer interrupt.
if(which_dev == 2)
yield();

usertrapret();
}




**测试一下:**

**cowtest:**

[外链图片转存中...(img-TxytPoKI-1748706565978)]

**usertest:**

[外链图片转存中...(img-kelfELYU-1748706565978)]

官网还是写的太详细了(bushi

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

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

相关文章

第304个Vulnhub靶场演练攻略:digital world.local:FALL

digital world.local&#xff1a;FALL Vulnhub 演练 FALL (digitalworld.local: FALL) 是 Donavan 为 Vulnhub 打造的一款中型机器。这款实验室非常适合经验丰富的 CTF 玩家&#xff0c;他们希望在这类环境中检验自己的技能。那么&#xff0c;让我们开始吧&#xff0c;看看如何…

Unity 模拟高度尺系统开发详解——实现拖动、范围限制、碰撞吸附与本地坐标轴选择

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity 模拟高度尺系统开发详解——实现拖动、范围限制、碰撞吸附与本地坐标轴选择 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不…

万字详解RTR RTSP SDP RTCP

目录 1 RTSP1.1 RTSP基本简介1.2 RSTP架构1.3 重点内容分析 2 RTR2.1 RTR简介2.2 RTP 封装 H.2642.3 RTP 解封装 H.2642.4 RTP封装 AAC2.5 RTP解封装AAC 3 SDP3.1 基础概念3.2 SDP协议示例解析3.3 重点知识 4 RTCP4.1 RTCP基础概念4.2 重点 5 总结 1 RTSP 1.1 RTSP基本简介 一…

云服务器如何自动更新系统并保持安全?

云服务器自动更新系统是保障安全、修补漏洞的重要措施。下面是常见 Linux 系统&#xff08;如 Ubuntu、Debian、CentOS&#xff09;和 Windows 服务器自动更新的做法和建议&#xff1a; 1. Linux 云服务器自动更新及安全维护 Ubuntu / Debian 系统 手动更新命令 sudo apt up…

[paddle]paddle2onnx无法转换Paddle3.0.0的json格式paddle inference模型

使用PDX 3.0rc1 训练时序缺陷检测后导出的模型无法转换 Informations (please complete the following information): Inference engine for deployment: PD INFERENCE 3.0-->onnxruntime Why convert to onnx&#xff1a;在端侧设备上部署 Paddle2ONNX Version: 1.3.1 解…

React项目在ios和安卓端要做一个渐变色背景,用css不支持,可使用react-native-linear-gradient

以上有个模块是灰色逐渐到白的背景色过渡 如果是css&#xff0c;以下代码就直接搞定 background: linear-gradient(180deg, #F6F6F6 0%, #FFF 100%);但是在RN中不支持这种写法&#xff0c;那应该写呢&#xff1f; 1.引入react-native-linear-gradient插件&#xff0c;我使用的是…

【数据分析】特征工程-特征选择

【数据分析】特征工程-特征选择 &#xff08;一&#xff09;方差过滤法1.1 消除方差为0的特征1.2 保留一半的特征1.3 特征是二分类时 &#xff08;二&#xff09;相关性过滤法2.1 卡方过滤2.2 F检验2.3 互信息法 &#xff08;三&#xff09;其他3.1 包装法3.2 嵌入法3.3 衍生特…

uni-app 安卓消失的字符去哪里了?maxLength失效了!

前情提要 皮一下~这个标题我还蛮喜欢的嘿嘿嘿【附上一个自行思考的猥琐的笑容】 前段时间不是在开发uni-app的一个小应用嘛,然后今天测试发现,有一个地方在苹果是没有问题的,但是在安卓上出现了问题,附上安卓的截图 在这里我是有限制maxLength=50的,而且,赋值字符串到字…

嵌入式STM32学习——串口USART 2.0(printf重定义及串口发送)

printf重定义&#xff1a; C语言里面的printf函数默认输出设备是显示器&#xff0c;如果要实现printf函数输出正在串口或者LCD显示屏上&#xff0c;必须要重定义标准库函数里调用的与输出设备相关的函数&#xff0c;比如printf输出到串口&#xff0c;需要将fputc里面的输出指向…

【大模型】情绪对话模型项目研发

一、使用框架&#xff1a; Qwen大模型后端Open-webui前端实现使用LLamaFactory的STF微调数据集&#xff0c;vllm后端部署&#xff0c; 二、框架安装 下载千问大模型 安装魔塔社区库文件 pip install modelscope Download.py 内容 from modelscope import snapshot_downlo…

【PCI】PCI入门介绍(包含部分PCIe讲解)

先解释一下寻址空间&#xff1a; 机器是32bit的话&#xff0c;意味着4G&#xff08;2的32次方&#xff09;寻址空间&#xff0c;内存条作为它的实际物理存储设备。大部分在跑内存程序运行&#xff0c;少部分用来存放其他东西。这是一个常见的4G寻址空间分布&#xff08;不一定是…

使用PowerBI个人网关定时刷新数据

使用PowerBI个人网关定时刷新数据 PowerBI desktop连接mysql&#xff0c;可以设置定时刷新数据或在PowerBI服务中手动刷新数据,步骤如下&#xff1a; 第一步&#xff1a; 下载网关。以个人网关为例&#xff0c;如图 第二步&#xff1a; 双击网关&#xff0c;点击下一步&…

数字人引领政务新风尚:智能设备助力政务服务

在信息技术飞速发展的今天&#xff0c;政府机构不断探索提升服务效率和改善服务质量的新途径。实时交互数字人在政务服务中的应用正成为一大亮点&#xff0c;通过将“数字公务员”植入各种横屏智能设备中&#xff0c;为民众办理业务提供全程辅助。这种创新不仅优化了政务大厅的…

深入剖析Java类加载机制:双亲委派模型的突破与实战应用

引言&#xff1a;一个诡异的NoClassDefFoundError 某金融系统在迁移到微服务架构后&#xff0c;突然出现了一个诡异问题&#xff1a;在调用核心交易模块时&#xff0c;频繁抛出NoClassDefFoundError&#xff0c;但类明明存在于classpath中。经过排查&#xff0c;发现是由于不同…

tauri2项目打开某个文件夹,类似于mac系统中的 open ./

在 Tauri 2 项目中打开文件夹 在 Tauri 2 项目中&#xff0c;你可以使用以下几种方法来打开文件夹&#xff0c;类似于 macOS 中的 open ./ 命令功能&#xff1a; 方法一&#xff1a;使用 shell 命令 use tauri::Manager;#[tauri::command] async fn open_folder(path: Strin…

企业文件乱、传输慢?用群晖 NAS 构建安全高效的共享系统

在信息化办公不断加速的今天&#xff0c;企业对文件存储、共享与安全管理的需求愈发严苛。传统文件共享方式效率低下、权限混乱、远程访问困难&#xff0c;极大影响了协同办公效率。此时&#xff0c;一套可靠、高效、安全的文件共享解决方案便成为众多企业的“刚需”。 这正是…

防爆手机VS普通手机,区别在哪里?

在加油站掏出手机接打电话、在化工厂车间随手拍照记录……这些看似寻常的行为&#xff0c;实则暗藏致命风险。普通手机在易燃易爆环境中可能成为“隐形炸弹”&#xff0c;而防爆手机却能安全护航。这两者看似相似&#xff0c;实则从底层基因到应用场景都存在着本质差异&#xf…

在RTX5060Ti上进行Qwen3-4B的GRPO强化微调

导语 最近赶上618活动&#xff0c;将家里的RTX 4060显卡升级为了RTX 5060Ti 16GB版本&#xff0c;显存翻了一番&#xff0c;可以进行一些LLM微调实验了&#xff0c;本篇博客记录使用unsloth框架在RTX 5060Ti 16GB显卡上进行Qwen3-4B-Base模型的GRPO强化微调实验。 简介 GPU性…

武汉火影数字VR大空间制作

VR大空间是一种利用空旷的物理空间&#xff0c;结合先进的虚拟现实技术&#xff0c;让用户能够在其中自由移动并深度体验虚拟世界的创新项目方式。 在科技飞速发展的当下&#xff0c;VR大空间正以其独特的魅力&#xff0c;成为科技与娱乐领域的耀眼新星&#xff0c;掀起了一股沉…

(增强)基于sqlite、mysql、redis的消息存储

原文链接&#xff1a;&#xff08;增强&#xff09;基于sqlite、mysql、redis的消息存储 教程说明 说明&#xff1a;本教程将采用2025年5月20日正式的GA版&#xff0c;给出如下内容 核心功能模块的快速上手教程核心功能模块的源码级解读Spring ai alibaba增强的快速上手教程…