6.s081 学习实验记录(六)copy on write fork

文章目录

    • 实现COW
      • 一、问题
      • 二、注意
      • 三、实现COW
      • 四、实验结果

实现COW

一、问题

准备:切换到 cow 分支

目前 xv6 的 fork 系统调用创建的子进程会赋值父进程所有的用户态内存,如果父进程比较大,那么这个复制过程会很耗时,而且一般通过 fork 创建的进程会紧接着执行 exec 系统调用,这将丢弃 fork 阶段拷贝的所有内存。

因此,我们需要实现一个具有cow功能的fork,在创建子进程的时候仅创建一个页表,而实际的内容(PTE)仍然指向父进程。当进程需要写某些内容的时候,发生页错误,在页错误的处理函数中为进程分配一个新的页面,并拷贝该页面的内容(不管是父进程还是子进程,谁先触发谁拷贝)。

cow会使得物理页的回收变得麻烦,因为一个物理页可能被很多进程共享,只有最后一个引用该页面的进程释放该页面,该页面才能得到回收。

二、注意

  • 修改uvmcopy(),将父进程的用户态空间物理页 map 到子进程,而不是为子进程分配新页面。清理所有设置了 PTE_W 位的父子进程共享的页面的该标记位,因此父子进程都没有这些页面的写权限。
  • 修改 usertrap(),使其可以处理 pagefault,当一个cow页面(被多个进程引用的页面)发生了 write page-fault,此时通过 kalloc() 分配一个新的页面,并将旧页面的数据拷贝过去,然后设置新分配页面的 PTE_W bit位。如果 write page fault 发生的页面是一个只读页面,那么该进程应该被kill
  • 可以为每个用户态的页面设置一个引用计数,用于实现物理页的释放。可以设置一个固定大小的数组用来存储每个页的引用计数,数组的大小为物理内存的大小除以4k。通过每个页的高地址除以4KB来计算索引 (kalloc() and kfree())。
  • 修改copyout() 以使用相同的算法来操作cow页面(因为copyout函数是在向用户页表中写入内容,因此会触发cow页面的复制)
  • 可以使用 PTE 页表项中的 RSW 位来记录当前页表项对应的页面是否为cow页面
  • kernel/riscv.h 中定义了一些可能有用的宏和页表相关的flags
  • 如果一个进程的cow页面发生了页面错误,此时没有物理内存可以分配,此时该进程应该被kill

三、实现COW

思路:

  • kernel/riscv.h:定义 PTE_RSW
  • kernel/kalloc.c:添加一个全局数组以及锁,记录引用计数;kalloc()kfree() 增加对于引用计数的增减操作
  • kernel/vm.c:修改uvmcopy()实现子进程不拷贝父进程的用户态空间,而只是建立PTE映射。此时需要将拷贝的原来置PTE_W的页,清理该标志位,并设置 PTE_RSW 位。
  • kernel/vm.c:修改copyout(),在向一个cow页(PTE_RSW = 1)写入时,需要申请新的页面并复制内容,然后其余操作同uvmcopy(此处也可以不修改copyout函数,而是使用kerneltrap的方式来实现,因为copyout在向用户态的页表中写入内容时,如果不可写,也会出现 page write fault)。
  • kernel/trap.c:usertrap()函数增加对于page fault的处理,而 page write fault 的异常值为15.
    在这里插入图片描述

代码:

  • kernel/riscv.h
#define PTE_RSW (1L << 8) // RSW
  • kernel/defs.h
#include "memlayout.h"
extern int page_refcnt[PHYSTOP / PGSIZE];
/*
该锁保护的临界资源只有处于内核态的函数才能访问到,因此只有系统调用、trap路径可以访问
只要保证trap或者系统调用可能发生异常的地方之前释放锁就不会死锁
*/
extern struct spinlock ref_lock; 
  • kernel/main.c
initlock(&ref_lock, "ref_lock"); //初始化锁
procinit();      // process table
  • kernel/kalloc.c
int page_refcnt[PHYSTOP / PGSIZE]; //定义引用数组,大小为物理内存总量除以每个page的大小
struct spinlock ref_lock; //用于保证并发

void *
kalloc(void)
{
  struct run *r;
  int index = -1;
  // ... 
  if(r){
    memset((char*)r, 5, PGSIZE); // fill with junk
    // 新增代码
    index = (uint64)r / PGSIZE;
    if(r >= 0){
      acquire(&ref_lock);
      page_refcnt[index] = 1; //增加引用计数
      release(&ref_lock);
    }
    //新增结束
  }
    
  return (void*)r;
}

void
kfree(void *pa)
{
  struct run *r;
  int refcnt = 0;
  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");
  r = (struct run*)pa;
  // 新增代码
  acquire(&ref_lock);
  refcnt = (--page_refcnt[(uint64)r / PGSIZE]);
  release(&ref_lock);
  if(refcnt > 0){
    return;
  }
  //新增结束
  // Fill with junk to catch dangling refs.
}
  • kernel/vm.c
#include "param.h"
#include "types.h"
#include "memlayout.h"
#include "elf.h"
#include "riscv.h"
#include "defs.h"
#include "fs.h"
#include "spinlock.h" // 引入头文件
#include "proc.h" // 引入头文件

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;
  //char *mem = 0;
  // old 为父进程页表,new 为子进程页表
  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_W,清理该标记位,并设置 PTE_RSW
    if((*pte & PTE_W) && (*pte & PTE_U)){
      *pte &= ~PTE_W; //清理 PTE_W
      *pte |= PTE_RSW;
    }
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    // 不需要分配内存
    // if((mem = kalloc()) == 0)
    //   goto err;
    
    acquire(&ref_lock);
    page_refcnt[pa / PGSIZE]++;
    release(&ref_lock);
    //memmove(mem, (char*)pa, PGSIZE);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      acquire(&ref_lock);
      page_refcnt[pa / PGSIZE]--;
      release(&ref_lock);
      goto err;
    }
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  return -1;
}
int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;
  pte_t *pte = 0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    if(va > myproc()->sz){
      return -1;
    }
    // 新增
    //pte = PA2PTE(pa0); 此处不能使用该宏,因为该宏将flags等都抹除了
    pte = walk(pagetable, va0, 0);
    if(*pte & PTE_RSW){
      //如果是cow页面,分配一个新的页面
      uint64 addr = (uint64)kalloc();
      if(addr == 0){
        // 分配失败,杀掉当前进程
        myproc()->killed = 1;
      } else {
        // 复制旧页面的全部内容到新页面
        memmove((void*)addr, (char*)pa0, PGSIZE);
        uint flags = PTE_FLAGS(*pte);
        // 解除旧页面和va0的映射,该函数内部会调用 kfree() 减少引用计数
        uvmunmap(pagetable, va0, 1, 1);
        // 设置PTE的标记位,重置其物理地址以及设置PTE_W标志位
        *pte = (PA2PTE(addr) | flags | PTE_W);
        *pte &= ~PTE_RSW; //清理 PTE_RSW 标志位
        pa0 = (uint64)addr; //更正pa0指向新的页面
      }
    }
    // 新增结束
    n = PGSIZE - (dstva - va0);
    if(n > len)
      n = len;
    memmove((void *)(pa0 + (dstva - va0)), src, n);

    len -= n;
    src += n;
    dstva = va0 + PGSIZE;
  }
  return 0;
}
  • kernel/trap.c
int cowtrap(pagetable_t pagetable, uint64 va);

void
usertrap(void)
{
  // ...
  if(r_scause() == 8){
	// ...
    syscall();
  } else if(r_scause() == 15){
    //新增
    uint64 va = r_stval();
    if (va >= p->sz)
      p->killed = 1;
    if (cowtrap(p->pagetable, va) != 0)
      p->killed = 1;
    // 新增结束
  } else if((which_dev = devintr()) != 0){
    // ok
  } else {
  	//...
  }
  // ...
}

int cowtrap(pagetable_t pagetable, uint64 va){
  uint64 addr;
  pte_t *pte = walk(pagetable, va, 0);
  if (pte == 0)
    return -1;
  // check the PTE
  if ((*pte & PTE_RSW) == 0 || (*pte & PTE_U) == 0 || (*pte & PTE_V) == 0) {
    return -1;
  }
  if ((addr = (uint64)kalloc()) == 0) {
    return -1;
  }
  // 获取旧页的物理地址
  uint64 pa = PTE2PA(*pte);
  // 拷贝旧页面内容到新页面
  memmove((char*)addr, (char*)pa, PGSIZE);
  //减少旧页面的引用计数
  kfree((void*)pa);
  uint flags = PTE_FLAGS(*pte);
  // 重新设置pte页表项
  *pte = (PA2PTE(addr) | flags | PTE_W);
  // 取消当前进程pte的PTE_RSW标志位
  *pte &= ~PTE_RSW;
  return 0;
}

四、实验结果

  • cowtest
    在这里插入图片描述
  • userstests -q
    在这里插入图片描述
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java根据前端所要格式返回树形3级层级数据

一、业务分析&#xff0c;根据前端需求返回如下数据格式 二、后端设计数据类型VO /*** author TTc* version 1.0* date 2024/2/15 16:47*/ Data AllArgsConstructor NoArgsConstructor public class Catalog2Vo {/*** 一级父分类的 id*/private String catalog1Id;/*** 三级子…

ForkJoin 的使用以及原理

原理 Fork-Join 是一种并行计算模式&#xff0c;它通常用于解决递归式或者分治式的问题。其原理基于将一个大的任务划分成若干个小任务&#xff0c;然后并行地执行这些小任务&#xff0c;最后将它们的结果合并起来得到最终的结果。 具体来说&#xff0c;Fork-Join 模式包含两个…

RK3399平台开发系列讲解(USB篇)U盘等存储类设备

🚀返回专栏总目录 文章目录 一、什么是U盘等存储类设备二、U盘设备传输数据结构三、U盘识别需要打开的宏沉淀、分享、成长,让自己和他人都能有所收获!😄 📢介绍U盘等存储类设备。 一、什么是U盘等存储类设备 USB Mass Storage Device Class(USB MSC/UMS) USB大容量存…

分享几个丝滑oled代码

最近一段业余时间在捣鼓esp32&#xff0c;发现对于一个搞diy的来说&#xff0c;它的生态&#xff0c;不管是开发环境、氛围还是可玩度都是独一挡的&#xff0c;国内外基于此的扩展真是太多了&#xff0c;找了几个通过按键/旋钮进行0.96寸OLED控制的案例&#xff0c;超级丝滑&am…

芯品荟|吉他屏驱应用介绍

PART ONE 市场简介 - Market Profile - 古典吉他与小提琴、钢琴并列为世界著名三大乐器。 目前&#xff0c;带屏成为吉他产品的新发展趋势。 核心应用 调音器、节拍器、录音器、效果、练习、循环乐段。 特色应用 4.3寸以下TFT屏 分辨率800*480以下 不带音弦按键替代&…

鸿蒙OS跨进程IPC与RPC通信

一、IPC与RPC通信概述 基本概念 IPC&#xff08;Inter-Process Communication&#xff09;与RPC&#xff08;Remote Procedure Call&#xff09;用于实现跨进程通信&#xff0c;不同的是前者使用Binder驱动&#xff0c;用于设备内的跨进程通信&#xff0c;后者使用软总线驱动…

对称密钥的分配、公钥的分配

目录 密钥分配 1 对称密钥的分配 KDC 对会话密钥 KAB 的分配 对称密钥分配协议&#xff1a;Kerberos 2 公钥的分配 认证中心 CA (Certification Authority) 数字证书 (digital certificate) 已签名的 B 的数字证书的产生过程 X.509 数字证书 认证系统 证书链 证书…

智慧农业一体化平台概述

智慧农业是以物联网为基础,以信息化技术为支撑通过对于科研、生产、物流、销售的各个农业生产环节的信息化管理,实现科学指导、高效生产、科学预测、精准销售、数据决策。因此,构建智慧农业一体化平台,完成对于农业科技管理、农业生产过程管理、农产品物流与商贸管理,从而…

记一个js原生 日期 时间 处理 格式化 对象 Intl 方法

具体对应搜搜。听说用空格分开能增加关键词搜到的概率 说起来最近好像越来越懒了

Quartz---基础

1.概述 Quartz是一个完全由Java编写的开源任务调度框架&#xff0c;通过触发器来设置作业定时运行规则&#xff0c;控制作业的运行时间。Quartz框架的主要核心组件包括调度器、触发器和作业。调度器作为作业的总指挥&#xff0c;触发器作为作业的操作者&#xff0c;而作业则为应…

有关光猫、路由器、交换机、网关的理解

前提 在了解计算机网络的过程中&#xff0c;出现了这四个名词&#xff1a;光猫、路由器、交换机、网络。有点模糊&#xff0c;查阅互联网相关资料&#xff0c;进行整理。如有错误&#xff0c;欢迎大家批评指正。 光猫 首先光猫是物理存在的&#xff0c;大家在家里应该都可以…

代码随想录day25--回溯的应用4

LeetCode491.非递减子序列 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;…

AI生图软件:让创意无限飞扬

随着科技的飞速发展&#xff0c;人工智能(AI)已经逐渐渗透到我们的日常生活之中&#xff0c;其中包括图像编辑。AI生图软件就是这样一种应用了AI技术的创新产品&#xff0c;它正在改变着图像编辑的方式&#xff0c;让我们能够以前所未有的方式创作和分享视觉内容。 一、什么是A…

300分钟吃透分布式缓存-01讲:业务数据访问性能太低怎么办?

这节课主要讲缓存的基本思想、缓存的优点、缓存的代价三个部分。 缓存的定义 先来看下缓存的定义。 & 缓存最初的含义&#xff0c;是指用于加速 CPU 数据交换的 RAM&#xff0c;即随机存取存储器&#xff0c;通常这种存储器使用更昂贵但快速的静态 RAM&#xff08;SRAM&…

七、Mybatis缓存

缓存就是内存中的数据&#xff0c;常常来自对数据库查询结果的保存&#xff0c;使用缓存、可以避免频繁的与数据库进行交互&#xff0c;进而提高响应速度一级缓存是sqlSession级别的缓存&#xff0c;在操作数据库时需要构造sqlsession对象&#xff0c;在对象中有一个数据结构&a…

前端技巧之svg精灵图svg-sprite-loader

首先说明精灵图的必要性&#xff0c;其可以让我们只需要向服务器请求一次图片资源&#xff0c;就能加载很多图片&#xff0c;即能够减轻http请求造成的服务器压力。 然后这里要说明的是这个插件是webpack上面的&#xff0c;所以在vue2中比较好用&#xff0c;如果在vue3中&…

C语言—字符数组(3)

可能不是那么的完整&#xff0c;先凑合看吧&#xff0c;如果我学会如何修改以后&#xff0c;我慢慢回来修改的 1.编写程序实现对两个字符串的连接功能&#xff1b; 法一:不使用strcat函数,写程序直接实现&#xff0c;记得添加结束符&#xff0c;不然程序访问数组时候将变得不…

Vue路由

Vue路由 一、路由的基本使用二、组件的存放目录问题三、路由的封装抽离四、声明式导航-导航链接五、声明式导航-查询参数传参六、Vue路由-重定向七、编程式导航-两种路由跳转方式八、编程式导航-两种路径跳转传参九、多级路由十、缓存组件 一、路由的基本使用 1.目标 认识插件…

算法学习系列(三十五):贪心(杂)

目录 引言一、合并果子&#xff08;Huffman树&#xff09;二、排队打水&#xff08;排序不等式&#xff09;三、货仓选址&#xff08;绝对值不等式&#xff09;四、耍杂技的牛&#xff08;推公式&#xff09; 引言 上一篇文章也说过了这个贪心问题没有一个规范的套路和模板&am…

《白话C++》第10章 STL和boost,Page73~74 boost::scoped_array

当所要创建的具体类型必须在运行时才能确定&#xff0c;此时需要使用new来实现动态创建&#xff1b; 另外还有一种&#xff1a;当需要一次性创建多个对象&#xff0c;但到底是几个无法在写代码时知道&#xff0c;需要在运行时动态创建&#xff0c;这种情况下也需要动态创建。此…