url: https://pdos.csail.mit.edu/6.1810/2023/labs/mmap.html
mmap和munmap系統調用允許UNIX程序對其地址空間進行精細控制。它們可用于進程間共享內存、將文件映射到進程地址空間,并作為用戶級頁面錯誤處理方案的一部分,例如課程中討論的垃圾回收算法。在本實驗中,您將向xv6添加mmap和munmap功能,重點關注內存映射文件。
The manual page (run man 2 mmap) shows this declaration for mmap:
void *mmap(void *addr, size_t len, int prot, int flags,int fd, off_t offset);
mmap 可以通過多種方式調用,但本實驗僅需要實現與文件內存映射相關的部分功能。您可以假設 addr 始終為零,這意味著內核應自行決定文件的映射虛擬地址。mmap 會返回該地址,如果失敗則返回 0xffffffffffffffff。len 是要映射的字節數,它可能與文件長度不同。prot 表示內存是否應映射為可讀、可寫和/或可執行;您可以假設 prot 是 PROT_READ 或 PROT_WRITE 或兩者兼具。flags 可以是 MAP_SHARED(表示對映射內存的修改應寫回文件)或 MAP_PRIVATE(表示不應寫回文件),無需實現其他標志位。fd 是要映射的文件的打開文件描述符。您可以假設 offset 為零(即文件中的映射起始位置)。
即使映射相同 MAP_SHARED 文件的進程不共享物理頁,也是允許的。
munmap 的手冊頁(運行 man 2 munmap)顯示其聲明如下:
int munmap(void *addr, size_t len);
munmap 應移除指定地址范圍內的內存映射。如果進程修改了該內存區域且映射方式為 MAP_SHARED,則需先將修改內容寫回文件。munmap 可能僅解除部分 mmap 映射區域,但可以假設其操作范圍僅限于:從起始位置解除、從末尾解除,或解除整個區域(而不會在區域中間“打洞”解除部分映射)。
你需要實現足夠的 mmap 和 munmap 功能,以確保 mmaptest 測試程序能夠正常運行。如果 mmaptest 沒有用到某個 mmap 的特性,你就不需要實現該特性。
When you’re done, you should see this output:
$ mmaptest
mmap_test starting
test mmap f
test mmap f: OK
test mmap private
test mmap private: OK
test mmap read-only
test mmap read-only: OK
test mmap read/write
test mmap read/write: OK
test mmap dirty
test mmap dirty: OK
test not-mapped unmap
test not-mapped unmap: OK
test mmap two files
test mmap two files: OK
mmap_test: ALL OK
fork_test starting
fork_test OK
mmaptest: all tests succeeded
$ usertests -q
usertests starting
...
ALL TESTS PASSED
$
以下是一些提示:
- 首先,將 _mmaptest 添加到 UPROGS 中,并添加 mmap 和 munmap 系統調用,以便編譯 user/mmaptest.c。目前,只需從 mmap 和 munmap 返回錯誤。我們在 kernel/fcntl.h 中為您定義了 PROT_READ 等。運行 mmaptest,它將在第一個 mmap 調用失敗。
- 在頁面錯誤處理時懶加載頁表。也就是說,mmap 不應分配物理內存或讀取文件。相反,在 usertrap 中(或由 usertrap 調用的頁面錯誤處理代碼中)執行此操作,就像寫時復制實驗一樣。懶加載的原因是為了確保對大文件的 mmap 快速,并且對大于物理內存的文件的 mmap 成為可能。
- 跟蹤每個進程 mmap 映射的內容。定義一個與“應用程序的虛擬內存”講座中描述的 VMA(虛擬內存區域)相對應的結構。這應該記錄由 mmap 創建的虛擬內存范圍的地址、長度、權限、文件等。由于 xv6 內核中沒有內核內存分配器,因此可以聲明一個固定大小的 VMA 數組,并根據需要從中分配。大小為 16 應該足夠。
- 實現 mmap:在進程的地址空間中找到一個未使用的區域來映射文件,并將一個 VMA 添加到進程的映射區域表中。VMA 應該包含一個指向被映射文件的 struct file 的指針;mmap 應該增加文件的引用計數,以便在文件關閉時結構不會消失(提示:參見 filedup)。運行 mmaptest:第一個 mmap 應該成功,但是對 mmap-ed 內存的第一次訪問將導致頁面錯誤并殺死 mmaptest。
- 添加代碼以使 mmap-ed 區域的頁面錯誤分配一個物理頁面的內存,將相關文件的 4096 字節讀取到該頁面,并將其映射到用戶地址空間。使用 readi 讀取文件,該文件需要一個偏移參數來讀取文件(但是您將不得不鎖定/解鎖傳遞給 readi 的 inode)。不要忘記正確設置頁面的權限。運行 mmaptest;它應該到達第一個 munmap。
- 實現 munmap:找到地址范圍的 VMA 并取消映射指定的頁面(提示:使用 uvmunmap)。如果 munmap 刪除了之前 mmap 的所有頁面,則應該減少相應 struct file 的引用計數。如果取消映射的頁面已被修改并且文件被映射為 MAP_SHARED,則應將頁面寫回文件。查看 filewrite 以獲取靈感。
- 理想情況下,您的實現只會寫回程序實際修改的 MAP_SHARED 頁面。RISC-V PTE 中的臟位(D)指示頁面是否已被寫入。但是,mmaptest 不會檢查未修改的頁面是否沒有被寫回;因此,您可以不查看 D 位就寫回頁面。
- 修改 exit 以取消映射進程的映射區域,就像調用了 munmap 一樣。運行 mmaptest;mmap_test 應該通過,但可能不是 fork_test。
- 修改 fork 以確保子進程具有與父進程相同的映射區域。不要忘記增加 VMA 的 struct file 的引用計數。在子進程的頁面錯誤處理程序中,分配一個新的物理頁面而不是與父進程共享頁面是可以的。后者會更酷,但需要更多的實現工作。運行 mmaptest;它應該通過 mmap_test 和 fork_test。
運行 usertests -q 以確保一切仍然正常工作。
TODO: here