惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

文章列表

游戏玩后感:ReLief:献给亲爱的你 我的周边(谷子)分享 游戏玩后感:Kanon 简谱:致真实的你 《Rust中常见的有关生命周期的误解》学习笔记 简谱:StarMap 简谱:かく咲きたらばいと恋ひめやも 简谱:东风 简谱:无法诉说的思念 简谱:Girlish 游戏玩后感:时钟机关的Layline 简谱:风之琶音 简谱:星空的记忆 简谱:因为遇见了你 简谱:月童 番茄简谱脚本转调器 游戏玩后感:青空下的约定:Refine 游戏玩后感:在这苍穹展翅 书籍读后感:控制论与科学方法论 游戏玩后感:恋爱表达式 游戏玩后感:樱之诗 MLIR-tutorial学习笔记 游戏玩后感:潜伏之赤途 游戏玩后感:纯爱咖啡厅:帕露菲重制版 游戏玩后感:智以泪聚 游戏玩后感:初雪樱 游戏玩后感:告别回忆:从今以后 游戏玩后感:梦灯花 游戏玩后感:金辉恋曲四重奏 游戏玩后感:五彩斑斓的世界 昇腾310P使用记录 游戏玩后感:AIR 游戏玩后感:弹丸论破 游戏玩后感:流景之海的艾佩莉亚 Xilinx_HLS上板过程记录 游戏玩后感:告别回忆2 游戏玩后感:恋爱绮谭 Faiss和Rapidsai_Raft使用记录 游戏玩后感:近月少女的礼仪 游戏玩后感:樱色之云,绯色之恋 游戏玩后感:幸运草的约定 游戏玩后感:星之梦、候鸟和丸子与银河龙 游戏玩后感:白色相簿2 Windows上使用VTune分析PyTorchExtension调用的Cpp程序 SpinalHDL上板过程记录 游戏玩后感:仰望夜空的星辰 最简单的算卦方法之一:梅花易数法 游戏玩后感:苍之彼方的四重奏 krkr引擎解包工具介绍 自定义CUDA实现PyTorch算子的四种简单方法 游戏玩后感:星空的记忆 游戏玩后感:9nine 游戏玩后感:AtriMyDearMoments 游戏玩后感:极限脱出 游戏玩后感:魔女的夜宴 SSH实现多跳代理 动漫观后感:向山进发 flv重封装H264、AAC流 动漫观后感:夏日重现 CSP模板 游戏玩后感:海沙风云 动漫观后感:灵能百分百 游戏玩后感:交响乐之雨 游戏玩后感:爱上火车LastRun 游戏玩后感:LittleBustersEX 游戏玩后感:SummerPockets 游戏玩后感:逆转裁判 Ultra96V2开发板简单使用 SpinalWorkshop实验笔记(三) SpinalWorkshop实验笔记(二) SpinalWorkshop实验笔记(一) PYNQ开发板上使用USB声卡+OSS兼容层播放音频 TestOS移植K210开发板 rCore-Tutorial-Book-v3学习笔记(七) 动漫观后感:凉宫春日的忧郁 rCore-Tutorial-Book-v3学习笔记(♭七) rCore-Tutorial-Book-v3学习笔记(六) rCore-Tutorial-Book-v3学习笔记(五) rCore-Tutorial-Book-v3学习笔记(四) rCore-Tutorial-Book-v3学习笔记(三) rCore-Tutorial-Book-v3学习笔记(二) rCore-Tutorial-Book-v3学习笔记(一) 游戏玩后感:RewritePlus MIT-6.S081-2020实验(xv6-riscv64)十一:net MIT-6.S081-2020实验(xv6-riscv64)十:mmap MIT-6.S081-2020实验(xv6-riscv64)九:fs MIT-6.S081-2020实验(xv6-riscv64)八:lock MIT-6.S081-2020实验(xv6-riscv64)七:thread MIT-6.S081-2020实验(xv6-riscv64)五:lazy MIT-6.S081-2020实验(xv6-riscv64)四:traps MIT-6.S081-2020实验(xv6-riscv64)三:pgtbl MIT-6.S081-2020实验(xv6-riscv64)二:syscall 动漫观后感:吹响吧上低音号 MIT-6.S081-2020实验(xv6-riscv64)一:util 快速生成网络mp4视频缩略图技术 用plantuml画图示例 QQ缩略图和大图不同实现 Python制作字符图片 动漫观后感:命运石之门 Unity3D+Post Processing Stack V2自定义后处理效果研究
MIT-6.S081-2020实验(xv6-riscv64)六:cow
VnYzm · 2021-01-06 · via

实验文档

概述

这次实验实现copy on write功能,和上次实验一样也是缺页中断的应用,但不同的是,这次实验涉及的物理内存和虚拟地址的操作要比上个实验多不少,因此难度也更大一些。

内容

首先是uvmcopy的部分,原来的操作是从老页表中获得虚拟地址对应的物理地址,创建一个新物理页,然后将老物理地址的内容复制到新物理页,再把新物理页通过新页表映射到虚拟地址,现在就要改成直接将老物理地址通过新页表映射到虚拟地址,同时需要将老页表和新页表对应底层pte抹去PTE_W为并添加PTE_C位,这里的PTE_C是我自己定义的一个标志位。根据Riscv的标准,pte的低10位作为标志位,其中的0-7位是包括PTE_V、PTE_W之类已经被用掉的标志位,8-9位是可供用户自定义使用的标志位,这里我选取第8位,即PTE_C = 1L << 8。0表示该pte没有用在copy on write中,1表示有,这样在处理缺页中断的时候就比较方便了,只要该pte的PTE_C位为0,说明这次缺页中断的原因不是copy on write,而是真的缺页,就可以直接返回错误:

  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);
    *pte &= ~PTE_W;
    *pte |= PTE_C;
    flags = PTE_FLAGS(*pte);
    // if((mem = kalloc()) == 0)
    //   goto err;
    // memmove(mem, (char*)pa, PGSIZE);
    if(mappages(new, i, PGSIZE, pa, flags) != 0) goto err;
    add_count(pa);
  }
  return 0;

 err:
  uvmunmap(new, 0, i / PGSIZE, 1);
  panic("uvmcopy: map page failed");
  return -1;

这里我映射失败就直接让程序panic了,因为如果真的要处理的话还得把之前所有新老页表里的底层pte的标志位改回去,事实上我也想不出mappages失败且内部没有panic的情况。

然后是缺页中断处理,和上次实验一样,单独抽象成一个函数,主要过程就是获取缺页的物理地址,如果物理地址不存在或者PTE_C不为1就返回错误,然后创建新页,把老页的内容复制过来,并修改新页对应pte的标志位,注意下一步需要尝试释放老页,防止内存泄漏。这里的“尝试释放”指的是让老页的引用计数-1,如果引用计数为0了就真的释放。“尝试释放”的过程直接就写在kfree函数里面,因为加入copy on write机制后,所有对物理内存的操作都需要受引用计数的制约:

int handle_page(uint64 va, pagetable_t pgtbl) {
    pte_t *pte; char *mem; uint flags;
    if ((pte = walk(pgtbl, va, 0)) == 0) return -1;
    if ((*pte & PTE_C) == 0) return -1;
    if ((mem = kalloc()) == 0) return -1;
    flags = PTE_FLAGS((*pte & (~PTE_C)) | PTE_W);
    uint64 pa = PTE2PA(*pte);
    memmove(mem, (char*)pa, PGSIZE);
    *pte = PA2PTE((uint64)mem) | flags;
    kfree((void *)pa); return 0;
}

然后就是copyout函数的修改,为什么不需要修改copyin和copyinstr函数呢,因为fork涉及的都是用户区的内存,所以缺页也只会在写用户内存的情况下发生,copyout是内核内存写到用户内存,所以需要处理,另外两个函数是用户内存写到内核内存,是读用户内存,所以不需要处理。另一个和上次实验不同的地方是,上次实验之所以copyout函数需要修改,是因为在对虚拟地址调用walkaddr函数的时候,因为实际的物理地址不存在,所以返回错误,因此只要在walkaddr返回不存在的物理地址时进行缺页处理即可;而这次实验walkaddr是可以得到合法的物理地址的,只是这个物理地址不能被写,所以错误会在memmove到这个物理地址的时候才发生,而且这个缺页中断是在内核态发生的,走的也是kerneltrap函数,因此我们定义在usertrap函数里的处理代码捕获不到它。因此我们要做的,就是在调用walkaddr函数后对pte进行检查,如果PTE_C位为1,则进行缺页处理。

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    pa0 = walkaddr(pagetable, va0);
    if(pa0 == 0)
      return -1;
    if (*(walk(pagetable, va0, 0)) & PTE_C) handle_page(va0, pagetable);
    pa0 = walkaddr(pagetable, va0);
    ......

这里我的代码写的比较矬,为了检查标志位还重新walk一遍,最后要获得新物理地址又walkaddr一遍,实际上可以定义另一个版本的walkaddr直接返回pte,handle_page也可以改写让其返回新物理地址,后面有时间再改。trap.c的代码和上次实验几乎一样,就不贴了。

然后是物理内存的处理,为了节省空间,我没有直接用物理地址模4096,而是先将物理地址减掉内核的地址空间,再模4096,因为fork不涉及物理内存,即数组索引为(pa - KERNBASE) >> PGSHIFT,当然代价就是每次进行处理引用计数的时候需要先判断pa必须大于等于KERNBASE,不然内核申请或释放物理内存的时候一减变成负数,就访问非法内存了。数组大小就可以根据memlayout.h里的值进行计算,发现物理内存的最大值PHYSTOP减KERNBASE等于128*1024*1024`,因此总页数为128*1024/4=32768,这就是数组的大小。另外很重要的一点是引用数组的声明:

struct {
    struct spinlock lock;
    uint a[32768];
} count;

需要用到锁,这个实验文档没讲,略坑,我也是看了别人的代码才知道,不用锁的话会内存泄漏,应该是多进程竞争扰乱了引用计数的加减,目前还没看到xv6文档里关于锁的部分,所以也不知道哪些地方可能产生资源竞争。了解这一点后面就很容易了,kfree函数:

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

  if ((uint64)pa >= KERNBASE) {
      acquire(&count.lock);
      if (count.a[((uint64)pa - KERNBASE) >> PGSHIFT] > 1) {
          count.a[((uint64)pa - KERNBASE) >> PGSHIFT]--;
          release(&count.lock); return;
      } else {
          count.a[((uint64)pa - KERNBASE) >> PGSHIFT] = 0;
          release(&count.lock);
      }
  }

alloc函数里直接在返回物理地址前使用add_count函数让计数加1(初始时和释放后引用计数都为0,所以加1后就是1),这里代码不贴了,add_count函数:

void add_count(uint64 pa) {
  if (pa >= KERNBASE) {
      acquire(&count.lock);
      count.a[(pa - KERNBASE) >> PGSHIFT]++;
      release(&count.lock);
  }
}