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

推荐订阅源

T
Troy Hunt's Blog
人人都是产品经理
人人都是产品经理
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Microsoft Security Blog
Microsoft Security Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - Franky
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Martin Fowler
Martin Fowler
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Microsoft Azure Blog
Microsoft Azure Blog
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
WordPress大学
WordPress大学
腾讯CDC
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
博客园 - 司徒正美
博客园 - 【当耐特】
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
宝玉的分享
宝玉的分享
Google DeepMind News
Google DeepMind News
罗磊的独立博客
Schneier on Security
Schneier on Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
量子位
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
O
OpenAI News
TaoSecurity Blog
TaoSecurity Blog
H
Heimdal Security Blog
Blog — PlanetScale
Blog — PlanetScale
Forbes - Security
Forbes - Security
B
Blog
I
InfoQ
D
DataBreaches.Net
月光博客
月光博客
T
Tenable Blog
S
Security Affairs
C
Check Point Blog
T
Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
www.infosecurity-magazine.com
www.infosecurity-magazine.com

VnYzm的博客

游戏玩后感: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制作字符图片 动漫观后感:命运石之门 使用OpenCV对图片进行特征点检测和匹配
MIT-6.S081-2020实验(xv6-riscv64)六:cow
VnYzm · 2021-01-06 · via VnYzm的博客

实验文档

概述

这次实验实现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);
  }
}