目录

    • Task: Implement copy-on write
      • step 1:对内存块进行引用计数
      • step 2:`uvmcopy` 实现 fork 时将 parent 的物理页映射到 child 中
      • step 3:在 `usertrap` 中增加对 page fault 的处理
      • 执行测试

官方说明:Lab: Copy-on-Write Fork for xv6

这个实验是利用 page fault 实现操作系统的 Copy-On-Write 功能(COW)。

COW 的意思是说,当 fork 时,子进程不会完全拷贝父进程的整个内存空间,而是与父进程共享同一份物理内存,并在 page table 中记录映射关系,当任意一个进程需要写某块内存时,会发生 page fault,page fault handler 会真正分配出一块新的内存并重新建立映射,从而节省了大量的内存拷贝。

通过这个 lab,对 COW 有了更深的了解。

Task: Implement copy-on write

这个 task 的要求是实现 COW,并通过 cowtestusertests

step 1:对内存块进行引用计数

由于多个进程可能会共享同一份物理内存,因此像 kfree() 这类函数不能随意释放一个物理内存,而是需要借助引用计数来决定能否真正释放物理内存,所以我们需要实现对每个物理内存块的引用计数。

kalloc.c 文件中,我们模仿 kmem 结构体写一个 kref,用来对每个内存块进行引用计数:

struct {struct spinlock lock;int counts[PHYSTOP / PGSIZE];
} kref;

kinit() 对 kref 进行初始化:

kinit

更改引用计数的函数:

// 增加对某个物理地址的引用计数
void
kref_inc(uint64 pa)
{acquire(&kref.lock);kref.counts[pa / PGSIZE]++;release(&kref.lock);
}// 减少对某个物理地址的引用计数
void
kref_dec(uint64 pa)
{acquire(&kref.lock);kref.counts[pa / PGSIZE]--;release(&kref.lock);
}// 获取某个物理地址的引用计数
int
kref_count(uint64 pa)
{int count;acquire(&kref.lock);count = kref.counts[pa / PGSIZE];release(&kref.lock);return count;
}

然后修改 kalloc(),当新分配一个物理内存块时,其引用计数应当为 1:

kalloc
然后是修改 kfree() 函数,当通过 kfree 想释放一个内存块时,需要检查这个内存块的引用计数,如果存在多个引用,只需要对引用计数 -1 即可,只有当没有更多进程引用这个内存块时,才能真正释放它:

kfree

step 2:uvmcopy 实现 fork 时将 parent 的物理页映射到 child 中

调用 fork 时,会使用 uvmcopy 来克隆一份 parent 的物理内存空间给 child,而我们为了实现 COW,在这里只需要共享同一个物理内存。

具体的做法就是,遍历 parent 的整个内存空间,将其物理地址封装成 PTE 放入 child 的 page table 中,同时需要将 parent 和 child 的 PTE 都改为只读的,这样之后任一进程在 write 时能够发生 page fault 并进而完成实际的内存分配。

除了 PTE 需要设置为只读的,还需要在 PTE 标记一下这是一个 COW page,从而与普通的只读 page 进行区分,为此,需要使用 PTE 中的预留位作为 COW 标志位:

PTE_COW
修改 uvmcopy

uvmcopy
这一块代码对原有拷贝整个内存空间的代码进行改造,实现遍历整个内存空间,并获取每块内存的 PTE 和物理地址,对 PTE 的 flags 做了修改后,再将其添加到 child 的 page table 中。另外注意,需要增加对物理内存的引用计数,因为这里每个内存增加了一个 child 进行对其的引用。

step 3:在 usertrap 中增加对 page fault 的处理

当一个被标为 COW 的只读 page 发生 page fault 时,我们需要对其进行 COW 的处理,为其分配一份新的物理内存,并修改 page table 中的映射,这样当 page fault 被处理并恢复正常流程后,就可以对内存进行 write 了。

首先我们需要一个函数来判断一个 page 是否为 COW page:

// 判断是否为 cow page
// 是的话返回 1,否则返回 0
int is_cowpage(pagetable_t pagetable, uint64 va){if (va >= MAXVA)return 0;pte_t *pte = walk(pagetable, va, 0);if(pte == 0)return 0;if ((*pte & PTE_V) == 0)return 0;return (*pte & PTE_COW) != 0;
}

另外我们将处理 COW 导致的 page fault 的逻辑封装为函数 handle_cow()

void* handle_cow(pagetable_t pagetable, uint64 va){va = PGROUNDDOWN(va);pte_t *pte = walk(pagetable, va, 0);if(pte == 0)return 0;uint64 pa = PTE2PA(*pte);if (pa == 0)return 0;// 根据 ref count 判断是否需要分配新的物理页if (kref_count(pa) == 1) {// 如果只存在一个引用,则直接修改 PTE 的 flags 并返回即可*pte |= PTE_W;  // 增加 PTE 的写权限*pte &= ~PTE_COW;  // 清除 PTE 的 COW 标志return (void*)pa;}// 如果存在多个引用,则需要分配新的物理页并拷贝旧页面内容char *mem = kalloc();  // 分配物理内存if (mem == 0) {return 0;}memmove(mem, (void*)pa, PGSIZE);  // 拷贝旧页面内容// 修改 va 的 mapping*pte = PA2PTE(mem) | ((PTE_FLAGS(*pte) | PTE_W) & (~PTE_COW));// 将旧物理页的 ref count 减一kfree((void*)pa);return mem;
}

这里的易错点:

  • 我们需要通过物理页的 ref count 来决定处理 COW page fault 时,是否需要分配一个新的物理页,因为当被标为 COW page 时,是两个及以上的进程来共享同一个物理 page,当随着有些进程因为 COW page fault 而创建了新的内存块而减少 ref count,直到当 ref count 为 1 时,发生 COW page fault 的进程就不再需要分配新的物理页,而是直接将之前的物理页恢复可写的 flag 即可。
  • 修改 PTE 的 flags 时,注意不要出错

有了这两个函数,我们就可以在 usertrap() 中增加对 page fault 的处理逻辑:

usertrap

在发生 page fault 时,会有三种可能:

  1. 访问了非法内存页
  2. 内存页合法,但是权限不合适(比如在不可执行的 page 上执行指令等)
  3. 在 COW 的只读 page 上执行 write 操作。这也是我们唯一能处理并恢复的 page fault

完成以上修改后,这个实验就完成了。

执行测试

make qemu 后分别执行 cowtestusertests

cowtest
测试通过~

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IP地址工具,判断IP是否在指定范围内(支持ipv6)

常用方法,判断一个ip是否在指定的ip范围内,范围可能包括起始ip范围或者掩码形式,无其它依赖, package com.yk.ip;import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import jav…

操作系统-文件原理

目录 一、磁盘 1.1 磁盘结构 1. 盘片: 2. 盘面: 3. 磁头: 4. 磁道: 5. 扇区: 6. 磁道密度和扇区密度: 1.2 磁盘访问 1. 寻道(Seeking): 2. 延迟旋转&#xff…

C++进阶-- map和set

关联式容器 在前面,我们所学的vector、list、deque,这些都是序列容器,也就是底层为线性序列的数据结构。 而关联式容器是C标准库中的一种类别,用于存储键值对(key-value pair),关联式容器中的元…

vxe-table编辑单元格动态插槽slot的使用

业务场景:表格中只有特定某一行的的单元格可以编辑,列很多,为每个列写个插槽要写很多重复代码,所以这里使用动态插槽,简化代码量。显示编辑图标,点击编辑图标隐藏。失去焦点保存调后台接口。 解决办法&…

Linux多线程服务端编程:使用muduo C++网络库 学习笔记 附录C 关于Boost的看法

这是作者为电子工业出版社出版的《Boost程序库完全开发指南》写的推荐序,此处节选了作者对在C工程项目中使用Boost的看法。 最近一年(这篇文章写于2010年8月)作者电话面试了数十位C应聘者。惯用的暖场问题是“工作中使用过STL的哪些组件&…

十行代码开发一个AI应用

Semantic Kernal 简介 Semantic Kernel (SK) is a lightweight SDK that lets you easily mix conventional programming languages with the latest in Large Language Model (LLM) AI "prompts" with templating, chaining, and planning capabilities out-of-the-…

关于vue中关于eslint报错的问题

1 代码保存的时候会自动将单引号报错为双引号 导致eslint报错的问题, 解决思路: 在项目根目录下新建一个.prettierrc.json文件 { “tabWidth”: 2,“useTabs”: false,“singleQuote”: true,“semi”: false} 2 关于报错代码的时候 出现尾随逗号报错…

Zabbix 系统告警“More than 75% used in the configuration cache”处理办法

Zabbix系统报错提示 Zabbix 系统告警“More than 75% used in the configuration cache”,看了一下意思是可用的配置缓存超过75%。 修改缓存大小 vim /etc/zabbix/zabbix_server.confesc : /CacheSize 找到配置 将64M改大一点,保存退出。 重启zabbix…

WPF常用mvvm开源框架介绍 vue的mvvm设计模式鼻祖

WPF(Windows Presentation Foundation)是一个用于构建桌面应用程序的.NET框架,它支持MVVM(Model-View-ViewModel)架构模式来分离UI逻辑和业务逻辑。以下是一些常用的WPF MVVM开源框架: Prism Prism是由微软…

代码随想录算法训练营 Day32 | LeetCode122.买卖股票的最佳时机II、LeetCode55. 跳跃游戏、LeetCode45.跳跃游戏II

LeetCode122.买卖股票的最佳时机II 那么这里面根据贪心思想: 1、在最低点时买入,如果该点比上一点价格更低,只有两种情况:上一点为买入点,则此时更新买入点;上一点不是买入点,则此时将股票卖出…

物联网的核心技术有哪些?

物联网的核心技术有哪些? 说起物联网的相关技术,涉及到很多,比如自动识别技术、传感器技术、网络通信技术、云计算等,但说到核心技术,我认为有以下6个。这6个核心技术分别是感知和识别技术、物联网设备硬件、通信技术、数据处理技…

【MySQL】数据库中常用的函数

目录 聚合函数COUNT()函数的多种用法COUNT(*)COUNT(主键)COUNT(1)COUNT(常量)COUNT(非主键)COUNT(distinct(字段)) COUNT()函数小结 字符函数length(str)函数:获取参数值的字节个数concat(str1,str2,...)函数:字符串拼接upper(str)、lower(str)函数:大小…

Linux高负载排查最佳实践

在Linux系统中,经常会因为负载过高导致各种性能问题。那么如何进行排查,其实是有迹可循,而且模式固定。 本次就来分享一下,CPU占用过高、磁盘IO占用过高的排查方法。 还是那句话,以最佳实践入手,真传一句话…

1 开源鸿蒙OpenHarmony niobe407 STM32F407IGT6芯片轻型系统全量源码4.1版本下载流程

开源鸿蒙OpenHarmony niobe407 STM32F407IGT6芯片轻型系统全量源码4.1版本下载流程 作者将狼才鲸日期2024-02-27 一、前景提要 如果通过DevEco Marketplace网站获取下载源码的话,不全,有些板子下不到;OpenHarmony开发板列表,官方…

数据库-第二/三章 关系数据库和标准语言SQL【期末复习|考研复习】

前言 总结整理不易,希望大家点赞收藏。 给大家整理了一下计数据库系统概论中的重点概念,以供大家期末复习和考研复习的时候使用。 参考资料是王珊老师和萨师煊老师的数据库系统概论(第五版)。 文章目录 前言第二、三章 关系数据库和标准语言SQL2.1 关系2…

JVM原理-基础篇

Java虚拟机(JVM, Java Virtual Machine)是运行Java应用程序的核心组件,它是一个抽象化的计算机系统模型,为Java字节码提供运行环境。JVM的主要功能包括:类加载机制、内存管理、垃圾回收、指令解释与执行、异常处理与安…

React react.fragment和<>的使用及区别

React一个常用的模式是组件返回多个元素。Fragment可以为你的子元素分组而不需要在DOM上为它们添加额外的节点。 Fragment 使用 render() {return (<React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> );}短语法使用 这里…

虚拟机内存不够用了?全流程操作Look一下?

虚拟机信息&#xff1a;操作系统&#xff1a;CentOS Linux 7 (Core)&#xff0c;用的是VMware Workstation 16 Pro 版本16.2.3 build-19376536&#xff1b;我的主机 Windows 10 Education, 64-bit (Build 22000.1817) 10.0.22000 前言&#xff1a;虚拟机用久了就会出现内存不足…

代码随想录算法训练营Day37|738.单调递增的数字、968.监控二叉树

738.单调递增的数字 题目链接&#xff1a;738.单调递增的数字 文档链接&#xff1a;738.单调递增的数字 视频链接&#xff1a;贪心算法&#xff0c;思路不难想&#xff0c;但代码不好写&#xff01;LeetCode:738.单调自增的数字 C实现 class Solution { public:int monotoneIn…

Rocky Linux 安装部署 Zabbix 6.4

一、Zabbix的简介 Zabbix是一种开源的企业级监控解决方案&#xff0c;用于实时监测服务器、网络设备和应用程序的性能和可用性。它提供了强大的数据收集、处理和可视化功能&#xff0c;同时支持事件触发、报警通知和自动化任务等功能。Zabbix易于安装和配置&#xff0c;支持跨平…