MIT 6.S081 2020 Lab6 Copy-on-Write Fork for xv6 个人全流程
文章目录
- 零、写在前面
- 一、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()
。当你的内核能够成功执行 cowtest
和 usertests
这两个程序时,说明你完成了任务。
官网推荐的实现步骤:
-
修改
uvmcopy()
,不要为子进程分配新页面,而是将父进程的物理页面映射到子进程中。清除父子两者对应页表项中的PTE_W
(可写标志)。 -
修改
usertrap()
以识别缺页异常。如果在 COW 页面上发生缺页异常,则使用kalloc()
分配一个新页面,将原页面内容复制过去,并在当前进程的页表中安装新页面,并设置PTE_W
。 -
确保物理页面只在最后一个引用消失时才释放。你可以为每个物理页面维护一个“引用计数”,记录有多少个用户页表引用了该页面。
- 当
kalloc()
分配页面时,将引用计数设为 1。 - 当
fork()
让子进程共享页面时,将引用计数加 1。 - 每当进程从页表中移除页面时,引用计数减 1。
- 只有当引用计数为 0 时,
kfree()
才能将页面放回空闲列表。 - 可以用一个定长的整数数组来存储这些引用计数。你需要设计如何为页面选择索引以及数组大小。例如,你可以用页面物理地址除以 4096 作为索引,数组元素个数可以设为
kalloc.c
中kinit()
放入空闲列表的最高物理地址除以 4096。
- 当
-
修改
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 safetyint refCnt[PHYSTOP / PGSIZE]; // page ref cnt array
} pageRef;
在 kinit 中新增一行 pageRef 自旋锁的初始化
void
kinit()
{initlock(&kmem.lock, "kmem");initlock(&pageRef.lock, "pageRef"); // initialize the lock for pageReffreerange(end, (void*)PHYSTOP);
}
在 kalloc 中 对新增页面的引用计数初始化为1
void *
kalloc(void)
{// ...if(r) {memset((char*)r, 5, PGSIZE); // fill with junkpageRef.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 nowif(--pageRef.refCnt[PA2IDX(pa)] <= 0) {memset(pa, 1, PGSIZE); // fill with junkr = (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 meif(pageRef.refCnt[PA2IDX(pa)] <= 1) {release(&pageRef.lock);return pa;}// apply for a new pageuint64 newpa = (uint64)kalloc();if(newpa == 0) {release(&pageRef.lock);return 0;}// copy old data to new onememmove((void*)newpa, (void*)pa, PGSIZE);// refCnt of old page decpageRef.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 processif(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 vaif((pte = walk(pagetable, va, 0)) == 0)panic("uvmcowcopy: walk");uint64 pa = PTE2PA(*pte);uint64 newpa;// clone pageif((newpa = (uint64)kTry_pgclone((void*)pa)) != 0){// set PTE_W and unset PTE_COWuint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;// unmap but do not free uvmunmap(pagetable, PGROUNDDOWN(va), 1, 0);// map to new pgif(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);// cowif(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 callif(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 pgif (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
相关文章:

MIT 6.S081 2020 Lab6 Copy-on-Write Fork for xv6 个人全流程
文章目录 零、写在前面一、Implement copy-on write1.1 说明1.2 实现1.2.1 延迟复制与释放1.2.2 写时复制 零、写在前面 可以阅读下 《xv6 book》 的第五章中断和设备驱动。 问题 在 xv6 中,fork() 系统调用会将父进程的整个用户空间内存复制到子进程中。**如果父…...

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

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

万字详解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 系统(如 Ubuntu、Debian、CentOS)和 Windows 服务器自动更新的做法和建议: 1. Linux 云服务器自动更新及安全维护 Ubuntu / Debian 系统 手动更新命令 sudo apt up…...
训练中常见的运动强度分类
概述 有氧运动是耐力基础,乳酸阈值是耐力突破的关键,提升乳酸阈值可以延缓疲劳,无氧运动侧重速度和力量,混氧和最大摄氧量用于细化训练强度和评估潜力。 分类强度供能系统乳酸浓度训练目标有氧运动低(60%-80% HR&…...
java 递归地复制文件夹及其所有子文件夹和文件
java 递归地复制文件夹及其所有子文件夹和文件 根据你的需求,下面是一个 Java 代码示例,用于递归地复制文件夹及其所有子文件夹和文件。由于你提到文件夹是数据层面的,这里假设你可以通过 folderById 来获取文件夹的相关信息,并且…...

[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:在端侧设备上部署 Paddle2ONNX Version: 1.3.1 解…...

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

【数据分析】特征工程-特征选择
【数据分析】特征工程-特征选择 (一)方差过滤法1.1 消除方差为0的特征1.2 保留一半的特征1.3 特征是二分类时 (二)相关性过滤法2.1 卡方过滤2.2 F检验2.3 互信息法 (三)其他3.1 包装法3.2 嵌入法3.3 衍生特…...
第4节 Node.js NPM 使用介绍
本文介绍了 Node.js 中 NPM 的使用,我们先来了解什么是 NPM。 NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种: 允许用户从NPM服务器下载别人编写的第三方包到本地使用。允许…...
RK3399 Android7.1增加应用安装白名单机制
通过设置应用包名白名单的方式限制未授权的应用软件安装。 diff --git a/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java b/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java index af9a533..ca…...

uni-app 安卓消失的字符去哪里了?maxLength失效了!
前情提要 皮一下~这个标题我还蛮喜欢的嘿嘿嘿【附上一个自行思考的猥琐的笑容】 前段时间不是在开发uni-app的一个小应用嘛,然后今天测试发现,有一个地方在苹果是没有问题的,但是在安卓上出现了问题,附上安卓的截图 在这里我是有限制maxLength=50的,而且,赋值字符串到字…...
#AI短视频制作完整教程
目录 前期准备AI工具选择制作流程后期优化发布策略 前期准备 1. 确定视频主题和风格 内容定位:教育、娱乐、商业推广、个人分享目标受众:年龄、兴趣、平台偏好视频时长:15-60秒(根据平台调整)风格调性:…...

嵌入式STM32学习——串口USART 2.0(printf重定义及串口发送)
printf重定义: C语言里面的printf函数默认输出设备是显示器,如果要实现printf函数输出正在串口或者LCD显示屏上,必须要重定义标准库函数里调用的与输出设备相关的函数,比如printf输出到串口,需要将fputc里面的输出指向…...

【大模型】情绪对话模型项目研发
一、使用框架: Qwen大模型后端Open-webui前端实现使用LLamaFactory的STF微调数据集,vllm后端部署, 二、框架安装 下载千问大模型 安装魔塔社区库文件 pip install modelscope Download.py 内容 from modelscope import snapshot_downlo…...
Git 教程 | 如何将指定文件夹回滚到上一次或某次提交状态(命令详解)
在日常开发中,我们经常会遇到这样的情况: “我想把某个文件夹恢复到之前的状态,但又不想影响整个项目,怎么办?” 别担心!这篇文章就教你如何用 Git 把项目中某个特定文件夹(或文件)回…...

【PCI】PCI入门介绍(包含部分PCIe讲解)
先解释一下寻址空间: 机器是32bit的话,意味着4G(2的32次方)寻址空间,内存条作为它的实际物理存储设备。大部分在跑内存程序运行,少部分用来存放其他东西。这是一个常见的4G寻址空间分布(不一定是…...
Cloudera Manager 学习笔记
目录 1 基础概念与原理1.1 Cloudera Manager的主要作用是什么?1.2 与Ambari有何区别?1.3 Cloudera Manager 的核心功能和架构是什么?1.4 解释一下 Cloudera Manager 中的服务模型和角色?1.5 Cloudera Manager 是如何实现对 CDH 集群的集中管…...
Deepin 23.10安装Docker
个人博客地址:Deepin 23.10安装Docker | 一张假钞的真实世界 Deepin 是基于 Debian 的国产 Linux 发行版,安装 Docker Desktop 可能会遇到兼容性问题,因为 Docker Desktop 官方主要支持 Ubuntu/Debian/Red Hat/Fedora/Arch 等主流发行版&…...

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

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

深入剖析Java类加载机制:双亲委派模型的突破与实战应用
引言:一个诡异的NoClassDefFoundError 某金融系统在迁移到微服务架构后,突然出现了一个诡异问题:在调用核心交易模块时,频繁抛出NoClassDefFoundError,但类明明存在于classpath中。经过排查,发现是由于不同…...
Kotlin JVM 注解详解
前言 Kotlin 作为一门现代 JVM 语言,提供了出色的 Java 互操作性。为了更好地支持与 Java 代码的交互,Kotlin 提供了一系列 JVM 相关注解。这些注解不仅能帮助我们控制 Kotlin 代码编译成 Java 字节码的行为,还能让我们的 Kotlin 代码更好地…...
将 node.js 项目作为后台进程持续运行
将 node.js 项目作为后台进程持续运行 方法 1:使用 pm2(生产环境推荐) 安装 pm2(Node.js 进程管理器):npm install pm2 -g启动应用:pm2 start hd/src/app.js --name "my-app"常用命…...
【PhysUnits】15.5 引入P1后的标准化表示(standardization.rs)
一、源码 这段代码实现了一个类型级别的二进制数标准化系统,主要用于处理二进制数的前导零和特殊值的简化。 use super::basic::{Z0, P1, N1, B0, B1, NonNegOne, NonZero};/// 处理 B0<H> 类型的标准化 /// Standardization for B0<H> types /// ///…...
MySQL-5.7 修改密码和连接访问权限
一、MySQL-5.7 修改密码和连接权限设置 修改密码语法 注意:rootlocalhost 和 root192.168.56.% 是两个不同的用户。在修改密码时,两个用户的密码是各自分别保存,如果两个用户密码设置不一样则登陆时注意登陆密码 GRANT ALL PRIVILEGES ON …...

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

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

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