国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6

這篇具有很好參考價值的文章主要介紹了6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

本來往年這里還有個Lazy Allocation的,今年不知道為啥直接給跳過去了。.

其他篇章

環(huán)境搭建
Lab1: Utilities
Lab2: System calls
Lab3: Page tables
Lab4: Traps
Lab5: Copy-on-Write Fork for xv6

參考鏈接

官網鏈接
xv6手冊鏈接,這個挺重要的,建議做lab之前最好讀一讀。
xv6手冊中文版,這是幾位先輩們的辛勤奉獻來的呀!再習慣英文文檔閱讀我還是更喜歡中文一點,開源無敵!
個人代碼倉庫
官方文檔

1. 簡單分析

寫時拷貝(Copy On Write)技術之前在15445也寫過了,這里再簡單介紹一下。我們知道,fork的過程有一條就是子進程會拷貝父進程的內存空間,但是這個拷貝是有一定開銷的,尤其是在需要拷貝的東西多的時候更明顯。但是這就引出了一個問題——我們真的需要去拷貝嗎?很顯然,從邏輯上來看,只有父進程或子進程對內存空間有修改時,這種拷貝才是有意義的,否則只是徒增開銷而已。依此便提出了COW思想——我們將拷貝的時機推遲到某個進程修改內存的時候,這樣就可以優(yōu)化掉很多無必要的開銷。

落實到實現(xiàn)策略上,Lab文檔為我們描述了一種方案——平時fork我們只需要為父子進程添加一個指向原始頁面的指針即可,這個頁面將被標記為只讀。這樣當父進程或子進程嘗試寫入頁面時,就會觸發(fā)page fault(這應該算異常吧),這個時候再由內核去重新分配內存空間,為進程提供一個可寫的頁面,處理結束,至此我們就基本實現(xiàn)了這個COW。

不過這么寫產生了一個問題,即是內存釋放,本來我們頁面的釋放是隨著進程釋放同步進行的,但是上面描述的策略中的進程不再持有真實的內存頁面,而僅僅是一個引用,為了處理釋放,我們可以采用引用計數(shù)的方法——我們可以在內存頁的元信息(meta data)中單獨保存一個值用于計數(shù),當我們的進程釋放時,遞減引用計數(shù),然后當計數(shù)為0時再調用內存的釋放。

需要注意的是,這個過程描述起來非常簡單,在xv6上的實現(xiàn)也不太困難,但是在實際的大型內核中總會有各種各樣的細節(jié)問題,Lab提供了一個探討COW存在的問題的鏈接,可以參考一下。
6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6,6.s081,c語言,操作系統(tǒng)
根據(jù)上面的分析,我們可以將這個Lab分為三個部分做:

  1. 在fork時造成內存復制的假象
  2. 處理page fault,在寫時真實復制內存
  3. 使用引用計數(shù)管理內存釋放

下面我們就來實現(xiàn)吧!

2. 在fork時實現(xiàn)頁面復用而非復制

根據(jù)我們之前l(fā)ab的經驗以及l(fā)ab中的hint,fork中執(zhí)行頁面復制的操作是在vm.c下的uvmcopy完成的:

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

    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    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;
}

可以看到,整體的流程是先分配一個mem,然后將父進程的pa拷貝到mem中去,然后把這個mem映射到子進程上,因此我們可以直接把pa映射過去即可:

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

    *pte &= ~PTE_W; // 取消寫權限
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      goto err;
    }
  }
  return 0;

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

3. 處理page fault

觸發(fā)page fault就會trap,而trap我們知道是在trap.c下的usertrap完成,而處理fault需要判斷fault的類型,這在xv6里面是一個選擇結構,通過r_scause()的值來判斷,在去年其實有一個Lazy Allocation的Lab的,里面有告訴我們r_scause()值為13或15為頁面錯誤,其中13為讀錯誤,15為寫錯誤,因此此處我們只需要處理值為15時的情況:

  else if (r_scause() == 15) {
    uint64 stval = r_stval();
    if (is_cow_fault(p->pagetable, stval)) {
      if (handle_cow_fault(p->pagetable, stval) < 0) {
        printf("usertrap(): alloc failed!\n"); 
        p->killed = 1;   // 當內存分配完,直接kill
      }
    }
    else {
      goto unexpected;
    }
  }
  else {
unexpected:
    printf("usertrap(): unexpected scause %p pid=%d\n", r_scause(), p->pid);
    printf("            sepc=%p stval=%p\n", r_sepc(), r_stval());
    setkilled(p);
  }

框架有了,我們怎么來判斷一個fault是不是cow導致的呢?我們可以在PTE中用一位標記一下:
6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6,6.s081,c語言,操作系統(tǒng)
查看參考手冊,我們可以看到8-9位是保留位,因此我們可以把第八位用于保存COW:
6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6,6.s081,c語言,操作系統(tǒng)
并在uvmcopy處置位

    *pte |=  PTE_C; // 設置寫時復制標志    

然后我們在vm.c實現(xiàn)上面兩個函數(shù):

int 
is_cow_fault(pagetable_t pagetable, uint64 va)
{
  if (va >= MAXVA)
    return 0;
  pte_t* pte = walk(pagetable, PGROUNDDOWN(va), 0);
  return pte && (*pte & (PTE_V | PTE_U | PTE_C));
}

int
handle_cow_fault(pagetable_t pagetable, uint64 va)
{
  va = PGROUNDDOWN(va);
  pte_t* pte = walk(pagetable, va, 0);
  if (!pte) {
    return -1;
  }
  uint64 pa = PTE2PA(*pte);
  uint flags = (PTE_FLAGS(*pte) & ~PTE_C) | PTE_W;  // 取消寫時復制標志,設置寫權限

  char* mem = kalloc();
  if (!mem) {
    return -1;
  }
  memmove(mem, (char*)pa, PGSIZE);
  uvmunmap(pagetable, va, 1, 1);  // 取消映射

  if (mappages(pagetable, va, PGSIZE, (uint64)mem, flags) != 0) {
    kfree(mem);
    return -1;
  }
  return 0;
}

并在defs.h創(chuàng)建聲明

int             is_cow_fault(pagetable_t pagetable, uint64 va);
int             handle_cow_fault(pagetable_t pagetable, uint64 va);

4. 引用計數(shù)管理內存釋放

首先思考一下我們的引用計數(shù)怎么實現(xiàn),hint提示我們可以利用一個數(shù)組,直接映射對應頁的引用計數(shù),于是我們在kalloc.c中:

// 引用計數(shù)的鎖和保存值
struct spinlock cow_ref_lock;
int cow_cnt[(PHYSTOP - KERNBASE) / PGSIZE];
#define PA2IDX(pa) (((uint64)(pa) - KERNBASE) / PGSIZE)

初始化鎖:

void
kinit()
{
  initlock(&kmem.lock, "kmem");
  initlock(&cow_ref_lock, "cow_ref_lock");  // 初始化引用計數(shù)的鎖
  freerange(end, (void*)PHYSTOP);
}

然后定義自增操作與自減操作:

void
inc_ref(void* pa) // 自增引用計數(shù)
{
  acquire(&cow_ref_lock);
  cow_cnt[PA2IDX(pa)]++;
  release(&cow_ref_lock);
}

void
dec_ref(void* pa) // 自減引用計數(shù)
{
  acquire(&cow_ref_lock);
  cow_cnt[PA2IDX(pa)]--;
  release(&cow_ref_lock);
}

完善allocfree

void
kfree(void *pa)
{
  dec_ref(r);
  if (cow_cnt[PA2IDX(r)] > 0) // 只有引用計數(shù)為1時才釋放
    return;

  struct run *r;

  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(&kmem.lock);
  r->next = kmem.freelist;
  kmem.freelist = r;
  release(&kmem.lock);
}

// Allocate one 4096-byte page of physical memory.
// Returns a pointer that the kernel can use.
// Returns 0 if the memory cannot be allocated.
void *
kalloc(void)
{
  struct run *r;

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

  if(r)
  {
    cow_cnt[PA2IDX(r)] = 1;      // 將引用計數(shù)置1
    memset((char*)r, 5, PGSIZE); // fill with junk
  }
  return (void*)r;
}

然后我們思考一下什么時候引用計數(shù)需要增加呢?那應該是fork的時候,因此我們需要暴露出inc_ref(略)然后在uvmcopy中調用它:

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

    if (*pte & PTE_W) // 對于本身可寫的頁才去取消寫權限
    {
      *pte &= ~PTE_W; // 取消寫權限
      *pte |= PTE_C; // 設置寫時復制標志
    }
    pa = PTE2PA(*pte);
    flags = PTE_FLAGS(*pte);
    if(mappages(new, i, PGSIZE, pa, flags) != 0){
      goto err;
    }
    inc_ref((void*)pa);
  }
  return 0;

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

最后還有個問題,就是對于不會觸發(fā)trap的頁操作,這里沒有涉及到,根據(jù)提示,我們可以找到vm.c下的copyout,這個函數(shù)是通過軟件訪問頁表,我們就仿照trap里為它新增一段邏輯:

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

  while(len > 0){
    va0 = PGROUNDDOWN(dstva);
    if (is_cow_fault(p->pagetable, stval)) {
      if (handle_cow_fault(p->pagetable, stval) < 0) {
        printf("copyout(): alloc failed!\n");
        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;
}

5. 測試

最后運行make grade評分即可,這里說一下我遇到過的錯:文章來源地址http://www.zghlxwxcb.cn/news/detail-632283.html

  • 終端剛開回車兩下就出現(xiàn) panic: uvmunmap: not aligned :
    原因是va沒有對齊,在單獨寫的那兩個函數(shù)里對vaa使用va = PGROUNDDOWN(va);即可;
  • Test file測試過不了:
    原因是copyout沒有改,改了就行;

到了這里,關于6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯(lián)網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • lab5:深入理解進程切換

    lab5:深入理解進程切換

    本文主要分析 Linux 5.4.34 版本內核中進程切換的基本操作與基本代碼框架。 content_switch 函數(shù)位于 Linux 內核源碼目錄的 kernel/sched/core.c 中,代碼如下: content_switch 函數(shù)有三個參數(shù):rq、prev、next,其中 rq 指向本次進程切換發(fā)生的 進程就緒隊列;prev 和 next 分別指向切換前后進程

    2024年02月05日
    瀏覽(17)
  • mit6.828 - lab5筆記(上)

    unix的文件系統(tǒng)相關知識 unix將可用的磁盤空間劃分為兩種主要類型的區(qū)域: inode區(qū)域 和 數(shù)據(jù)區(qū)域 。 unix為每個文件分配一個inode,其中保存文件的 關鍵元數(shù)據(jù) ,如文件的stat屬性和指向文件數(shù)據(jù)塊的指針。 數(shù)據(jù)區(qū)域中的空間會被分成大小相同的數(shù)據(jù)塊(就像內存管理中的分

    2024年02月02日
    瀏覽(46)
  • 操作系統(tǒng)復習 MITS6.1810 lab util 記錄

    操作系統(tǒng)復習 MITS6.1810 lab util 記錄

    介紹:主要用來熟悉下環(huán)境以及代碼結構。 See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep ), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep . 代碼: 單個管道一般用于

    2024年02月15日
    瀏覽(15)
  • 網絡攻防技術-Lab5-shellcode編寫實驗(SEED Labs – Shellcode Development Lab)

    網絡攻防技術-Lab5-shellcode編寫實驗(SEED Labs – Shellcode Development Lab)

    網絡攻防技術實驗,實驗環(huán)境、實驗說明、實驗代碼見 Shellcode Development Lab 1) 編譯mysh.s得到二進制文件 2) 執(zhí)行 1)中的二進制文件 ,結果如下圖, 我們 看到運行mysh之前的PID與運行mysh之后的PID是不同的,證明我們通過mysh啟動了一個新的shell。 3) 獲取機器碼,以便進一步

    2023年04月13日
    瀏覽(42)
  • MIT 6.S081 Operating System/Fall 2020 macOS搭建risc-v與xv6開發(fā)調試環(huán)境

    MIT 6.S081 Operating System/Fall 2020 macOS搭建risc-v與xv6開發(fā)調試環(huán)境

    電腦型號:Apple M2 Pro 2023 操作系統(tǒng):macOS Ventura 13.4 所以我的電腦是arm64架構的M2芯片 執(zhí)行安裝腳本 /bin/zsh -c \\\"$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)\\\" 鏡像選哪個都無所謂,我選擇的是阿里巴巴 查看安裝是否成功 brew --version 執(zhí)行brew的安裝腳本 這步需要先安裝

    2024年02月08日
    瀏覽(27)
  • 【MTI 6.S081 Lab】traps

    【MTI 6.S081 Lab】traps

    本實驗閱讀《深入理解計算機系統(tǒng)》第八章異常控制流并做shell實驗將會是很有幫助的 本實驗探討了如何使用陷阱實現(xiàn)系統(tǒng)調用。您將首先使用堆棧進行熱身練習,然后實現(xiàn)用戶級陷阱處理的示例。 了解一下您在6.1910(6.004)中接觸到的RISC-V程序集非常重要。在您的xv6 repo中

    2024年02月15日
    瀏覽(46)
  • MIT 6.S081 Lab Three

    MIT 6.S081 Lab Three

    本文為 MIT 6.S081 2020 操作系統(tǒng) 實驗三解析。 MIT 6.S081課程前置基礎參考: 基于RISC-V搭建操作系統(tǒng)系列 在本實驗中,您將探索頁表并對其進行修改,以簡化將數(shù)據(jù)從用戶空間復制到內核空間的函數(shù)。 開始編碼之前,請閱讀xv6手冊的第3章和相關文件: * kernel/memlayout.h* ,它捕獲了

    2024年02月09日
    瀏覽(20)
  • 【MIT 6.S081】Lab7: Multithreading

    本Lab比較簡單,就是為xv6添加一個用戶級的多線程功能,然后熟悉一下Linux下多線程編程。 筆者用時約2h 這一部分的代碼不涉及內核代碼,所以也比較簡單,根據(jù)提示修改 user/uthread.c 中的代碼即可。仿照內核中進程轉換函數(shù) swtch 的實現(xiàn)即可。首先,添加一個 context 上下文結

    2023年04月09日
    瀏覽(28)
  • MIT 6s081 lab2:system calls

    作業(yè)地址:Lab: System calls (mit.edu) Add $U/_trace to UPROGS in Makefile add a prototype for the system call to user/user.h , a stub to user/usys.pl , and a syscall number to kernel/syscall.h . The Makefile invokes the perl script user/usys.pl Add a sys_trace() function in kernel/sysproc.c that implements the new system call by remembering its argument

    2024年01月18日
    瀏覽(27)
  • MIT6.S081 - Lab2: system calls

    step1:系統(tǒng)調用聲明 user/user.h :系統(tǒng)調用函數(shù)(如 int fork(void) ) step2:ecall 進入內核態(tài) user/usys.S (該文件由 user/usys.pl 生成,后續(xù)添加函數(shù)可以在這里添加):執(zhí)行如下命令 將系統(tǒng)調用的編號(在 kernel/syscall.h 中定義)寫入 a7 寄存器 從 ecall 進入中斷處理函數(shù) step3:保存數(shù)據(jù)并

    2024年04月23日
    瀏覽(22)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包