基于RISC-V的Copy-On-Write

news2024/10/6 10:34:43

为什么需要写时拷贝呢?

shell执行指令的时候会 fork(),而这个 fork()出来的进程首先会调用的就是 exec来执行对应的命令,如果我们将 fork()创建的进程对地址空间进行了完整的拷贝,那将是一个巨大的消耗

因为在实际应用中,fork()拷贝的大部分内存都是不会用到的,最典型的就是在UNIX系统中,通常调用 fork()后便会调用 exec(),而 exec()做的第一件事就是把原来的地址空间给舍弃掉,那么原来拷贝过来的数据就全没用了

所以这个时候就需要copy-on-write机制

  • 这是一个系统级别的优化
  • 对于从 fork()->exec()的执行模式是一个很好的优化
    • 避免 fork()会把父进程的进程地址空间进行完全的拷贝
    • 解决因为 exec()把拷贝完的地址空间给舍弃,而造成我们无效的操作
  • 可以极大的减少拷贝
    • 对于特定的页使用极少的内存就可以维护

page fault的执行流程

page fault的执行流程和系统调用类似,同样是需要从用户态进入到内核态,并在usertrap中判断是什么原因导致的进入内核,
后执行对应的page fault处理方法,执行完再返回回到用户态,继续原来的操作


我们可以通过scause的值来判断是否为page fault导致的

从图中可知,scause=12,13,15的时候分别是instruction page fault,load page fault,store page fault

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xsULkO3b-1670655836900)(https://cdn.jsdelivr.net/gh/zevin02/picb@master/imgss/20221209190124.png)]

Copy-On-Write处理方法

  • fork之后,让子进程和父进程共享物理内存page,同时将对应的page设置为COW page

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XzgtMaqw-1670655836901)(https://cdn.jsdelivr.net/gh/zevin02/picb@master/imgss/20221210145755.png)]

  • 当我们需要修改某个进程对应的内存的时候,就会触发page fault
  • page fault 处理方法
    • 对于由于COW而导致的page fault的page 需要为其分配一个新的物理page
    • 将其和父进程共享的page内容拷贝到新的page中
    • 把新page的PTE标记为RW,取消COW

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p3cnstsA-1670655836902)(https://cdn.jsdelivr.net/gh/zevin02/picb@master/imgss/20221210145847.png)]

PTE

在RISC-V中的PTE,第8,9位是给supervisor保留的,按需自由设置,所以我们可以选取其中的一个位设置为COW的标志位

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kxNNIHV6-1670655836902)(https://cdn.jsdelivr.net/gh/zevin02/picb@master/imgss/20221210145939.png)]

我们可以使用PTE中的RSW bit来标记PTE为COW page

我们可以定义PTE_COW=1<<8

引用计数

Copy-On-Write是推迟为子进程开辟物理内存,最大程度减少拷贝的一个机制

一个物理页可能被多个进程所引用,为了能够使某个物理内存能够被释放,就需要对每个物理页都添加一个引用计数,表示有多少个进程引用了该物理页,直到该物理页的引用计数为0的时候,该物理页才能够被释放

在开辟物理页的时候,将其引用计数设置为1,当 fork()子进程的时候,增加引用计数,触发 page fault将对应的引用计数减1

虚拟地址空间

进程地址空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXB7578T-1670655836903)(https://cdn.jsdelivr.net/gh/zevin02/picb@master/imgss/20221210150015.png)]

子进程拷贝完父进程的页表之后,将每个页的PTE都设置清空 PTE_W,设置 PTE_COW

而对于上述的操作,会影响text
区,这一段也并没有 PTE_W,如果我们对其进行 COW操作就会出现问题

因为用户虚拟地址中的 text区是保存存储代码的,这块的虚拟地址不能发生COW,所以对于 va<PGSIZE,我们就需要直接拦截

核心代码

设置COW 标志位

#define PTE_V (1L << 0) // valid
#define PTE_R (1L << 1)
#define PTE_W (1L << 2)
#define PTE_X (1L << 3)
#define PTE_U (1L << 4) // user can access
#define PTE_COW (1L << 8) //判断COW page

修改fork的处理机制,修改 uvmcopy,使得fork的子进程印射到父进程的地址空间

int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)//将两个pagetable进行复制,两个不同的进程虚拟地址相同映射到页表的相同位置
{
  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");
    *pte&=(~PTE_W);//把w权限给取消掉
    *pte|=PTE_COW;//设置为cow页,这个操作并不会影响其父进程
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    // if((mem = kalloc()) == 0)
      // goto err;
    // memmove(mem, (char*)pa, PGSIZE);

    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      //  kfree((void*)pa);//减少引用计数

      goto err;
    }
    else
    {
      refadd(pa);//添加引用计数
    }
  }
  return 0;

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

修改 usertrappage fault进行识别与处理


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();

  ……………//省略
  //-------->begin
  else if (r_scause() == 15 || r_scause() == 13||r_scause()==12))
  {
    uint64 va = r_stval(); // 获得错误的虚拟地址
    if (va < PGSIZE)//解决segment fault,这些va小于pgsize都是在text区
    {
      p->killed = 1;
    }
  
    else
    {
      // va=PGROUNDDOWN(va);//16进制向下取整
      if (va < p->sz && iscow(p->pagetable, va)) // 是因为cow引起的page fault
      {
        if (cowalloc(p->pagetable, PGROUNDDOWN(va)) == 0) // 为新的页表分配内存
        {
          p->killed = 1;
        }
      }
      else
      {
        p->killed = 1;
      }
    }
  }
  //------->end
  …………//省略
  usertrapret();
}

标识COW page

int iscow(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;
  if ((*pte & PTE_U) == 0)
    return 0;
  if (*pte & PTE_COW)
    return 1;
  else
    return 0;
}

对产生 page faultva进行分配物理页


int cowalloc(pagetable_t pagetable, uint64 va) //为page fault的虚拟地址进行拷贝新的物理地址,内容从父进程里面全部拷贝过来
{
  if (va >= MAXVA)
    return 0;
  pte_t *pte = walk(pagetable, va, 0);
  if (pte == 0)
    return 0;
  if ((*pte & PTE_V) == 0)
    return 0;
  if ((*pte & PTE_U) == 0)
    return 0;
  //这个函数就是用来进行分配物理空间的
  uint64 pa = PTE2PA(*pte);
  if (((uint64)pa % PGSIZE) != 0 || (char *)pa < end || (uint64)pa >= PHYSTOP) //所有的物理地址大小都是4096字节,对齐,end是内核物理地址的最底段,PHYSTOP是内核物理地址的最顶端
    panic("cowalloc");
  if (ref.refcnt[(uint64)pa / PGSIZE] == 1)//引用计数为1的话,就说明已经没有人使用该page了,就可以直接使用了
  {
    *pte |= PTE_W;
    *pte &= ~PTE_COW;
    return 1;
  }
  else
  {

    uint64 ka = (uint64)kalloc(); //引用计数初始化
    if (ka == 0)                  //物理内存已经满了,这里我们采取简单的方法,直接将这个进程给杀掉,但是实际上在课上讲过,可以使用LRU的方法,把最近一直没有使用的页表给释放出来,然后新的进程去使用这个页表,可以提高效率
    {
      return 0;
    }
    memmove((void *)ka, (void *)pa, PGSIZE); //把他原来对应物理内存的地址进行拷贝过来,都是4096字节
    *pte &= (~PTE_COW);                      //取消他的cow标志位
    *pte |= PTE_W;                           //添加写权限
     *pte&=(~PTE_V);
    uint flag = PTE_FLAGS(*pte);
    // uvmunmap(pagetable, va, 1, 1);                      //这个地方因为是取消映射,也就是之前映射对应的物理地址对应的引用计数要减1
    if (mappages(pagetable, va, PGSIZE, ka, flag) != 0) //进行新的映射
    {
      //映射失败,同时页需要减少引用计数
      kfree((void *)ka);
      *pte|=(PTE_V);//添加这个有效的标志位
      // uvmunmap(pagetable, va, 1, 1);
      return 0;
    }
    kfree((void*)PGROUNDDOWN(pa));
    return 1;
  }

修改 copyout,有些COW页操作不是来自用户空间而是来自内核空间,因为 copyout是从内核态拷贝到用户态,是会对用户页产生写操作,而内核进程不会触发 usertrap,所以我们需要进行对其进行处理

int
copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)
{
  uint64 n, va0, pa0;

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    if(iscow(pagetable,va0))//每次都需要对这个va进行判断是否为cow page
    {
      if(cowalloc(pagetable,va0)==0)
      {
        return -1;
      }
    }
    ...//省略
  }
  return 0;
}

添加引用计数的结构体

每个页的起始地址都是4096对齐,所以都是可以被4096整除

这里的 KERNBASEPHYSTOP代表这内存物理地址的起始和结束,而 endFree memory的起始地址,这个 end是动态变化的,内核自己的代码和数据都是存在 kernel textkernel data中,所以这个结构体是存在 kernel data

struct
{
  struct spinlock lock;
  int refcnt[PHYSTOP / PGSIZE];//每个物理地址都是PGSIZE对齐
} ref;

修改 kinit,初始化ref结构体,kalloc只会分配 end~PHYSTOP间的内存,所以我们使用的物理内存都在这个范围内

void kinit()
{
  initlock(&kmem.lock, "kmem");
  initlock(&ref.lock, "ref");
  freerange(end, (void *)PHYSTOP);
}

修改 freerange,初始化空闲内存

void freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char *)PGROUNDUP((uint64)pa_start);
  for (; p + PGSIZE <= (char *)pa_end; p += PGSIZE)
  {
    ref.refcnt[(uint64)p / PGSIZE] = 1; //因为下面调用kfree要把每个物理地址上的引用计数都减少1,为0才能够释放空间,所以这里我们先给每个初始化成1,保证能够释放空间成功
    kfree(p);
  }
}

修改 kalloc,初始化引用计数

void *
kalloc(void)
{
  struct run *r;

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

  release(&kmem.lock);

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

修改 kfree,减少引用计数,直到引用计数为0时,才能把该物理页释放

void kfree(void *pa)
{

  if (((uint64)pa % PGSIZE) != 0 || (char *)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");
  struct run *r;
  acquire(&ref.lock);
  --ref.refcnt[(uint64)pa / PGSIZE];
  // release(&ref.lock);
  // Fill with junk to catch dangling refs.
  if (ref.refcnt[(uint64)pa / PGSIZE] == 0)
  {
    release(&ref.lock);
    memset(pa, 1, PGSIZE); //当引用计数为0的时候,才把这个空间释放,同时添加到空闲链表里面
    r = (struct run *)pa;
    acquire(&kmem.lock);
    r->next = kmem.freelist;
    kmem.freelist = r;
    release(&kmem.lock);
  }
  else
  {
    release(&ref.lock);
  }
}

增加引用计数

void refadd(uint64 pa) //添加引用计数
{
  if (((uint64)pa % PGSIZE) != 0 || (char *)pa < end || (uint64)pa >= PHYSTOP)
    panic("refadd");
  acquire(&ref.lock); //添加的时候要上锁,避免出现多线程同时操作同一个数的情况
  ref.refcnt[pa / PGSIZE]++;
  release(&ref.lock);
}

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

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

相关文章

微信小程序框架-全面详解(学习总结---从入门到深化)

小程序与普通网页开发的区别 小程序的主要开发语言是 JavaScript &#xff0c;小程序的开发同普通的网页 开发相比有很大的相似性。对于前端开发者而言&#xff0c;从网页开发迁移 到小程序的开发成本并不高&#xff0c;但是二者还是多少有些许区别的&#xff0c;例如&#xff…

HCIP实验 4-1:路由引入与路由控制

实验 4-1 路由引入与路由控制 学习目的 掌握OSPF与ISIS相互路由引入的配置方法掌握通过地址前缀列表过滤路由信息的配置方法掌握通过Route-policy过滤路由信息的配置方法 拓扑图 场景 你是你们公司的网络管理员。公司网络中有两部分路由区域&#xff0c;一部分运行OSPF,另外…

【Proteus仿真】【51单片机】厨房天然气泄漏检测报警系统

文章目录一、功能简介二、软件设计三、实验现象联系作者一、功能简介 本项目使用Proteus8仿真51单片机控制器&#xff0c;使用LCD1602、按键、天然气、烟雾传感器、ADC&#xff0c;报警模块等。 系统运行后&#xff0c;LCD1602显示传感器检测的天然气浓度和烟雾浓度值。 可通…

中国土地交易数据库:300w数据中国土地高频交易数据2000-2022

土地交易是土地在流通过程中多方发生的经济关系&#xff0c;土地交易的行为主要是交换的土地所有权、使用权、租赁权、抵押权等。在我国&#xff0c;土地作为一种重要资源&#xff0c;其收购储备和交易行为都由国家进行统一管理。经过改革开放几十年的探索和实践&#xff0c;土…

手机投影到电脑显示 此设备不支持miracast,因此不能以无线投影到它

在家里使用手机的体感游戏,发现手机屏幕比较小,想要将其投影到自己的笔记本电脑上,这样看得就比较大了。然后我就打开笔记本电脑,操作如下: 如下图: 原文地址:手机投影到电脑显示 此设备不支持miracast&#xff0c;因此不能以无线投影到它 - 廖强的博客 但是结果我们就看到了…

Mysql安装配置和Mysql使用六千字详解!!

目录 课前导读 一、Mysql的安装和配置 二、数据库简介&#xff1a; 1、数据库中典型代表&#xff1a; 2、数据库类型&#xff1a; 3、Mysql简介&#xff1a; 4、客户端和服务器简介&#xff1a; 三、初始MySQL 四、数据库操作 五、表的基本操作 六、表的基础增删查改…

虚拟主机、WordPress 主机和云主机之间的区别

&#x1f482; 个人网站:【海拥】【摸鱼游戏】【神级源码资源网站】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】&#x1f4ac; 免费且…

[附源码]JAVA毕业设计校园失物招领管理系统(系统+LW)

[附源码]JAVA毕业设计校园失物招领管理系统&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

港科夜闻|罗康锦教授获委任为香港科大工学院院长

关注并星标每周阅读港科夜闻建立新视野 开启新思维1、罗康锦教授获委任为香港科大工学院院长。该委任任期由2023年1月1日开始。罗康锦教授服务香港科大多年&#xff0c;是智慧交通系统、智慧城市和可持续发展的杰出学者&#xff0c;在学术研究方面屡获殊荣。罗教授拥有丰富的学…

阿里巴巴内部最新发布SpringCloud ALiBaBa全彩版

就在昨天&#xff0c;阿里巴巴发布了最新的SpringCloud ALiBaBa全解第三版同时也是全彩版&#xff0c;话不多说我们直接来看干货&#xff01; 由于文章的篇幅有限&#xff0c;下面只能为大家展示目录内容&#xff0c;需要领取完整版可以文末免费获取章节目录 微服务介绍 微服务…

Go 实现插入排序算法及优化

Go 实现插入排序算法及优化插入排序算法实现算法优化小结耐心和持久胜过激烈和狂热。 哈喽大家好&#xff0c;我是陈明勇&#xff0c;今天分享的内容是使用 Go 实现插入排序算法。如果本文对你有帮助&#xff0c;不妨点个赞&#xff0c;如果你是 Go 语言初学者&#xff0c;不妨…

python基于SVM的疫情评论情感数据分析

1、构建SVM情感分析模型 读取数据 使用pandas的库读取微薄数据读取并使进行数据打乱操作 import pandas as pd test pd.read_csv(".\\weibo.csv") test_data pd.DataFrame(test)[:1000] test_data 打乱数据 re_test_data test_data.sample(frac1).reset_index(…

代码随想录训练营第44天|完全背包、LeetCode 518. 零钱兑换 II、 377. 组合总和 Ⅳ

完全背包 有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品都有无限个&#xff08;也就是可以放入背包多次&#xff09;&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 完全背包和01背包问题唯一不同…

前端工程化实践——快速入门treeshaking

treeshaking treeshaking本质是去除多余代码算法。在前端的性能优化中&#xff0c;es6 推出了tree shaking机制&#xff0c;tree shaking就是当我们在项目中引入其他模块时&#xff0c;他会自动将我们用不到的代码&#xff0c;或者永远不会执行的代码摇掉&#xff0c;在…

【Java设计模式】用盖房子案例讲解建造者模式(生成器模式)

一、前言 今天学习了Java设计模式中的建造者模式&#xff0c;细心整理了学习笔记以及对这个模式的自我理解&#xff0c;希望大家喜欢&#xff01; 二、需求介绍 现在我们需要建房子&#xff0c;过程为打桩、砌墙、封顶。房子有各种各样的&#xff0c;比如普通房&#xff0c;…

【Java开发】 Spring 10 :Spring Boot 自动配置(Auto Configuration)原理及手动实现

用了这么久的 SpringBoot &#xff0c;我们再来回顾一下它&#xff0c;本文介绍 Spring Boot 的自动配置&#xff0c;这是它区别于 Spring 的最大的点&#xff0c;本文的自动配置项目包含三个项目&#xff0c;建议拉取仓库里的代码进行实践&#xff1a;尹煜 / AutoConfigDemo …

SOFA Weekly|MOSN v1.3.0 版本发布、公众号半自助投稿、本周 Contributor QA

SOFA WEEKLY | 每周精选 筛选每周精华问答&#xff0c;同步开源进展欢迎留言互动&#xff5e;SOFAStack&#xff08;Scalable Open Financial Architecture Stack&#xff09;是蚂蚁集团自主研发的金融级云原生架构&#xff0c;包含了构建金融级云原生架构所需的各个组件&#…

不接受反驳,性能最强,功能最强的Java日志框架

Logback 算是JAVA 里一个老牌的日志框架&#xff0c;从06年开始第一个版本&#xff0c;迭代至今也十几年了。不过logback最近一个稳定版本还停留在 2017 年&#xff0c;好几年都没有更新&#xff1b;logback的兄弟 slf4j 最近一个稳定版也是2017年&#xff0c;有点凉凉的意思。…

tep支持pytest-xdist分布式执行用例及合并Allure报告

tep近期更新频率较快&#xff0c;一方面是作者在积极投入到tep工具开发中&#xff1b;另一方面是我们聚集了20位小伙伴&#xff0c;一起合力打造EasyPytest测试平台&#xff0c;teprunner的FastAPI升级版本&#xff0c;依托于tep&#xff0c;帮你高效管理pytest测试用例。陆续也…

使用OpenGPT(ChatGPT)搭建 QQ 机器人

本教程来自&#xff1a;OpenGPT搭建QQ机器人-憨憨博客 有问题可来我博客询问&#xff1a;我的博客 准备 一个服务器&#xff1a;Windos&#xff0c;Centos&#xff0c;Ubuntu 环境&#xff1a;Python 一个 QQ 号用作机器人 一个 OpenAI 账号 (注册教程自行搜索) 搭建 这里我用…