首页 > 其他分享 >xv6 lab5 cow

xv6 lab5 cow

时间:2023-02-07 18:25:27浏览次数:38  
标签:uint64 COW cow PTE xv6 pa void lab5 pte

21年好像没有懒分配,所以20年的lab6就成了21年的lab5

 

# cow实现 主要思想是增加一个标志位,一个引用标记。在中断处理时,进行懒复制,在write出错时再进行实际分配处理。

首先改uvmcopy,改原来标志位,将新的页也映射之前页的物理地址。

//kernel/riscv.h
//增加cow定义
define PTE_COW (1L << 8)
//kernel/vm.c
int
uvmcopy(pagetable_t old, pagetable_t new, uint64 sz)
{
  pte_t *pte;
  uint64 pa, i;
  uint flags;

  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_W 标志位,设置 PTE_COW 标志位表示是一个懒复制页(多个进程引用同个物理页)
    *pte = (*pte & ~PTE_W) | PTE_COW;
    flags = PTE_FLAGS(*pte);
    // 将父进程的物理页直接 map 到子进程 (懒复制)
    // 权限设置和父进程一致(不可写,PTE_COW)
    if(mappages(new, i, PGSIZE, (uint64)pa, flags) != 0){
      goto err;
    }
    // 将物理页的引用次数增加 1
    krefpage((void*)pa);
  }
  return 0;

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

判断中断,当是写中断,并且页可用且COW标志位有值时就是COW模式。且要保证没有超出原来的大小。

//kernel/trap.c

extern pte_t* walk(pagetable_t, uint64, int);
//中断判断
void
usertrap(void)
{
...
 } else if((which_dev = devintr()) != 0){
    // ok
  } else if((r_scause() == 15) && uvmcheckcowpage(r_stval())) { // copy-on-write
    if(uvmcowcopy(r_stval()) == -1){ // 如果内存不足,则杀死进程
      p->killed = 1;
    }
  } else {
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    p->killed = 1;
  }
...
}

// 这里没有放在vm中,因为vm中没有声明 proc 不想再整import,就放在这里了。记得walk要声明一下 
// 检查一个地址指向的页是否是懒复制页
int uvmcheckcowpage(uint64 va) {
  pte_t *pte;
  struct proc *p = myproc();
  
  return va < p->sz // 在进程内存范围内
    && ((pte = walk(p->pagetable, va, 0))!=0)
    && (*pte & PTE_V) // 页表项存在
    && (*pte & PTE_COW); // 页是一个懒复制页
}

// 实复制一个懒复制页,并重新映射为可写
int uvmcowcopy(uint64 va) {
  pte_t *pte;
  struct proc *p = myproc();

  if((pte = walk(p->pagetable, va, 0)) == 0)
    panic("uvmcowcopy: walk");
  
  // 调用 kalloc.c 中的 kcopy_n_deref 方法,复制页
  // (如果懒复制页的引用已经为 1,则不需要重新分配和复制内存页,只需清除 PTE_COW 标记并标记 PTE_W 即可)
  uint64 pa = PTE2PA(*pte);
  uint64 new = (uint64)kcopy_n_deref((void*)pa); // 将一个懒复制的页引用变为一个实复制的页
  if(new == 0)
    return -1;
  
  // 重新映射为可写,并清除 PTE_COW 标记
  uint64 flags = (PTE_FLAGS(*pte) | PTE_W) & ~PTE_COW;
  uvmunmap(p->pagetable, PGROUNDDOWN(va), 1, 0);
  if(mappages(p->pagetable, va, 1, new, flags) == -1) {
    panic("uvmcowcopy: mappages");
  }
  return 0;
}

在分配时要注意引用,如果引用为0了,就可以free了。如果不为0,就跳过。记得加锁,这种多线程没有锁是不可能的。

//kernel/kalloc.c
// 用于访问物理页引用计数数组

#define PA2PGREF_ID(p) (((p)-KERNBASE)/PGSIZE)
#define PGREF_MAX_ENTRIES PA2PGREF_ID(PHYSTOP)

struct spinlock pgreflock; // 用于 pageref 数组的锁,防止竞态条件引起内存泄漏
int pageref[PGREF_MAX_ENTRIES + 1]; // 从 KERNBASE 开始到 PHYSTOP 之间的每个物理页的
#define PA2PGREF(p) pageref[PA2PGREF_ID((uint64)(p))]

void
kfree(void *pa)
{
   struct run *r;

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

  acquire(&pgreflock);
  if(--PA2PGREF(pa) <= 0) {
    // 当页面的引用计数小于等于 0 的时候,释放页面

    // Fill with junk to catch dangling refs.
    // pa will be memset multiple times if race-condition occurred.
    memset(pa, 1, PGSIZE);

    r = (struct run*)pa;

    acquire(&kmem.lock);
    r->next = kmem.freelist;
    kmem.freelist = r;
    release(&kmem.lock);
  }
  release(&pgreflock);
}

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

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

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

void *kcopy_n_deref(void *pa) {
  acquire(&pgreflock);

  if(PA2PGREF(pa) <= 1) { // 只有 1 个引用,无需复制
    release(&pgreflock);
    return pa;
  }

  // 分配新的内存页,并复制旧页中的数据到新页
  uint64 newpa = (uint64)kalloc();
  if(newpa == 0) {
    release(&pgreflock);
    return 0; // out of memory
  }
  memmove((void*)newpa, (void*)pa, PGSIZE);

  // 旧页的引用减 1
  PA2PGREF(pa)--;

  release(&pgreflock);
  return (void*)newpa;
}

// 为 pa 的引用计数增加 1
void krefpage(void *pa) {
  acquire(&pgreflock);
  PA2PGREF(pa)++;
  release(&pgreflock);
}

一点心得

这里测试会报错,只要不是panic不运行就可以了。可能会出现进程的错误,如果看到了,就看看自己的代码有哪里错了,比如,if块中的内容有两行以上,忘了加大括号。或者r_scause() 写成r_sstatus()。

标签:uint64,COW,cow,PTE,xv6,pa,void,lab5,pte
From: https://www.cnblogs.com/yych0745/p/17099402.html

相关文章

  • POJ 3267 The Cow Lexicon
    TheCowLexiconTimeLimit: 2000MS MemoryLimit: 65536KTotalSubmissions: 11349 Accepted: 5432DescriptionFewknowthatthecowshavetheirowndictionar......
  • POJ3263 Tallest Cow 括号技巧
    题目描述FJ'sN(1≤N≤10,000)cowsconvenientlyindexed1..Narestandinginaline.Eachcowhasapositiveintegerheight(whichisabitofsecret).You......
  • 【P5196】【USACO19JAN】Cow Poetry G
    前言这是我们今天课上一道练习,结果全班就我一个过了。看到这道题我就有了思路(不过还是调了很久。Solution题意明白,不多赘述。首先考虑对于一行诗,凑足\(k\)个音节有......
  • P1472 奶牛家谱 Cow Pedigrees(DP)
    题目描述农民约翰准备购买一群新奶牛。在这个新的奶牛群中,每一个母亲奶牛都生两个小奶牛。这些奶牛间的关系可以用二叉树来表示。这些二叉树总共有N个节点(3<=N<200)......
  • P3119 [USACO15JAN]Grass Cownoisseur G 题解
    做过的原题,模拟赛时PDF里的题面实在有点难受。首先有显然结论:在一个环上反走一定是不值的,因为环上的点本来就相互可达。所以考虑缩点。缩点后的问题可以看成:求对于每一......
  • xv6 lab4
    RISC-VassemblyWhichregisterscontainargumentstofunctions?Forexample,whichregisterholds13inmain'scallto printf?a0-a7a2Whereisthecallto......
  • C++Day09 深拷贝、写时复制(cow)、短字符串优化
    一、std::string的底层实现1、深拷贝1classString{2public:3String(constString&rhs):m_pstr(newchar[strlen(rhs)+1]()){4}5private:6cha......
  • CS144-Lab5-ARP
    lab地址:lab5-doc代码实现:lab5-code完整目录:0.ByteStream1.StreamReassembler2.TCPReceiver3.TCPSender4.TCPConnection5.ARP6.IP-Router1.目标lab......
  • MIT 6.1810 Lab: Xv6 and Unix utilities
    lab网址:https://pdos.csail.mit.edu/6.828/2022/labs/util.htmlxv6Book:https://pdos.csail.mit.edu/6.828/2022/xv6/book-riscv-rev3.pdfBootxv6这部分主要完成系统的......
  • 第三章 页表(xv6 2021版)
    本文翻译自MTxv6|Chapter3| Pagetables3.1分页硬件3.2内核地址空间3.3代码解析:创建地址空间3.4物理内存分配3.5代码解析:物理内存分配器3.6进程地址空间......