MIT 6.S081 2020Lab5 lazy page allocation 個人全流程

文章目錄

    • 零、寫在前面
    • 一、Eliminate allocation from sbrk()
      • 1.1 說明
      • 1.2 實現
    • 二、Lazy allocation
      • 2.1 說明
      • 2.2 實現
    • 三、Lazytests and Usertests
      • 3.1 說明
      • 3.2 實現
        • 3.2.1 lazytests
        • 3.2.2 usertests


零、寫在前面

可以閱讀下4.6頁面錯誤異常

像應用程序申請內存,內核分配和映射這些內存其實是很耗費時間的。比如,一個 GB 的內存包含 262,144 個 4096 字節的頁;即便每次分配的開銷很小,這么多次操作累積起來仍然非常耗時。

此外,一些程序會分配比實際使用更多的內存(例如,為了實現稀疏數組),或者會提前分配內存但遲遲不使用。為加快 sbrk() 的執行速度,現代內核采用了一種稱為懶惰分配的技術:sbrk() 不再立即分配物理內存,而是僅記錄下哪些用戶地址被分配了,并在用戶頁表中將這些地址標記為無效

當進程首次嘗試訪問這些**“懶惰分配”**的頁面時,CPU 會觸發一次缺頁異常(page fault)。內核在處理該異常時,會分配一頁物理內存、將其清零,并將其映射到進程的地址空間中。

總的來說,懶惰分配是一種常見的降低均攤成本的操作

在本實驗中,你將為 xv6 添加這個懶惰分配的功能。

記得先切換分支到lazy

一、Eliminate allocation from sbrk()

1.1 說明

你的第一個任務是sbrk(n) 系統調用的實現中刪除物理頁面分配。該系統調用對應的函數是 sysproc.c 文件中的 sys_sbrk()

sbrk(n) 系統調用的作用是將當前進程的內存大小增加 n 字節,并返回新分配區域的起始地址(即原來的內存大小)。你需要修改 sbrk(n) 的實現,使其僅僅將進程的大小(myproc()->sz)增加 n 字節并返回舊的大小

注意:不應該在這里分配物理內存,因此需要刪除對 growproc() 的調用。但你仍然需要更新進程的大小字段。

試著猜一猜:這個修改會導致什么問題?會有什么地方出錯?

進行上述修改后,啟動 xv6,并在 shell 中輸入 echo hi。你應該會看到類似下面的輸出:

init: starting sh
$ echo hi
usertrap(): unexpected scause 0x000000000000000f pid=3sepc=0x0000000000001258 stval=0x0000000000004008
va=0x0000000000004000 pte=0x0000000000000000
panic: uvmunmap: not mapped

其中,usertrap(): ... 是來自 trap.c 中用戶異常處理函數(user trap handler)的信息;它捕獲到了一個它不知道如何處理的異常。你需要弄清楚為什么會發生這個缺頁異常(page fault)

信息中的 stval=0x0000000000004008 表示導致缺頁異常的虛擬地址是 0x4008

1.2 實現

我們先來看看初始的sys_sbrk

uint64 sys_sbrk(void) {int addr;int n;if(argint(0, &n) < 0)return -1;addr = myproc()->sz;if(growproc(n) < 0)return -1;return addr;
}
  • 從寄存器a0拿出n
  • growproc(n) 來分配n byte 的物理內存
  • 返回addr

修改后:

  • 刪除分配物理內存的邏輯
  • 如果n 大于0,我們增加sz
  • 否則,我們dealloc 掉n個字節
uint64 sys_sbrk(void) {int addr;int n;if(argint(0, &n) < 0)return -1;struct proc* p = myproc();addr = p->sz;//  if(growproc(n) < 0)//    return -1;// allocif (n > 0){(p->sz) += n;} else {// shrinkuint sz = p->sz;p->sz = uvmdealloc(p->pagetable, sz, sz + n);}return addr;
}

我們啟動xv6來測試下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

和預期一致,我們接著做后面的作業。

二、Lazy allocation

2.1 說明

修改 trap.c 中的代碼,使其能在用戶空間發生缺頁異常時,為發生異常的地址映射一頁新分配的物理內存,然后返回用戶空間,讓進程繼續執行。你應當在打印 "usertrap(): ..." 的那條 printf 語句之前加入你的處理代碼。此外,你還需要根據需要修改 xv6 內核中的其他代碼,使 echo hi 能夠正常運行。

官網的一些提示:

  • 你可以在 usertrap() 中通過檢查 r_scause() 是否為 1315 來判斷是否是頁面異常(page fault):
    • 13 表示加載時的頁面異常(load page fault)
    • 15 表示存儲時的頁面異常(store page fault)
  • r_stval() 返回 RISC-V 的 stval 寄存器的值,它表示觸發異常的虛擬地址
  • 你可以參考 vm.c 中的 uvmalloc() 函數的代碼,這是 sbrk() 通過 growproc() 最終調用的函數。你會用到以下兩個函數:
    • kalloc():用于分配一頁物理內存。
    • mappages():用于將虛擬地址映射到物理頁。
  • 使用 PGROUNDDOWN(va) 宏將發生異常的虛擬地址向下對齊到頁邊界。
  • uvmunmap() 默認會觸發 panic;你需要修改它的行為,使其在取消映射時不會因為某些頁未映射就 panic
  • 如果內核崩潰了,你可以查找 kernel/kernel.asm 中的 sepc 來定位異常發生的位置。
  • 使用你在頁表實驗(pgtbl lab)中寫的 vmprint 函數來打印頁表內容,輔助調試。
  • 如果你遇到 incomplete type proc 的錯誤,記得先 #include "spinlock.h",再 #include "proc.h"

如果一切順利,你的懶惰分配(lazy allocation)代碼應該能使 echo hi 正常工作。在執行過程中,系統應該至少會發生一次頁面異常(觸發懶惰分配),也可能會觸發兩次。

2.2 實現

  • 按照官網提示,在trap.c 中的usertrap 的 printf else分支前添加處理代碼
  • 如果 是 13(load page fault)或 15(store page fault)我們就調用 uvmalloc() 函數分配物理內存,并映射用戶頁表。
// ...
} else if((which_dev = devintr()) != 0){// ok
} else if(r_scause() == 13 || r_scause() == 15) {uint64 va = r_stval();if (uvmalloc(p->pagetable, PGROUNDDOWN(va), PGROUNDDOWN(va) + PGSIZE) == 0)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;
}

值得注意的是,因為我們是利用缺頁異常實現延遲分配,所以 uvmunmap 中的無法根據虛擬地址找到實際物理頁面的情況需要取消panic,改為continue,否則就會觸發panic:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

然后我們運行一下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

三、Lazytests and Usertests

3.1 說明

我們為你提供了一個名為 lazytests 的 xv6 用戶程序,它會測試一些特定情況,這些情況可能會對你的懶惰內存分配器造成壓力。請修改你的內核代碼,使 lazytestsusertests 中的所有測試都能通過。

你需要處理以下幾種情況:

  • 處理 sbrk() 的負數參數:即當進程釋放內存時,要正確縮小進程的地址空間。
  • 如果進程在訪問一個高于 sbrk() 分配范圍的虛擬地址時發生缺頁異常,應終止該進程
  • 正確處理 fork() 中父進程到子進程的內存拷貝,包括懶惰分配頁的復制。
  • 當進程將一個來自 sbrk() 的合法地址傳遞給系統調用(如 readwrite),但該地址尚未實際分配物理內存時,也應正確處理并觸發分配
  • 正確處理內存耗盡的情況:如果在頁面異常處理函數中 kalloc() 失敗,表示系統已無可用內存,應該終止當前進程。
  • 處理位于用戶棧下方的非法頁面上的訪問異常

你的實現是合格的,如果你的內核能夠通過 lazytestsusertests 的全部測試,如下所示:

$ lazytests
lazytests starting
running test lazy alloc
test lazy alloc: OK
running test lazy unmap...
usertrap(): ...
test lazy unmap: OK
running test out of memory
usertrap(): ...
test out of memory: OK
ALL TESTS PASSED$ usertests
...
ALL TESTS PASSED$

3.2 實現

先跑一下看看哪里報錯,結合官網提示去調:

3.2.1 lazytests

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

~~然后它就死了。~~但是給了很多信息。

我們先看uvmcopy,原代碼:

  • 這個函數會把給定的父進程頁表拷貝內存到子進程頁表,頁表和物理內存都進行拷貝
  • 官網提示我們正確完成拷貝包括懶惰分配頁的復制
// Given a parent process's page table, copy
// its memory into a child's page table.
// Copies both the page table and the
// physical memory.
// returns 0 on success, -1 on failure.
// frees any allocated pages on failure.
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;
}

然后注意到 panic(“uvmcopy: page not present”);被觸發顯然是因為遇到了我們假分配的頁面

那就很簡單了,注釋掉panic,改為continue即可。

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

再跑一遍:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

它又死了,這次報錯在 freewalk

我們查看一下源碼:

  • 它會遞歸釋放頁表頁
  • 所以葉子映射都必須已經釋放
// Recursively free page-table pages.
// All leaf mappings must already have been removed.
void freewalk(pagetable_t pagetable)
{// there are 2^9 = 512 PTEs in a page table.for(int i = 0; i < 512; i++){pte_t pte = pagetable[i];if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){// this PTE points to a lower-level page table.uint64 child = PTE2PA(pte);freewalk((pagetable_t)child);pagetable[i] = 0;} else if(pte & PTE_V){panic("freewalk: leaf");}}kfree((void*)pagetable);
}

鈉根據該函數的描述,我們知道說明有葉子節點沒有被釋放。

這其實是比較奇怪的,然后看到官網這一條提示:

  • 如果進程在訪問一個高于 sbrk() 分配范圍的虛擬地址時發生缺頁異常,應終止該進程

于是合理懷疑是因為測試點里面有對于非法地址的訪問,觸發缺頁異常,然后讓我們誤以為是懶惰分配,從而分配了物理內存。

于是在最早的usertrap中的邏輯中加一個地址界限的判斷:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

這次就沒問題了:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

3.2.2 usertests

同樣的,我們根據錯誤找問題:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

通過vscode找到這個sbrkarg函數是在 usertest.c 下

  • 用來測試對分配內存的讀寫
  • 我們報錯是在write的地方報錯了
// test reads/writes from/to allocated memory
void sbrkarg(char *s)
{char *a;int fd, n;a = sbrk(PGSIZE);fd = open("sbrk", O_CREATE|O_WRONLY);unlink("sbrk");if(fd < 0)  {printf("%s: open sbrk failed\n", s);exit(1);}if ((n = write(fd, a, PGSIZE)) < 0) {printf("%s: write sbrk failed\n", s);exit(1);}close(fd);// test writes to allocated memorya = sbrk(PGSIZE);if(pipe((int *) a) != 0){printf("%s: pipe() failed\n", s);exit(1);} 
}

官網有著這樣一條提示:

  • 當進程將一個來自 sbrk() 的合法地址傳遞給系統調用(如 readwrite),但該地址尚未實際分配物理內存時,也應正確處理并觸發分配

那其實很好理解了,我們沒有對 write 訪問假分配時進行分配物理內存。

這個就需要我們去查看write 系統調用的實現,以及寫邏輯。

先找到 sys_write

uint64 sys_write(void)
{struct file *f;int n;uint64 p;if(argfd(0, 0, &f) < 0 || argint(2, &n) < 0 || argaddr(1, &p) < 0)return -1;return filewrite(f, p, n);
}

發現調用了 filewrite

然后查看 filewrite 發現它又調用了 writei 來進行寫邏輯

writei 又調用了either_copyin,而either_copyin又調用了copyin

最終在 copyin 中發現了對于pagetable 的訪問

總而言之是這么個邏輯:

sys_write() -> filewrite() -> writei() -> either_copyin() -> copyin() -> walkaddr()

查看 walkaddr:

  • 果然有問題,如果訪問到空頁或者無效頁,它直接返回0了(0代表未映射)
uint64 walkaddr(pagetable_t pagetable, uint64 va)
{pte_t *pte;uint64 pa;if(va >= MAXVA)return 0;pte = walk(pagetable, va, 0);if(pte == 0)return 0;if((*pte & PTE_V) == 0)return 0;if((*pte & PTE_U) == 0)return 0;pa = PTE2PA(*pte);return pa;
}

我們特判嘗試物理分配即可:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

值得注意的是兩個頭文件的引用順序:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

測試一下:

外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/84489.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/84489.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/84489.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

(Git) 稀疏檢出(Sparse Checkout) 拉取指定文件

文章目錄 &#x1f3ed;作用&#x1f3ed;指令總覽&#x1f477;core.sparseCheckout&#x1f477;sparse-checkout 文件 &#x1f3ed;實例演示?END&#x1f31f;交流方式 &#x1f3ed;作用 類似于 .gitignore 進行文件的規則匹配。 一般在需要拉取大型項目指定的某些文件…

docker初學

加載鏡像&#xff1a;docker load -i ubuntu.tar 導出鏡像&#xff1a;docker save -o ubuntu1.tar ubuntu 運行&#xff1a; docker run -it --name mu ubuntu /bin/bash ocker run -dit --name mmus docker.1ms.run/library/ubuntu /bin/bash 進入容器&#xff1a;docke…

Docker系列(二):開機自啟動與基礎配置、鏡像加速器優化與疑難排查指南

引言 docker 的快速部署與高效運行依賴于兩大核心環節&#xff1a;基礎環境搭建與鏡像生態優化。本期博文從零開始&#xff0c;系統講解 docker 服務的管理配置與鏡像加速實踐。第一部分聚焦 docker 服務的安裝、權限控制與自啟動設置&#xff0c;確保環境穩定可用&#xff1b…

計算機視覺(圖像算法工程師)學習路線

計算機視覺學習路線 Python基礎 常量與變量 列表、元組、字典、集合 運算符 循環 條件控制語句 函數 面向對象與類 包與模塊Numpy Pandas Matplotlib numpy機器學習 回歸問題 線性回歸 Lasso回歸 Ridge回歸 多項式回歸 決策樹回歸 AdaBoost GBDT 隨機森林回歸 分類問題 邏輯…

工業軟件國產化:構建自主創新生態,賦能制造強國建設

隨著全球產業環境的變化和技術的發展&#xff0c;建立自主可控的工業體系成為我國工業轉型升級、走新型工業化道路、推動國家制造業競爭水平提升的重要抓手。 市場倒逼與政策護航&#xff0c;國產化進程雙輪驅動 據中商產業研究院預測&#xff0c;2025年中國工業軟件市場規模…

OpenCV CUDA 模塊圖像過濾------創建一個高斯濾波器函數createGaussianFilter()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::cuda::createGaussianFilter 是 OpenCV CUDA 模塊中的一個工廠函數&#xff0c;用于創建一個高斯濾波器。這個濾波器可以用來平滑圖像&#…

【RocketMQ 生產者和消費者】- 生產者發送故障延時策略

文章目錄 1. 前言2. FaultItem3. LatencyFaultToleranceImpl 容錯集合處理類3.1 updateFaultItem 更新容錯集合3.2 isAvailable 判斷 broker 是否可用3.3 pickOneAtLeast 至少選出一個故障 broker 4. MQFaultStrategy 故障策略類4.1 屬性4.2 updateFaultItem 更新延遲故障容錯信…

【HarmonyOS 5】Map Kit 地圖服務之應用內地圖加載

#HarmonyOS SDK應用服務&#xff0c;#Map Kit&#xff0c;#應用內地圖 目錄 前期準備 AGC 平臺創建項目并創建APP ID 生成調試證書 生成應用證書 p12 與簽名文件 csr 獲取 cer 數字證書文件 獲取 p7b 證書文件 配置項目簽名 項目開發 配置Client ID 開通地圖服務 配…

(1-6-1)Java 集合

目錄 0.知識概述&#xff1a; 1.集合 1.1 集合繼承關系類圖 1.2 集合遍歷的三種方式 1.3 集合排序 1.3.1 Collections實現 1.3.2 自定義排序類 2 List 集合概述 2.1 ArrayList &#xff08;1&#xff09;特點 &#xff08;2&#xff09;常用方法 2.2 LinkedList 3…

Vue.extend

Vue.extend 是 Vue 2 中的一個重要 API&#xff0c;用于基于一個組件配置對象創建一個“可復用的組件構造函數”。它是 Vue 內部構建組件的底層機制之一&#xff0c;適用于某些高級用法&#xff0c;比如手動掛載組件、彈窗動態渲染等。 ?? 在 Vue 3 中已被移除&#xff0c;V…

【MySQL系列】SQL 分組統計與排序

博客目錄 引言一、基礎語法解析二、GROUP BY 的底層原理三、ORDER BY 的排序機制四、NULL 值的處理策略五、性能優化建議六、高級變體查詢 引言 在現代數據分析和數據庫管理中&#xff0c;分組統計是最基礎也是最核心的操作之一。無論是業務報表生成、用戶行為分析還是系統性能…

spring中的InstantiationAwareBeanPostProcessor接口詳解

一、接口定位與核心功能 InstantiationAwareBeanPostProcessor是Spring框架中擴展Bean生命周期的關鍵接口&#xff0c;繼承自BeanPostProcessor。它專注于Bean的實例化階段&#xff08;對象創建和屬性注入&#xff09;的干預&#xff0c;而非父接口的初始化階段&#xff08;如…

uniapp使用sse連接后端,接收后端推過來的消息(app不支持!!)

小白終成大白 文章目錄 小白終成大白前言一、什么是SSE呢&#xff1f;和websocket的異同點有什么&#xff1f;相同點不同點 二、直接上實現代碼總結 前言 一般的請求就是前端發 后端回復 你一下我一下 如果需要有什么實時性的 后端可以主動告訴前端的技術 我首先會想到 webso…

QML學習06Button

QMLx學習06Button 1、Button1.1 狀態改變&#xff08;checkable&#xff09;1.2 排斥性&#xff08;autoExclusive&#xff09;1.3 重復觸發&#xff08;autoRepeat&#xff09;、第一次觸發延時時間&#xff08;autoRepeatDelay&#xff09;、相互之間觸發的時間間隔&#xff…

什么是前端工程化?它有什么意義

前端工程化是指通過工具、流程和規范,將前端開發從手工化、碎片化的模式轉變為系統化、自動化和標準化的生產過程。其核心目標是 提升開發效率、保障代碼質量、增強項目可維護性,并適應現代復雜 Web 應用的需求。 一、前端工程化的核心內容 1. 模塊化開發 代碼模塊化:使用 …

校園二手交易系統

該交易平臺分為兩部分&#xff0c;前臺和后臺。用戶在前臺進行商品選購以及交易&#xff1b;管理員登錄后臺可以對商品進行維護&#xff0c;主要功能包含&#xff1a; 后臺系統的主要功能模塊如下&#xff1a; 登錄功能、注冊功能、后臺首頁 系統設置&#xff1a; 菜單管理、…

06-Web后端基礎(java操作數據庫)

1. 前言 在前面我們學習MySQL數據庫時&#xff0c;都是利用圖形化客戶端工具(如&#xff1a;idea、datagrip)&#xff0c;來操作數據庫的。 我們做為后端程序開發人員&#xff0c;通常會使用Java程序來完成對數據庫的操作。Java程序操作數據庫的技術呢&#xff0c;有很多啊&a…

uni-app學習筆記十三-vue3中slot插槽的使用

在頁面開發中&#xff0c;通常一個頁面分為頭部&#xff0c;尾部&#xff0c;和中心內容區。其中頭部&#xff0c;尾部一般比較固定&#xff0c;而中心區域往往是多樣的&#xff0c;需要自定義開發。此時&#xff0c;我們可以引入slot(插槽)來實現這一目標。<slot> 作為一…

Agent模型微調

這篇文章講解&#xff1a; 把 Agent 和 Fine-Tuning 的知識串起來&#xff0c;在更高的技術視角看大模型應用&#xff1b;加深對 Agent 工作原理的理解&#xff1b;加深對 Fine-Tuning 訓練數據處理的理解。 1. 認識大模型 Agent 1.1 大模型 Agent 的應用場景 揭秘Agent核心…

【最新版】Arduino IDE的安裝入門Demo

1、背景說明 1、本教程編寫日期為2025-5-24 2、Arduino IDE的版本為&#xff1a;Arduino IDE 2.3.6 3、使用的Arduino為Arduino Uno 1、ArduinoIDE的安裝 1、下載。網址如下&#xff1a;官網 2、然后一路安裝即可。 期間會默認安裝相關驅動&#xff0c;默認安裝即可。 3、安…