【MTI 6.S081 Lab】Copy-on-write

news2024/11/15 20:08:41

【MTI 6.S081 Lab】Copy-on-write

  • The problem
  • The solution
  • Implement copy-on-write fork (hard)
    • 实验任务
    • Hints
    • 解决方案
      • 问题解决思考
      • uvmcopy
      • kfree
      • kalloc
      • kpageref
      • cow_handler
      • trap

虚拟内存提供了一定程度的间接性:内核可以通过将PTE标记为无效或只读来拦截内存引用,从而导致页面错误,并可以通过修改PTE来更改地址的含义。在计算机系统中有一种说法,任何系统问题都可以通过一定程度的间接性来解决。这个实验室探索了一个例子:copy-on-write fork

The problem

xv6中的fork()系统调用将父进程的所有用户空间内存复制到子进程中。如果父对象很大,则复制可能需要很长时间。更糟糕的是,这项工作经常被大量浪费:fork()通常在子进程中后跟exec(),这会丢弃复制的内存,通常不会使用大部分内存。另一方面,如果父进程和子进程都使用复制的页面,并且其中一个或两个都写入,则确实需要复制。‘

The solution

实现写时复制(COW)fork()的目标是推迟分配和复制物理内存页,直到实际需要副本(如果有的话)。突然安排

COW fork()只为子进程创建一个页表,用户内存的PTE指向父进程的物理页面。COW fork()将父进程和子进程中的所有用户PTE标记为只读。当任一进程尝试写入其中一个COW页面时,CPU将强制执行页面故障。内核页面错误处理程序检测到这种情况,为出错进程分配一页物理内存,将原始页面复制到新页面中,并修改出错进程中的相关PTE以引用新页面,这一次PTE标记为可写。当页面错误处理程序返回时,用户进程将能够写入页面的副本。

COW fork()使得释放实现用户内存的物理页面变得有点棘手。一个给定的物理页面可能被多个进程的页面表引用,并且只有当最后一个引用消失时才应该释放。在像xv6这样的简单内核中,这种记账相当简单,但在生产内核中,这可能很难做到正确;例如,请参阅Patching until the COWs come home.

Implement copy-on-write fork (hard)

实验任务

你的任务是在xv6内核中实现copy-on-write fork。如果修改后的内核成功地执行了cowtest和“usertests-q”程序,那么就完成了。

为了帮助你测试你的实现,我们已经提供了一个xv6程序叫做cowtest。cowtest运行不同的测试,但是在未修改xv6的情况下,第一个测试都会失败。

$ cowtest
simple: fork() failed

”simple“测试分配超过一半可用的物理内存,然后fork()。fork失败的原因是没有足够的物理内存分配给子进程去完整的copy父进程的所有内存。

但你完成这个实验后,你的内核应该能通过所有的cowtest和usertests -q的测试。

$ cowtest
simple: ok
simple: ok
three: zombie!
ok
three: zombie!
ok
three: zombie!
ok
file: ok
ALL COW TESTS PASSED
$ usertests -q
...
ALL TESTS PASSED

这是一个合理的攻击计划。

  1. 修改uvmcopy()将父进程的物理页面映射到子进程,而不是分配新页面。清除已设置PTE_W的页的子进程和父进程PTE中的PTE_W。
  2. 修改usertrap()以识别页面错误。当最初可写入的COW页面出现写入页面错误时,使用kalloc()分配一个新页面,将旧页面复制到新页面,然后在PTE_W设置的PTE中安装新页面。最初只读的页面(未映射PTE_W,如文本段中的页面)应保持只读,并在父进程和子进程之间共享;试图写入这样一个页面的进程应该被终止。
  3. 确保每个物理页在最后一个PTE引用消失时都被释放,而不是之前。实现这一点的一个好方法是,为每个物理页面保留引用该页面的用户页面表数量的“引用计数”。当kalloc()分配页面时,将页面的引用计数设置为1。当fork导致子级共享页面时,增加页面的引用数,每当任何进程将页面从其页面表中删除时,减少页面的计数。只有当引用计数为零时,kfree()才应将页面放回空闲列表。将这些计数保存在一个固定大小的整数数组中是可以的。您必须制定出一个方案,说明如何索引数组以及如何选择其大小。例如,您可以用页面的物理地址除以4096对数组进行索引,并通过kalloc.c中的kinit()为数组指定一个元素数,该元素数等于自由列表中任何页面的最高物理地址。您可以随意修改kalloc.c(例如,kalloc()和kfree())以保持引用计数。
  4. 当遇到COW页面时,修改copyout()以使用与页面错误相同的方案。

Hints

  • 对于每个PTE,有一种方法来记录它是否是COW映射可能是有用的。为此,您可以使用RISC-V PTE中的RSW(保留用于软件)位。
  • usertests -q探索了cowtest没有测试的场景,所以不要忘记检查所有测试都通过了。
  • 一些有用的宏和页表标志的定义在kernel/rescv.h的末尾。
  • 如果发生COW页面故障,并且没有可用内存,则应终止进程。

解决方案

问题解决思考

  • 用一位代表是否是COW页面。因为只读页面不存在COW的问题,所以只要对可写页面进行COW映射即可。

    第九位代表是否是COW页面,也即

    #define PTE_C (1L << 8)
    
  • 页表需要创建。也即至少需要三个页表页

  • 蹦床页面,每次fork都在allocproc中分配了,所以我们不用管蹦床页面

  • usertrap中确定页面错误的类型

    在这里插入图片描述

    在SCAUSE(注,Supervisor cause寄存器,保存了trap机制中进入到supervisor mode的原因)寄存器的介绍中,有多个与page fault相关的原因。比如,

    • 13表示是因为load引起的page fault;
    • 15表示是因为store引起的page fault;
    • 12表示是因为指令执行引起的page fault。
  • XV6内核会打印出错的虚拟地址,并且这个地址会被保存在STVAL寄存器中,所以要更新SVAL所指虚拟地址中物理页面。

uvmcopy

// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
int
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");
    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);
    //   goto err;
    // }
    if ((flags & PTE_C) || (flags & PTE_W)) {
      flags = (flags & (~PTE_W)) | PTE_C;
      *pte = (*pte & (~PTE_W)) | PTE_C;
    }
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      goto err;
    }
    kpageref(pa, 1);
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}

kfree

// Free the page of physical memory pointed at by pa,
// 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");

  int idx = (uint64)pa / PGSIZE;
  acquire(&pageref.lock);
  int count = pageref.count[idx];
  if (count > 1) {      // 还有大于一个在引用,直接返回即可
    pageref.count[idx]--;
    release(&pageref.lock);
    return;
  }
  pageref.count[idx] = 0;
  release(&pageref.lock);

  // 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);
}

kalloc

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r) {
    kmem.freelist = r->next;
    int idx = (uint64)r / PGSIZE;
    acquire(&pageref.lock);
    pageref.count[idx] = 1;
    release(&pageref.lock);
  }
  release(&kmem.lock);

  if(r)
    memset((char*)r, 5, PGSIZE); // fill with junk
  return (void*)r;
}

kpageref

void
kpageref(uint64 pa, int inc) {
  int idx = pa / PGSIZE;
  acquire(&pageref.lock);
  pageref.count[idx] += inc;
  release(&pageref.lock);
}

cow_handler

int
cow_handler(pagetable_t pagetable, uint64 va) {
  pte_t *pte;
  uint64 pa;
  uint flags;
  char *mem;

  if (va >= MAXVA) {
    return -1;
  }
  va = PGROUNDDOWN(va);
  if ((pte = walk(pagetable, va, 0)) == 0) {
    return 0;
  }
  pa = PTE2PA(*pte);
  flags = PTE_FLAGS(*pte);
  if (!(flags & PTE_C)) {
    return -1;      // 不是cow
  }
  if((mem = kalloc()) == 0)
    return -1;
  memmove(mem, (char*)pa, PGSIZE);
  // 更新flag
  flags |= PTE_W;
  flags &= (~PTE_C);
  *pte = PA2PTE(mem) | flags;
  kfree((void *)pa);        // 减少pa的引用计数
  return 0;
}

trap

//
// handle an interrupt, exception, or system call from user space.
// called from trampoline.S
//
void
usertrap(void)
{
  uint64 scause = r_scause(); 
  if(scause == 8){
    // system call
  } else if (scause == 15) {
    if (cow_handler(p->pagetable, r_stval()) < 0) {
      setkilled(p);
    }
  } else if((which_dev = devintr()) != 0){
}

要特别注意死锁,在死锁上花了三四个小时。

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

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

相关文章

Quartz项目搭建与任务执行源码分析

数据库准备 准备一个MySQL数据库&#xff0c;版本为8.0&#xff0c;然后创建一个库&#xff0c;并从quartz官方的版本包中找到名称为tables_mysql_innodb.sql的脚本执行进去&#xff08;脚本内容文后也有提供&#xff09;。 项目依赖说明 创建一个Maven项目&#xff0c;引入…

Python方式实现简易弹道计算机

1 问题 本周无意间刷到了德国豹2A5坦克的火控介绍&#xff0c;想自己编写一个不考虑空气阻力以及测风影响的简易弹道计算机&#xff08;大口径火炮&#xff09;。 2 方法 由高中物理知识了解到&#xff0c;炮弹出膛之后基本就是抛物线列个抛物线方程就好了; 百度得火炮的弹道方…

【Django学习】(十六)session_token认证过程与区别_响应定制

一、认识session与token 这里就直接引用别人的文章&#xff0c;不做过多说明 网络应用中session和token本质是一样的吗&#xff0c;有什么区别&#xff1f; - 知乎 二、token响应定制 在全局配置表中配置 DEFAULT_AUTHENTICATION_CLASSES: [# 指定jwt Token认证rest_framew…

python_day17_多线程

threading模块 import timedef sing():while True:print("唱歌~~~~~~~~~~~~")time.sleep(1)def dance():while True:print("跳舞############")time.sleep(1) if __name__ __main__:sing()dance()此时为单线程 import threading import timedef sing(…

Java 异常处理的使用和思考

概念 异常处理的概念起源于早期的编程语言&#xff0c;如 LISP、PL/I 和 CLU。这些编程语言首次引入了异常处理机制&#xff0c;以便在程序执行过程中检测和处理错误情况。异常处理机制随后在 Ada、Modula-3、C、Python、Java 等编程语言中得到了广泛采用和发展。在 Java 中&a…

迁移学习《Efficient and Robust Pseudo-Labeling for Unsupervised Domain Adaptation》

1 摘要 问题&#xff1a;无监督域适应传统方法将超过一定置信度阈值的数据视为目标域的伪标记数据&#xff0c;因此选择合适的阈值会影响目标性能。 在本文中&#xff0c;提出了一种新的基于置信度的加权方案来获得伪标签&#xff0c;并提出了一种自适应阈值调整策略&#xff0…

oCPC实践录 | oCPC下机制设计变得毫无意义?(2)无声的战争

接上回oCPC实践录 | oCPC下机制设计变得毫无意义&#xff1f;&#xff08;1&#xff09;事出异常必有妖&#xff0c;互联网广告最开始采用的广义第一价格密封拍卖&#xff08;GFP)&#xff0c;对广告主而言&#xff0c;需要不断感知竞争对手的变化&#xff0c;修改报价&#xf…

text-generation-webui加载chatglm2-6b时,报错,要求set the option trust_remote_code=True

背景 使用text-generation-webui加载chatglm2-6b大模型时报错&#xff0c;要求设置option trust_remote_codeTrue&#xff0c;一开始没注意界面&#xff0c;去翻找配置文件&#xff0c;后来发现&#xff0c;就在Model界面&#xff0c;有一个复选框&#xff0c;可以进行设置&am…

IO模型epoll

通过epoll实现一个并发服务器 服务器 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h…

【新星计划】STM32F103C8T6 - C语言 - 蓝牙JDY-31-SPP串口通信实验

文章目录 蓝牙技术的发展历史SPP蓝牙串口BLE协议&#xff08;超低功耗应用蓝牙协议&#xff09; 常见通用蓝牙模块JDY-31-SPPHC05/06 Keil 工程开发模版main.c 源文件&#xff1a;接线方式&#xff1a;烧录工具&#xff1a;FlyMcu串口调试工具&#xff1a;XCOM蓝牙调试助手APP …

Yolov8训练自己的数据集合(最新实验数据)

一、安装环境 笔者环境如下&#xff1a; win10 anaconda python3.8二、clone代码 地址如下&#xff0c;可以直接使用git命令进行clone,也可以直接去网站下载 git clone https://github.com/ultralytics/ultralytics代码目录如下所示 三、安装必要的库 其实这里比较重要的是…

java的instanceof操作符

说明 java的instanceof操作符可以用于类型的比较或者模式匹配比较。instanceof关键字左边是对象的引用&#xff0c;右边是引用类型或者模式。 如果instanceof关键字右边是一个引用类型&#xff0c;那么instanceof关键字是一个类型比较操作符。 如果instanceof关键字右边是一个…

6.事件监听(绑定)

6.1事件监听 ●什么是事件? 事件是在编程时系统内发生的动作或者发生的事情 比如用户在网页上单击一个按钮 ●什么是事件监听? 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为绑定事件或者注册事件&…

FL Studio 21官方中文版功能介绍及2023最新下载详细图文安装激活教程。FL Studio 21需要系统配置要求

FL Studio 21版本更新现已发布&#xff0c;在这次更新中优化了很多功能&#xff0c;但这些现在都不重要&#xff0c;FL Studio21版本的这次更新中令人瞩目的更新莫过于对简体中文版的支持了。以前FL Studio只有英文版&#xff0c;想要用上中文版只有用汉化包&#xff0c;而且有…

某信用中心之加速乐实战分析

某信用中心之加速乐实战分析 某信用中心之加速乐实战分析声明逆向目标逆向分析第一层cookie获取第二层cookie获取调试分析JS文件 模拟执行致谢 某信用中心之加速乐实战分析 声明 本文章中所有内容仅供学习交流&#xff0c;抓包内容、敏感网址、数据接口均已做脱敏处理&#x…

【二叉树进阶】搜索二叉树(递归+非递归两种版本详解)

文章目录 前言1. 二叉搜索树的概念2. 二叉搜索树的结构2.1 结点结构2.2 树结构 3. 插入操作&#xff08;非递归&#xff09;3.1 思路分析3.2 代码实现3.3 中序遍历&#xff08;测试用&#xff09; 4. 查找操作&#xff08;非递归&#xff09;4.1 思路分析4.2 代码实现 5. 删除操…

七大经典比较排序算法

1. 插入排序 (⭐️⭐️) &#x1f31f; 思想&#xff1a; 直接插入排序是一种简单的插入排序法&#xff0c;思想是是把待排序的数据按照下标从小到大&#xff0c;依次插入到一个已经排好的序列中&#xff0c;直至全部插入&#xff0c;得到一个新的有序序列。例如&#xff1a;…

Doc as Code (3):业内人士的观点

作者 | Anne-Sophie Lardet 在技术传播国际会议十周年之际&#xff0c;Fluid Topics 的认证技术传播者和功能顾问 Gaspard上台探讨了“docOps 作为实现Doc as Code的中间结构”的概念。在他的演讲中&#xff0c;观众提出了几个问题&#xff0c;我们想分享Gaspard的见解&#x…

深入学习 Redis - 渐进式遍历 scan 命令、数据库管理命令

目录 前言 一、scan 命令 二、数据库管理命令 select dbsize flushdb / flushall 前言 之前我们所了解到的 keys * 是一次性把整个 redis 中所有的 key 都获取到&#xff0c;但是整个操作比较危险&#xff0c;可能会一下子的都太多的 key&#xff0c;阻塞 redis 服务器. …

NLP(六十三)使用Baichuan-7b模型微调人物关系分类任务

任务介绍 人物关系分类指的是对文本中的两个人物&#xff0c;在特定的关系列表中&#xff0c;判断他们之间的人物关系。以样本亲戚 1837年6月20日&#xff0c;威廉四世辞世&#xff0c;他的侄女维多利亚即位。为例&#xff0c;其中亲戚为人物关系&#xff0c;威廉四世为实体1&a…