2021 XV6 5:Copy-on-Write Fork

news2025/7/19 10:17:19

目录

1.概述

2.修改uvmcopy

3.修改trap.c

4.引用计数机制

5.修改copyout

6.结果


1.概述

首先,这是一个很有意义的性能优化方案。

提出的背景是,如果我们每次fork的时候,都完整分配一系列物理页把父进程的内容拷贝进来,是一种十分不明智的行为。不仅浪费空间,而且还耗费调用fork时的时间。

那么解决这种问题的方法就是COW,一种写时复制的机制,思路如下:

1.在fork的时候,不要求申请新的内存空间,而是把原来的物理页映射到用户页表里面,但是我们打上不可写和COW两个标记位,这样,在下一次写必要的原来可写的页的时候,就会触发一个异常。具体异常和exception code对应关系如下,我们在这里触发的是15: 

2.触发异常后,我们就知道,原来这个页原来是可写的,但是现在打上了COW的标记位,那么我们在这个时候再分配一个物理页来给程序,并且修改原来的页表项,恢复写的标记,除去COW标记位,并且把新的物理页映射进去。

虽然看起来简单,但是实现起来还是有一些细节的,以下是具体实现。

2.修改uvmcopy

 在xv6里面的fork里面调用uvmcopy来让子进程复制父进程的物理空间,原来的方法是分配了一个和父进程一样大小的物理空间。

但是我们现在要COW,所以把原来的页表项拿出来,改一下标志位,具体来说就是去掉write标志位,这可以引发异常便于我们后续分配物理页,加上COW标记,可以在COW分配物理页时检查是否合法。

具体代码如下所示:

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");
    // 清空页表项中的PTE_W
    *pte &= ~PTE_W;
    *pte |= PTE_COW;
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    // 这里没有申请新的物理页 没有必要释放pa
    // 将原来的物理页直接映射到子进程的页表中 标志位设置不可写 COW
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      goto err;
    }
    addref(pa);
    // 
    // 注释分配一个物理页的代码
    // if((mem = kalloc()) == 0)
    //   goto err;
    // memmove(mem, (char*)pa, PGSIZE);
    // if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){
    //   kfree(mem);
    //   goto err;
    // }
  }
  return 0;

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

 其中addref之后在引用计数的机制里边说。

3.修改trap.c

 具体来说我们在下一次写一个COW页的时候会触发15号异常,我们在trap.c里的usertrap里边修改一个处理的代码:

else if(r_scause() == 15){// Store/AMO page fault
    // 取出无法翻译的地址
    // printf("cow\n");
    uint64 va=r_stval();
    if(handler_cow_pagefault(p->pagetable, va)<0){
      //杀死进程
      p->killed=1;
    }

我在vm.c里面实现了这个函数的定义,这样对于访问类似于walk函数啥的会更加方便(因为walk没有对外提供),而且对外提供一个封装接口:

// 取出COW的物理页
// 分配一个新的页映射到进程页表中 并且将原来的物理页拷贝到新的页中
// 修改页表项的flag 除去COW位 加上write标志位
int
handler_cow_pagefault(pagetable_t pagetable, uint64 va)
{
  // 取出原来无法翻译的va地址
  if (va >= MAXVA)
    return -1;
  
  pte_t *pte=walk(pagetable, va, 0);

  if (pte == 0 || (*pte & PTE_COW)==0)
    return -1;

  uint64 pa=PTE2PA(*pte);
  
  // 分配一个新的物理页 将原来物理页中内容拷贝至新物理页
  pagetable_t new_page=(pte_t*)kalloc();
  if (new_page==0)
    return -1;
  
  memmove((char*)new_page, (char*)pa, PGSIZE);
  // 减少原来物理页引用计数
  kfree((void*)pa);
  // 将新的物理页映射至页表中 去除COW位 加上write位
  pte_t flags=PTE_FLAGS(*pte);
  flags &= ~PTE_COW;
  flags |= PTE_W;
  // printf("In cow set:   cow:%d  w:%d\n",flags&PTE_COW, flags&PTE_W);
  *pte=PA2PTE(new_page) | flags;
  return 0;
}

4.引用计数机制

我们这里说是要COW,但是我们细想一下,采用COW会带来什么问题?

我们在释放一个页的时候,由于是COW,是不是可能有多个进程可能共享一个一样的映射,那么我们每个进程释放一个页的时候都把这个页放到空闲链表里边不是炸了吗。

所以我们需要解决这个问题,最直接的方法是,给每个页一个引用计数,当这个计数从1变成0的时候,我们就把这个页放到空闲页链表里边,如果是大于1的,我们只需要减小引用计数就行,如果等于0或者小于0,直接panic。

说起来简单,做起来还有一些细节要处理。

首先是数据结构,不要以为一个数组就完事了,由于多核多进程,我们得给这个数组加上一个全局锁,避免数据竞争。其次数组大小是多少呢?就是(PHYSTOP-KERNBASE)/PGSIZE,这里也可以看出来我们如何把一个物理页映射到数组的下标的,把上面的PHYSTOP换成物理页物理地址就行了。

以下是数据结构的定义:

#define INDEX(pa) ((pa - KERNBASE)>>12)
#define INDEXSIZE 32768//由KERNBASE到PHYSTOP共32768个页面

struct {
  struct spinlock lock;
  int count[INDEXSIZE];
} memref;

然后在初始化的时候,我们得把引用计数变成1,变成1的原因是,在init的时候调用freerange,里面用的kfree,会把页面释放然后放到空闲链表里,然后页表引用计数就变成0了。

初始化如下所示:

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

void
freerange(void *pa_start, void *pa_end)
{
  char *p;
  p = (char*)PGROUNDUP((uint64)pa_start);
  for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE)
  {
    // printf("%d\n",INDEX((uint64)p));
    acquire(&memref.lock);
    memref.count[INDEX((uint64)p)]=1;
    release(&memref.lock);
    kfree(p);
  }
    // kfree(p);
}

这里要注意的就是别memset(memref.count,1,sizeof(...)),由于memset是按照字节分配的,这里要是这样写就直接变成010101了,直接炸掉。

接下来是kalloc,我们在成功分配一个页的时候只要把引用计数加一就好了:

void addref(uint64 pa)
{
  acquire(&memref.lock);
  memref.count[INDEX(pa)]++;
  release(&memref.lock);
}


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

  acquire(&kmem.lock);
  r = kmem.freelist;
  if(r)
  {
    kmem.freelist = r->next;
    addref((uint64)r);
  }
  release(&kmem.lock);

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

最后是麻烦的kfree,首先我们理一下思路:

1.检查这个页面的引用计数是否大于1,如果是,直接调整引用计数然后返回

2.检查这个页面的物理地址范围。

3.对引用计数是1和0分别处理。

其实这个0应该放在最开始处理的,但是后来想了想,都是0,panic了,用原来的代码清空一遍也没啥影响了,但是还是要panic。

代码如下所示:

void
kfree(void *pa)
{
  struct run *r;
  // printf("In kfree:1  %d ref:%d\n",INDEX((uint64)pa),memref.count[INDEX((uint64)pa)]);

  acquire(&memref.lock);
  if (memref.count[INDEX((uint64)pa)]>1)
  {
    --memref.count[INDEX((uint64)pa)];
    release(&memref.lock);
    return;
  }
  release(&memref.lock);
  // printf("In kfree:2\n");

  if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP)
    panic("kfree");

  // Fill with junk to catch dangling refs.
  memset(pa, 1, PGSIZE);

  r = (struct run*)pa;

  acquire(&memref.lock);
  if (memref.count[INDEX((uint64)pa)]==0)
  {
    release(&memref.lock);
    panic("kfree");
  }
  else if (memref.count[INDEX((uint64)pa)]==1)
  {
    --memref.count[INDEX((uint64)pa)];
    release(&memref.lock);
    acquire(&kmem.lock);
    r->next = kmem.freelist;
    kmem.freelist = r;
    release(&kmem.lock);
  }
  else
  {
    release(&memref.lock);
    panic("kfree");
  }
}

5.修改copyout

首先我们要知道为什么要改copyout,因为我们在把内核数据复制到用户空间的时候,也就相当于在写用户的物理地址,如果这个时候对应的页表项是写时复制的,我们应该也做一次新的物理页的分配,也就是之前的handler_cow_pagefault函数。

注意,这里一定要先检查是否是COW的页面,如果不是,去直接调用这个函数是不合理的。

这里前面还得检查一下虚拟地址是否合法,不然在usertests里边会炸掉。

代码如下所示:

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

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pte_t *pte;
    if (va0 >= MAXVA)
    {
      return -1;
    }
    
    if ((pte=walk(pagetable,va0,0))==0)
    {
      return -1;
    }
    if (*pte & PTE_COW)
    {
      if(handler_cow_pagefault(pagetable, va0)<0)
      {
        return -1;
      }
    }
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    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;
}

6.结果

cowtest:

 usertests:前面太长了

 

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

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

相关文章

【SpringBoot】定制⾃⼰的 Health Indicator

Spring Boot ⾃带的 Health Indicator ⽬的 检查应⽤程序的运⾏状态 状态 DOWN - 503OUT_OF_SERVICE - 503UP - 200UNKNOWN - 200 机制 通过 HealthIndicatorRegistry 收集信息HealthIndicator 实现具体检查逻辑 配置项 management.health.defaults.enabledtrue|falsem…

【学习笔记】Reids的哨兵机制

【学习笔记】Reids的哨兵机制 文章目录【学习笔记】Reids的哨兵机制前言什么是哨兵机制&#xff1f;如何判断主库是否挂掉&#xff1f;主从库的切换和消息的通知前言 什么是哨兵机制&#xff1f; 哨兵机制&#xff08;sentinel&#xff09; 是Redis解决高可用的一种解决方案&a…

视频会议解决方案-最新全套文件

视频会议解决方案-最新全套文件一、建设背景二、建设思路业务挑战三、建设方案四、获取 - 视频会议全套最新解决方案合集一、建设背景 随着中国经济的迅速发展&#xff0c;很多企业的发展也进入快车道&#xff0c;分支机构越来越多&#xff0c;形成了遍布全国范围甚至全球范围…

Echarts:惊艳的Map

大家在网上看天气预报的时候应该就看到过在中国地图上显示不同省份的温度&#xff0c;并根据温度的高低显示不同的颜色&#xff0c;又或者看到各个省份的新冠肺炎新增人数&#xff0c;根据不同的新增人数显示不同的颜色之类的图像。这些&#xff0c;使用echarts中的map就可以实…

python实现灰色关联法(GRA)

原文&#xff1a;https://mp.weixin.qq.com/s/Uuri-FqRWk3V5CH7XrjArg 1 灰色关联分析法简介 白色系统是指信息完全明确的系统&#xff0c;黑色系统是指信息不完全明确的系统&#xff0c;而灰色系统是介于白色与黑色系统之间的系统&#xff0c;是指系统内部信息和特征是部分已…

IDEA设置和相关快捷键记录汇总

IDEA设置和相关快捷键 前言 Java 开发 现在基本都是使用 IDEA 作为开发工具&#xff0c;IDEA 有很多设置和常用的快捷键&#xff0c;熟悉之后能更好的提高开发效率&#xff0c;这里总结了下从慕课网的内容 开发工具IDEA从入门到爱不释手-慕课网 (imooc.com)&#xff0c;摘录做…

Kafka - 04 Java客户端实现消息发送和订阅

1. Kafka测试命令行操作 1. 主题命令行操作 在上一节中我们安装了Kafka单机环境和集群环境&#xff0c;这一节来测试下Linux环境安装Kafka后的命令行操作。 我们之前在用Windows环境安装Kafka Kafka应用场景|基础架构|Windows安装|命令行操作 和命令行操作时&#xff0c;讲到…

哪种类型的蓝牙耳机好?超高性价比蓝牙耳机推荐

朋友让我推荐蓝牙耳机的时候&#xff0c;总是喜欢问哪款蓝牙耳机的性能更强&#xff0c;想要直接入手那款性能更强的蓝牙耳机&#xff0c;以此节省对比的时间。但是用户自行进行对比的步骤&#xff0c;显然是不能省的&#xff0c;所以推荐这四款高性价比的蓝牙耳机&#xff0c;…

华为云桌面Workspace,让你的办公更加舒适惬意

在各行各业转型的过程中&#xff0c;企业对于线上办公的需求不断增多&#xff0c;越来越需要一个云办公平台&#xff0c;为企业更好实现数字化网络化办公降本增效。正逢佳节之际&#xff0c;在此为各大企业推荐一个高效的办公神器——华为云桌面Workspace。相信作为企业决策者的…

详解设计模式:抽象工厂模式

工厂方法模式&#xff0c;又称工厂模式、多态工厂模式和虚拟构造器模式&#xff0c;通过工厂父类定义负责创建产品的公共接口&#xff0c;子类负责生产具体对象。可以理解为简单工程模式的升级&#xff0c;解决简单工厂模式的弊端。 &#xff5e; 本篇内容包括&#xff1a;关于…

Executors-四种创建线程的手段

1 Executors.newCachedThreadPool() 从构造方法可以看出&#xff0c;它创建了一个可缓存的线程池。当有新的任务提交时&#xff0c;有空闲线程则直接处理任务&#xff0c;没有空闲线程则创建新的线程处理任务&#xff0c;队列中不储存任务。线程池不对线程池大小做限制&#x…

ESP三相SVPWM控制器的simulink仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB程序 1.算法描述 SVPWM则以三相的合成矢量为出发点&#xff0c;其基本思想为&#xff1a;在数学意义上的abc轴也好&#xff0c;αβ轴也好&#xff0c;其产生的电压都应该等于dq轴合成的那个电压。那么只要让…

swiper在动态创建dom过程中的问题:数据从后端请求回来后加载到页面上,dom加载完发现swiper没用了

怎么动态创建div标签&#xff1a; 要轮播的数据是后端返回的&#xff0c;所以我们要发ajax请求接收数据&#xff1b; 下面演示的是已经接收回来的数据&#xff0c;动态创建div标签&#xff1a; setTimeout(()>{var list ["aaa","bbb","ccc&quo…

【Redis】从计算机组成原理开始讲解为什么会出现Redis

文章目录前置知识数据库的出现Redismemcache与redis的区别前置知识 首先需要知道的一个常识就是&#xff1a;数据是存放在磁盘里面的。 而磁盘有两个指标&#xff1a; 寻址&#xff1a;表示找到对应的数据所需要的时间&#xff0c;ms带宽&#xff1a;表示单位时间可以有多少个…

Python排序:冒泡排序,选择排序,插入排序,希尔排序

编程中的交换元素逻辑&#xff1a; # python中交换元素 有内置的三方底层逻辑 可以直接交换 a 2 b 3 a, b b, a print(a) # a为3# 其他编程需要有一个中间的变量来转换 变量设为temp a 2 b 3 temp a a b b temp print(a) # a为3 -----冒泡排序----- 相邻…

openfeign原理

openfeign原理 EnableFeignClients注解启用Feign客户端&#xff0c;通过Import注解导入了FeignClientsRegistrar类加载额外的Bean。FeignClientsRegistrar实现了ImportBeanDefinitionRegistrar接口&#xff0c;在Spring启动过程中会调用registerBeanDefinitions方法注册BeanDe…

自动化项目倍加福WCS-PG210E使用GSD文件

1&#xff0e;硬件电气连接 WCS-PG210E WCS3B WCS2B Pin 颜色 Pin 颜色 24V UB 1 BN棕色 2 WH白色 0V GND 3 BU蓝色 3 BU蓝色 RS485- RS485- 4 BK黑色 1 BN棕色 RS485 RS485 2 WH白色 4 BK黑色 保留 5 GY灰色 5 GY灰色 2. 安装W…

Nginx (4):nginx动静分离

什么是动静分离不解释了&#xff0c;网上说的很清楚&#xff0c;这里只说配置 目的 02虚拟机运行一个tomcat&#xff0c;处理动态请求&#xff0c;而对静态文件的访问则交给01虚拟机。操作 下面是01虚拟机的配置文件内容&#xff1a; server {listen 82;listen [::]:82;#root /…

六、nacos环境隔离、服务配置拉取和多环境配置共享

文章目录一、环境隔离-namespace1.namespace理解2.创建命名空间二、Nacos-实现配置管理三、nacos-实现服务配置拉取1.非热更新2.热更新&#xff1a;四、实现多环境配置共享1.开发环境&#xff1a;2.测试环境3.结论一、环境隔离-namespace 1.namespace理解 Nacos中服务存储和数…

Element Plus 组件库相关技术:7. 组件实现的基本流程及 Icon 组件的实现

前言 本章节我们将要实现 Icon 组件&#xff0c;Icon 组件应该是所有组件里面最简单的一个组件了&#xff0c;所以我们由简入深&#xff0c;循序渐进进行学习。Icon 组件虽然简单&#xff0c;但它却包含了一个组件的全部基础流程&#xff0c;通过实现 Icon 组件进行理解 Eleme…