Caffeinated 6.828:實驗 5:檔案系統、Spawn 和 Shell

Csail.mit發表於2018-12-27

Caffeinated 6.828:實驗 5:檔案系統、Spawn 和 Shell

簡介

在本實驗中,你將要去實現 spawn,它是一個載入和執行磁碟上可執行檔案的庫呼叫。然後,你接著要去充實你的核心和庫,以使作業系統能夠在控制檯上執行一個 shell。而這些特性需要一個檔案系統,本實驗將引入一個可讀/寫的簡單檔案系統。

預備知識

使用 Git 去獲取最新版的課程倉庫,然後建立一個命名為 lab5 的本地分支,去跟蹤遠端的 origin/lab5 分支:

athena% cd ~/6.828/lab
athena% add git
athena% git pull
Already up-to-date.
athena% git checkout -b lab5 origin/lab5
Branch lab5 set up to track remote branch refs/remotes/origin/lab5.
Switched to a new branch "lab5"
athena% git merge lab4
Merge made by recursive.
.....
athena%

在實驗中這一部分的主要新元件是檔案系統環境,它位於新的 fs 目錄下。透過檢查這個目錄中的所有檔案,我們來看一下新的檔案都有什麼。另外,在 userlib 目錄下還有一些檔案系統相關的原始檔。

  • fs/fs.c 維護檔案系統在磁碟上結構的程式碼
  • fs/bc.c 構建在我們的使用者級頁故障處理功能之上的一個簡單的塊快取
  • fs/ide.c 極簡的基於 PIO(非中斷驅動的)IDE 驅動程式程式碼
  • fs/serv.c 使用檔案系統 IPC 與客戶端環境互動的檔案系統伺服器
  • lib/fd.c 實現一個常見的類 UNIX 的檔案描述符介面的程式碼
  • lib/file.c 磁碟上檔案型別的驅動,實現為一個檔案系統 IPC 客戶端
  • lib/console.c 控制檯輸入/輸出檔案型別的驅動
  • lib/spawn.c spawn 庫呼叫的框架程式碼

你應該再次去執行 pingpongprimesforktree,測試實驗 4 完成後合併到新的實驗 5 中的程式碼能否正確執行。你還需要在 kern/init.c 中註釋掉 ENV_CREATE(fs_fs) 行,因為 fs/fs.c 將嘗試去做一些 I/O,而 JOS 到目前為止還不具備該功能。同樣,在 lib/exit.c 中臨時註釋掉對 close_all() 的呼叫;這個函式將呼叫你在本實驗後面部分去實現的子程式,如果現在去呼叫,它將導致 JOS 核心崩潰。如果你的實驗 4 的程式碼沒有任何 bug,將很完美地透過這個測試。在它們都能正常工作之前是不能繼續後續實驗的。在你開始做練習 1 時,不要忘記去取消這些行上的註釋。

如果它們不能正常工作,使用 git diff lab4 去重新評估所有的變更,確保你在實驗 4(及以前)所寫的程式碼在本實驗中沒有丟失。確保實驗 4 仍然能正常工作。

實驗要求

和以前一樣,你需要做本實驗中所描述的所有常規練習和至少一個挑戰問題。另外,你需要寫出你在本實驗中問題的詳細答案,和你是如何解決挑戰問題的一個簡短(即:用一到兩個段落)的描述。如果你實現了多個挑戰問題,你只需要寫出其中一個即可,當然,我們歡迎你做的越多越好。在你動手實驗之前,將你的問題答案寫入到你的 lab5 根目錄下的一個名為 answers-lab5.txt 的檔案中。

檔案系統的雛形

你將要使用的檔案系統比起大多數“真正的”檔案系統(包括 xv6 UNIX 的檔案系統)要簡單的多,但它也是很強大的,足夠去提供基本的特性:建立、讀取、寫入和刪除組織在層次目錄結構中的檔案。

到目前為止,我們開發的是一個單使用者作業系統,它提供足夠的保護並能去捕獲 bug,但它還不能在多個不可信使用者之間提供保護。因此,我們的檔案系統還不支援 UNIX 的所有者或許可權的概念。我們的檔案系統目前也不支援硬連結、時間戳、或像大多數 UNIX 檔案系統實現的那些特殊的裝置檔案。

磁碟上的檔案系統結構

主流的 UNIX 檔案系統將可用磁碟空間分為兩種主要的區域型別:節點區域和資料區域。UNIX 檔案系統在檔案系統中為每個檔案分配一個節點;一個檔案的節點儲存了這個檔案重要的後設資料,比如它的 stat 屬性和指向資料塊的指標。資料區域被分為更大的(一般是 8 KB 或更大)資料塊,它在檔案系統中儲存檔案資料和目錄後設資料。目錄條目包含檔名字和指向到節點的指標;如果檔案系統中的多個目錄條目指向到那個檔案的節點上,則稱該檔案是硬連結的。由於我們的檔案系統不支援硬連結,所以我們不需要這種間接的級別,並且因此可以更方便簡化:我們的檔案系統將壓根就不使用節點,而是簡單地將檔案的(或子目錄的)所有後設資料儲存在描述那個檔案的(唯一的)目錄條目中。

檔案和目錄邏輯上都是由一系列的資料塊組成,它或許是很稀疏地分散到磁碟上,就像一個環境的虛擬地址空間上的頁,能夠稀疏地分散在實體記憶體中一樣。檔案系統環境隱藏了塊佈局的細節,只提供檔案中任意偏移位置讀寫位元組序列的介面。作為像檔案建立和刪除操作的一部分,檔案系統環境服務程式在目錄內部完成所有的修改。我們的檔案系統允許使用者環境去直接讀取目錄後設資料(即:使用 read),這意味著使用者環境自己就能夠執行目錄掃描操作(即:實現 ls 程式),而不用另外依賴對檔案系統的特定呼叫。用這種方法做目錄掃描的缺點是,(也是大多數現代 UNIX 作業系統變體摒棄它的原因)使得應用程式依賴目錄後設資料的格式,如果不改變或至少要重編譯應用程式的前提下,去改變檔案系統的內部佈局將變得很困難。

扇區和塊

大多數磁碟都不能執行以位元組為粒度的讀寫操作,而是以扇區為單位執行讀寫。在 JOS 中,每個扇區是 512 位元組。檔案系統實際上是以塊為單位來分配和使用磁碟儲存的。要注意這兩個術語之間的區別:扇區大小是硬碟硬體的屬性,而塊大小是使用這個磁碟的作業系統上的術語。一個檔案系統的塊大小必須是底層磁碟的扇區大小的倍數。

UNIX xv6 檔案系統使用 512 位元組大小的塊,與它底層磁碟的扇區大小一樣。而大多數現代檔案系統使用更大尺寸的塊,因為現在儲存空間變得很廉價了,而使用更大的粒度在儲存管理上更高效。我們的檔案系統將使用 4096 位元組的塊,以更方便地去匹配處理器上頁的大小。

超級塊

Disk layout

檔案系統一般在磁碟上的“易於查詢”的位置(比如磁碟開始或結束的位置)保留一些磁碟塊,用於儲存描述整個檔案系統屬性的後設資料,比如塊大小、磁碟大小、用於查詢根目錄的任何後設資料、檔案系統最後一次掛載的時間、檔案系統最後一次錯誤檢查的時間等等。這些特定的塊被稱為超級塊。

我們的檔案系統只有一個超級塊,它固定為磁碟的 1 號塊。它的佈局定義在 inc/fs.h 檔案裡的 struct Super 中。而 0 號塊一般是保留的,用於去儲存引導載入程式和分割槽表,因此檔案系統一般不會去使用磁碟上比較靠前的塊。許多“真實的”檔案系統都維護多個超級塊,並將它們複製到間隔較大的幾個區域中,這樣即便其中一個超級塊壞了或超級塊所在的那個區域產生了介質錯誤,其它的超級塊仍然能夠被找到並用於去訪問檔案系統。

檔案後設資料

File structure

後設資料的佈局是描述在我們的檔案系統中的一個檔案中,這個檔案就是 inc/fs.h 中的 struct File。後設資料包含檔案的名字、大小、型別(普通檔案還是目錄)、指向構成這個檔案的塊的指標。正如前面所提到的,我們的檔案系統中並沒有節點,因此後設資料是儲存在磁碟上的一個目錄條目中,而不是像大多數“真正的”檔案系統那樣儲存在節點中。為簡單起見,我們將使用 File 這一個結構去表示檔案後設資料,因為它要同時出現在磁碟上和記憶體中。

struct File 中的陣列 f_direct 包含一個儲存檔案的前 10 個塊(NDIRECT)的塊編號的空間,我們稱之為檔案的直接塊。對於最大 10*4096 = 40KB 的小檔案,這意味著這個檔案的所有塊的塊編號將全部直接儲存在結構 File 中,但是,對於超過 40 KB 大小的檔案,我們需要一個地方去儲存檔案剩餘的塊編號。所以我們分配一個額外的磁碟塊,我們稱之為檔案的間接塊,由它去儲存最多 4096/4 = 1024 個額外的塊編號。因此,我們的檔案系統最多允許有 1034 個塊,或者說不能超過 4MB 大小。為支援大檔案,“真正的”檔案系統一般都支援兩個或三個間接塊。

目錄與普通檔案

我們的檔案系統中的結構 File 既能夠表示一個普通檔案,也能夠表示一個目錄;這兩種“檔案”型別是由 File 結構中的 type 欄位來區分的。除了檔案系統根本就不需要解釋的、分配給普通檔案的資料塊的內容之外,它使用完全相同的方式來管理普通檔案和目錄“檔案”,檔案系統將目錄“檔案”的內容解釋為包含在目錄中的一系列的由 File 結構所描述的檔案和子目錄。

在我們檔案系統中的超級塊包含一個結構 File(在 struct Super 中的 root 欄位中),它用於儲存檔案系統的根目錄的後設資料。這個目錄“檔案”的內容是一系列的 File 結構所描述的、位於檔案系統根目錄中的檔案和目錄。在根目錄中的任何子目錄轉而可以包含更多的 File 結構所表示的子目錄,依此類推。

檔案系統

本實驗的目標並不是讓你去實現完整的檔案系統,你只需要去實現幾個重要的元件即可。實踐中,你將負責把塊讀入到塊快取中,並且重新整理髒塊到磁碟上;分配磁碟塊;對映檔案偏移量到磁碟塊;以及實現讀取、寫入、和在 IPC 介面中開啟。因為你並不去實現完整的檔案系統,熟悉提供給你的程式碼和各種檔案系統介面是非常重要的。

磁碟訪問

我們的作業系統的檔案系統環境需要能訪問磁碟,但是我們在核心中並沒有實現任何磁碟訪問的功能。與傳統的在核心中新增了 IDE 磁碟驅動程式、以及允許檔案系統去訪問它所必需的系統呼叫的“大一統”策略不同,我們將 IDE 磁碟驅動實現為使用者級檔案系統環境的一部分。我們仍然需要對核心做稍微的修改,是為了能夠設定一些東西,以便於檔案系統環境擁有實現磁碟訪問本身所需的許可權。

只要我們依賴輪詢、基於 “程式設計的 I/O”(PIO)的磁碟訪問,並且不使用磁碟中斷,那麼在使用者空間中實現磁碟訪問還是很容易的。也可以去實現由中斷驅動的裝置驅動程式(比如像 L3 和 L4 核心就是這麼做的),但這樣做的話,核心必須接收裝置中斷並將它派發到相應的使用者模式環境上,這樣實現的難度會更大。

x86 處理器在 EFLAGS 暫存器中使用 IOPL 位去確定保護模式中的程式碼是否允許執行特定的裝置 I/O 指令,比如 INOUT 指令。由於我們需要的所有 IDE 磁碟暫存器都位於 x86 的 I/O 空間中而不是對映在記憶體中,所以,為了允許檔案系統去訪問這些暫存器,我們需要做的唯一的事情便是授予檔案系統環境“I/O 許可權”。實際上,在 EFLAGS 暫存器的 IOPL 位上規定,核心使用一個簡單的“要麼全都能訪問、要麼全都不能訪問”的方法來控制使用者模式中的程式碼能否訪問 I/O 空間。在我們的案例中,我們希望檔案系統環境能夠去訪問 I/O 空間,但我們又希望任何其它的環境完全不能訪問 I/O 空間。

練習 1i386_init 透過將型別 ENV_TYPE_FS 傳遞給你的環境建立函式 env_create 來識別檔案系統。修改 env.c 中的 env_create ,以便於它只授予檔案系統環境 I/O 的許可權,而不授予任何其它環境 I/O 的許可權。

確保你能啟動這個檔案系統環境,而不會產生一般保護故障。你應該要透過在 make grade 中的 fs i/o 測試。

.

問題 1、當你從一個環境切換到另一個環境時,你是否需要做一些操作來確保 I/O 許可權設定能被儲存和正確地恢復?為什麼?

注意本實驗中的 GNUmakefile 檔案,它用於設定 QEMU 去使用檔案 obj/kern/kernel.img 作為磁碟 0 的映象(一般情況下表示 DOS 或 Windows 中的 “C 盤”),以及使用(新)檔案 obj/fs/fs.img 作為磁碟 1 的映象(”D 盤“)。在本實驗中,我們的檔案系統應該僅與磁碟 1 有互動;而磁碟 0 僅用於去引導核心。如果你想去恢復其中一個有某些錯誤的磁碟映象,你可以透過輸入如下的命令,去重置它們到最初的、”嶄新的“版本:

$ rm obj/kern/kernel.img obj/fs/fs.img
$ make

或者:

$ make clean
$ make

小挑戰!實現中斷驅動的 IDE 磁碟訪問,既可以使用也可以不使用 DMA 模式。由你來決定是否將裝置驅動移植進核心中、還是與檔案系統一樣保留在使用者空間中、甚至是將它移植到一個它自己的的單獨的環境中(如果你真的想了解微核心的本質的話)。

塊快取

在我們的檔案系統中,我們將在處理器虛擬記憶體系統的幫助下,實現一個簡單的”緩衝區“(實際上就是一個塊緩衝區)。塊快取的程式碼在 fs/bc.c 檔案中。

我們的檔案系統將被限制為僅能處理 3GB 或更小的磁碟。我們保留一個大的、尺寸固定為 3GB 的檔案系統環境的地址空間區域,從 0x10000000(DISKMAP)到 0xD0000000(DISKMAP+DISKMAX)作為一個磁碟的”記憶體對映版“。比如,磁碟的 0 號塊被對映到虛擬地址 0x10000000 處,磁碟的 1 號塊被對映到虛擬地址 0x10001000 處,依此類推。在 fs/bc.c 中的 diskaddr 函式實現從磁碟塊編號到虛擬地址的轉換(以及一些完整性檢查)。

由於我們的檔案系統環境在系統中有獨立於所有其它環境的虛擬地址空間之外的、它自己的虛擬地址空間,並且檔案系統環境僅需要做的事情就是實現檔案訪問,以這種方式去保留大多數檔案系統環境的地址空間是很明智的。如果在一臺 32 位機器上的”真實的“檔案系統上這麼做是很不方便的,因為現在的磁碟都遠大於 3 GB。而在一臺有 64 位地址空間的機器上,這樣的快取管理方法仍然是明智的。

當然,將整個磁碟讀入到記憶體中需要很長時間,因此,我們將它實現成”按需“分頁的形式,那樣我們只在磁碟對映區域中分配頁,並且當在這個區域中產生頁故障時,從磁碟讀取相關的塊去響應這個頁故障。透過這種方式,我們能夠假裝將整個磁碟裝進了記憶體中。

練習 2、在 fs/bc.c 中實現 bc_pgfaultflush_block 函式。bc_pgfault 函式是一個頁故障服務程式,就像你在前一個實驗中編寫的寫時複製 fork 一樣,只不過它的任務是從磁碟中載入頁去響應一個頁故障。在你編寫它時,記住: (1) addr 可能並不會做邊界對齊,並且 (2) 在扇區中的 ide_read 操作並不是以塊為單位的。

(如果需要的話)函式 flush_block 應該會將一個塊寫入到磁碟上。如果在塊快取中沒有塊(也就是說,頁沒有對映)或者它不是一個髒塊,那麼 flush_block 將什麼都不做。我們將使用虛擬記憶體硬體去跟蹤,磁碟塊自最後一次從磁碟讀取或寫入到磁碟之後是否被修改過。檢視一個塊是否需要寫入時,我們只需要去檢視 uvpt 條目中的 PTE_D 的 ”dirty“ 位即可。(PTE_D 位由處理器設定,用於表示那個頁被寫入;具體細節可以檢視 x386 參考手冊的 第 5 章 的 5.2.4.3 節)塊被寫入到磁碟上之後,flush_block 函式將使用 sys_page_map 去清除 PTE_D 位。

使用 make grade 去測試你的程式碼。你的程式碼應該能夠透過 check_bccheck_super、和 check_bitmap 的測試。

fs/fs.c 中的函式 fs_init 是塊快取使用的一個很好的示例。在初始化塊快取之後,它簡單地在全域性變數 super 中儲存指標到磁碟對映區。在這之後,如果塊在記憶體中,或我們的頁故障服務程式按需將它們從磁碟上讀入後,我們就能夠簡單地從 super 結構中讀取塊了。

.

小挑戰!到現在為止,塊快取還沒有清除策略。一旦某個塊因為頁故障被讀入到快取中之後,它將一直不會被清除,並且永遠保留在記憶體中。給塊快取增加一個清除策略。在頁表中使用 PTE_Aaccessed 位來實現,任何環境訪問一個頁時,硬體就會設定這個位,你可以透過它來跟蹤磁碟塊的大致使用情況,而不需要修改訪問磁碟對映區域的任何程式碼。使用髒塊要小心。

塊點陣圖

fs_init 設定了 bitmap 指標之後,我們可以認為 bitmap 是一個裝滿位元位的陣列,磁碟上的每個塊就是陣列中的其中一個位元位。比如 block_is_free,它只是簡單地在點陣圖中檢查給定的塊是否被標記為空閒。

練習 3、使用 free_block 作為實現 fs/fs.c 中的 alloc_block 的一個模型,它將在點陣圖中去查詢一個空閒的磁碟塊,並將它標記為已使用,然後返回塊編號。當你分配一個塊時,你應該立即使用 flush_block 將已改變的點陣圖塊重新整理到磁碟上,以確保檔案系統的一致性。

使用 make grade 去測試你的程式碼。現在,你的程式碼應該要透過 alloc_block 的測試。

檔案操作

fs/fs.c 中,我們提供一系列的函式去實現基本的功能,比如,你將需要去理解和管理結構 File、掃描和管理目錄”檔案“的條目、 以及從根目錄開始遍歷檔案系統以解析一個絕對路徑名。閱讀 fs/fs.c 中的所有程式碼,並在你開始實驗之前,確保你理解了每個函式的功能。

練習 4、實現 file_block_walkfile_get_blockfile_block_walk 從一個檔案中的塊偏移量對映到 struct File 中那個塊的指標上或間接塊上,它非常類似於 pgdir_walk 在頁表上所做的事。file_get_block 將更進一步,將去對映一個真實的磁碟塊,如果需要的話,去分配一個新的磁碟塊。

使用 make grade 去測試你的程式碼。你的程式碼應該要透過 file_openfilegetblock、以及 fileflush/filetruncated/file rewrite、和 testfile 的測試。

file_block_walkfile_get_block 是檔案系統中的”勞動模範“。比如,file_readfile_write 或多或少都在 file_get_block 上做必需的登記工作,然後在分散的塊和連續的快取之間複製位元組。

.

小挑戰!如果操作在中途實然被打斷(比如,突然崩潰或重啟),檔案系統很可能會產生錯誤。實現軟體更新或日誌處理的方式讓檔案系統的”崩潰可靠性“更好,並且演示一下舊的檔案系統可能會崩潰,而你的更新後的檔案系統不會崩潰的情況。

檔案系統介面

現在,我們已經有了檔案系統環境自身所需的功能了,我們必須讓其它希望使用檔案系統的環境能夠訪問它。由於其它環境並不能直接呼叫檔案系統環境中的函式,我們必須透過一個遠端過程呼叫或 RPC、構建在 JOS 的 IPC 機制之上的抽象化來暴露對檔案系統的訪問。如下圖所示,下圖是對檔案系統服務呼叫(比如:讀取)的樣子:

      Regular env           FS env
   +---------------+   +---------------+
   |      read     |   |   file_read   |
   |   (lib/fd.c)  |   |   (fs/fs.c)   |
...|.......|.......|...|.......^.......|...............
   |       v       |   |       |       | RPC mechanism
   |  devfile_read |   |  serve_read   |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   |       v       |   |       |       |
   |     fsipc     |   |     serve     |
   |  (lib/file.c) |   |  (fs/serv.c)  |
   |       |       |   |       ^       |
   |       v       |   |       |       |
   |   ipc_send    |   |   ipc_recv    |
   |       |       |   |       ^       |
   +-------|-------+   +-------|-------+
           |                   |
           +-------------------+

圓點虛線下面的過程是一個普通的環境對檔案系統環境請求進行讀取的簡單機制。從(我們提供的)在任何檔案描述符上的 read 工作開始,並簡單地派發到相關的裝置讀取函式上,在我們的案例中是 devfile_read(我們還有更多的裝置型別,比如管道)。devfile_read 實現了對磁碟上檔案指定的 read。它和 lib/file.c 中的其它的 devfile_* 函式實現了客戶端側的檔案系統操作,並且所有的工作大致都是以相同的方式來完成的,把引數打包進一個請求結構中,呼叫 fsipc 去傳送 IPC 請求以及解包並返回結果。fsipc 函式把傳送請求到伺服器和接收來自伺服器的回覆的普通細節做了簡化處理。

fs/serv.c 中可以找到檔案系統伺服器程式碼。它是一個 serve 函式的迴圈,無休止地接收基於 IPC 的請求,並派發請求到相關的服務函式,並透過 IPC 來回送結果。在讀取示例中,serve 將派發到 serve_read 函式上,它將去處理讀取請求的 IPC 細節,比如,解包請求結構並最終呼叫 file_read 去執行實際的檔案讀取動作。

回顧一下 JOS 的 IPC 機制,它讓一個環境傳送一個單個的 32 位數字和可選的共享頁。從一個客戶端向伺服器傳送一個請求,我們為請求型別使用 32 位的數字(檔案系統伺服器 RPC 是有編號的,就像系統呼叫那樣的編號),然後透過 IPC 在共享頁上的一個 union Fsipc 中儲存請求引數。在客戶端側,我們已經在 fsipcbuf 處共享了頁;在服務端,我們在 fsreq0x0ffff000)處對映入站請求頁。

伺服器也透過 IPC 來傳送響應。我們為函式的返回程式碼使用 32 位的數字。對於大多數 RPC,這已經涵蓋了它們全部的返回程式碼。FSREQ_READFSREQ_STAT 也返回資料,它們只是被簡單地寫入到客戶端傳送它的請求時的頁上。在 IPC 的響應中並不需要去傳送這個頁,因為這個頁是檔案系統伺服器和客戶端從一開始就共享的頁。另外,在它的響應中,FSREQ_OPEN 與客戶端共享一個新的 “Fd page”。我們將快捷地返回到檔案描述符頁上。

練習 5、實現 fs/serv.c 中的 serve_read

serve_read 的重任將由已經在 fs/fs.c 中實現的 file_read 來承擔(它實際上不過是對 file_get_block 的一連串呼叫)。對於檔案讀取,serve_read 只能提供 RPC 介面。檢視 serve_set_size 中的註釋和程式碼,去大體上了解伺服器函式的結構。

使用 make grade 去測試你的程式碼。你的程式碼透過 serveopen/filestat/file_closefile_read 的測試後,你得分應該是 70(總分為 150)。

.

練習 6、實現 fs/serv.c 中的 serve_writelib/file.c 中的 devfile_write

使用 make grade 去測試你的程式碼。你的程式碼透過 file_writefileread after filewriteopen、和 large file 的測試後,得分應該是 90(總分為150)。

程式增殖

我們給你提供了 spawn 的程式碼(檢視 lib/spawn.c 檔案),它用於建立一個新環境、從檔案系統中載入一個程式映象並啟動子環境來執行這個程式。然後這個父程式獨立於子環境持續執行。spawn 函式的行為,在效果上類似於UNIX 中的 fork,然後同時緊跟著 fork 之後在子程式中立即啟動執行一個 exec

我們實現的是 spawn,而不是一個類 UNIX 的 exec,因為 spawn 是很容易從使用者空間中、以”外核心式“ 實現的,它無需來自核心的特別幫助。為了在使用者空間中實現 exec,想一想你應該做什麼?確保你理解了它為什麼很難。

練習 7spawn 依賴新的系統呼叫 sys_env_set_trapframe 去初始化新建立的環境的狀態。實現 kern/syscall.c 中的 sys_env_set_trapframe。(不要忘記在 syscall() 中派發新系統呼叫)

執行來自 kern/init.c 中的 user/spawnhello 程式來測試你的程式碼kern/init.c ,它將嘗試從檔案系統中增殖 /hello

使用 make grade 去測試你的程式碼。

.

小挑戰!實現 Unix 式的 exec

.

小挑戰!實現 mmap 式的檔案記憶體對映,並如果可能的話,修改 spawn 從 ELF 中直接對映頁。

跨 fork 和 spawn 共享庫狀態

UNIX 檔案描述符是一個通稱的概念,它還包括管道、控制檯 I/O 等等。在 JOS 中,每個這類裝置都有一個相應的 struct Dev,使用指標去指向到實現讀取/寫入/等等的函式上。對於那個裝置型別,lib/fd.c 在其上實現了類 UNIX 的檔案描述符介面。每個 struct Fd 表示它的裝置型別,並且大多數 lib/fd.c 中的函式只是簡單地派發操作到 struct Dev 中相應函式上。

lib/fd.c 也在每個應用程式環境的地址空間中維護一個檔案描述符表區域,開始位置在 FDTABLE 處。這個區域為應該程式能夠一次最多開啟 MAXFD(當前為 32)個檔案描述符而保留頁的地址空間值(4KB)。在任意給定的時刻,當且僅當相應的檔案描述符處於使用中時,一個特定的檔案描述符表才會被對映。在區域的 FILEDATA 處開始的位置,每個檔案描述符表也有一個可選的”資料頁“,如果它們被選定,相應的裝置就能使用它。

我們想跨 forkspawn 共享檔案描述符狀態,但是檔案描述符狀態是儲存在使用者空間的記憶體中。而現在,在 fork 中,記憶體是標記為寫時複製的,因此狀態將被複制而不是共享。(這意味著環境不能在它們自己無法開啟的檔案中去搜尋,並且管道不能跨一個 fork 去工作)在 spawn 上,記憶體將被保留,壓根不會去複製。(事實上,增殖的環境從使用一個不開啟的檔案描述符去開始。)

我們將要修改 fork,以讓它知道某些被”庫管理的系統“所使用的、和總是被共享的記憶體區域。而不是去”硬編碼“一個某些區域的列表,我們將在頁表條目中設定一個”這些不使用“的位(就像我們在 fork 中使用的 PTE_COW 位一樣)。

我們在 inc/lib.h 中定義了一個新的 PTE_SHARE 位,在 Intel 和 AMD 的手冊中,這個位是被標記為”軟體可使用的“的三個 PTE 位之一。我們將建立一個約定,如果一個頁表條目中這個位被設定,那麼在 forkspawn 中應該直接從父環境中複製 PTE 到子環境中。注意它與標記為寫時複製的差別:正如在第一段中所描述的,我們希望確保能共享頁更新。

練習 8、修改 lib/fork.c 中的 duppage,以遵循最新約定。如果頁表條目設定了 PTE_SHARE 位,僅直接複製對映。(你應該去使用 PTE_SYSCALL,而不是 0xfff,去從頁表條目中掩掉有關的位。0xfff 僅選出可訪問的位和髒位。)

同樣的,在 lib/spawn.c 中實現 copy_shared_pages。它應該迴圈遍歷當前程式中所有的頁表條目(就像 fork 那樣),複製任何設定了 PTE_SHARE 位的頁對映到子程式中。

使用 make run-testpteshare 去檢查你的程式碼行為是否正確。正確的情況下,你應該會看到像 fork handles PTE_SHARE right 和 ”spawn handles PTE_SHARE right” 這樣的輸出行。

使用 make run-testfdsharing 去檢查檔案描述符是否正確共享。正確的情況下,你應該會看到 read in child succeeded 和 “read in parent succeeded” 這樣的輸出行。

鍵盤介面

為了能讓 shell 工作,我們需要一些方式去輸入。QEMU 可以顯示輸出,我們將它的輸出寫入到 CGA 顯示器上和串列埠上,但到目前為止,我們僅能夠在核心監視器中接收輸入。在 QEMU 中,我們從圖形化視窗中的輸入作為從鍵盤到 JOS 的輸入,雖然鍵入到控制檯的輸入作為出現在串列埠上的字元的方式顯現。在 kern/console.c 中已經包含了由我們自實驗 1 以來的核心監視器所使用的鍵盤和串列埠的驅動程式,但現在你需要去把這些增加到系統中。

練習 9、在你的 kern/trap.c 中,呼叫 kbd_intr 去處理捕獲 IRQ_OFFSET+IRQ_KBDserial_intr,用它們去處理捕獲 IRQ_OFFSET+IRQ_SERIAL

lib/console.c 中,我們為你實現了檔案的控制檯輸入/輸出。kbd_intrserial_intr 將使用從最新讀取到的輸入來填充緩衝區,而控制檯檔案型別去排空緩衝區(預設情況下,控制檯檔案型別為 stdin/stdout,除非使用者重定向它們)。

執行 make run-testkbd 並輸入幾行來測試你的程式碼。在你輸入完成之後,系統將回顯你輸入的行。如果控制檯和視窗都可以使用的話,嘗試在它們上面都做一下測試。

Shell

執行 make run-icodemake run-icode-nox 將執行你的核心並啟動 user/icodeicode 又執行 init,它將設定控制檯作為檔案描述符 0 和 1(即:標準輸入 stdin 和標準輸出 stdout),然後增殖出環境 sh,就是 shell。之後你應該能夠執行如下的命令了:

echo hello world | cat
cat lorem |cat
cat lorem |num
cat lorem |num |num |num |num |num
lsfd

注意使用者庫常規程式 cprintf 將直接輸出到控制檯,而不會使用檔案描述符程式碼。這對除錯非常有用,但是對管道連線其它程式卻很不利。為將輸出列印到特定的檔案描述符(比如 1,它是標準輸出 stdout),需要使用 fprintf(1, "...", ...)printf("...", ...) 是將輸出列印到檔案描述符 1(標準輸出 stdout) 的快捷方式。檢視 user/lsfd.c 瞭解更多示例。

練習 10、這個 shell 不支援 I/O 重定向。如果能夠執行 run sh <script 就更完美了,就不用將所有的命令手動去放入一個指令碼中,就像上面那樣。為 <user/sh.c 中新增重定向的功能。

透過在你的 shell 中輸入 sh <script 來測試你實現的重定向功能。

執行 make run-testshell 去測試你的 shell。testshell 只是簡單地給 shell ”喂“上面的命令(也可以在 fs/testshell.sh 中找到),然後檢查它的輸出是否與 fs/testshell.key 一樣。

.

小挑戰!給你的 shell 新增更多的特性。最好包括以下的特性(其中一些可能會要求修改檔案系統):

  • 後臺命令 (ls &)
  • 一行中執行多個命令 (ls; echo hi)
  • 命令組 ((ls; echo hi) | cat > out)
  • 擴充套件環境變數 (echo $hello)
  • 引號 (echo "a | b")
  • 命令列歷史和/或編輯功能
  • tab 命令補全
  • 為命令列查詢目錄、cd 和路徑
  • 檔案建立
  • 用快捷鍵 ctl-c 去殺死一個執行中的環境

可做的事情還有很多,並不僅限於以上列表。

到目前為止,你的程式碼應該要透過所有的測試。和以前一樣,你可以使用 make grade 去評級你的提交,並且使用 make handin 上交你的實驗。

本實驗到此結束。 和以前一樣,不要忘了執行 make grade 去做評級測試,並將你的練習答案和挑戰問題的解決方案寫下來。在動手實驗之前,使用 git statusgit diff 去檢查你的變更,並不要忘記使用 git add answers-lab5.txt 去提交你的答案。完成之後,使用 git commit -am 'my solutions to lab 5’ 去提交你的變更,然後使用 make handin 去提交你的解決方案。


via: https://pdos.csail.mit.edu/6.828/2018/labs/lab5/

作者:csail.mit 選題:lujun9972 譯者:qhwdw 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出

相關文章