實驗文檔-lab5
一、思考題匯總
思考1:
查閱資料,了解 Linux/Unix 的 /proc 文件系統是什么?有什么作用? Windows 操作系統又是如何實現這些功能的?proc 文件系統這樣的設計有什么好處和可以改進的地方?
答:/proc文件系統是一個虛擬文件系統,通過它可以使用一種新的方法在Linux內核空間和用戶空間之間進行通信。在/proc文件系統中,我們可以將對虛擬文件的讀寫作為對內核中實體進行通信的一種手段,與普通文件不同的是,這些虛擬文件的內容為動態創建。
/proc文件系統是一種偽文件系統,存儲的是當前內核運行狀態的一系列文件。用戶可以通過這些文件查看有關的硬件以及當前進程的信息,甚至通過改變其中文件來改變內核運行狀態。
在windows系統中,通過Win32 API函數調用來完成與內核的交互。
proc文件系統的設計將對內核信息的訪問交互抽象為對文件的訪問修改,簡化了交互過程。
思考2:
如果我們通過 kseg0 讀寫設備,我們對於設備的寫入會緩存到 Cache 中。通過 kseg0 訪問設備是一種錯誤的行為,在實際編寫代碼的時候這么做會引發不可預知的問題。請你思考:這么做這會引起什么問題?對於不同種類的設備(如我們提到的串口設備和 IDE 磁盤)的操作會有差異嗎?可以從緩存的性質和緩存刷新的策略來考慮。
答:kseg0是存放內核的區域,一般通過cache訪問。如果在寫設備的時候將寫入緩存到cache,會導致之后想訪問內核時錯誤訪問cache中寫入設備的內容。
此外,cache在被置換時寫回。console這類外設有實時交互型。若通過cache實現一個向console的輸出,那么只有在cache被替換時才能在console上看到輸出。
思考3:
一個磁盤塊最多存儲 1024 個指向其他磁盤塊的指針,試計算我們的文件系統支持的單個文件的最大大小為多大?
答:
4KB * 1024 = 4MB
思考4:
查找代碼中的相關定義,試回答一個磁盤塊中最多能存儲多少個文件控制塊?一個目錄下最多能有多少個文件?
答:
一個磁盤塊中有FILE2BLK = 16個文件控制塊。一個目錄最多指向1024個磁盤塊,因此一共目錄中最多1024*16=16384個子文件。
思考5:
請思考,在滿足磁盤塊緩存的設計的前提下,我們實驗使用的內核支持的最大磁盤大小是多少?
答:最大磁盤大小為DISKMAX = 0x40000000,即1GB。
思考6:
如果將DISKMAX改成0xC0000000, 超過用戶空間,我們的文件系統還能正常工作嗎?為什么?
答:不能正常工作,因為如果這樣設計,寫磁盤的過程中會覆蓋掉內核的關鍵內容,可能會令操作系統運行異常。
思考7:
閱讀 user/file.c,思考文件描述符和打開的文件分別映射到了內存的哪一段空間。
在打開文件時,文件描述符的位置被函數fd_alloc安排。在該函數中,我們發現文件描述符被放置在內存中的FDTABLE域。即0x60000000以下的一段空間。
打開的文件與文件描述符被共同裝在一個Filefd結構體中,位於文件描述符所獨占的那一頁中。
思考8:
閱讀 user/file.c,你會發現很多函數中都會將一個struct Fd\*型的 指針轉換為struct Filefd\*型的指針,請解釋為什么這樣的轉換可行。
答:在user/fd.h中,對於Filefd結構體:
struct Filefd {
struct Fd f_fd;
u_int f_fileid;
struct File f_file;
};
會發現Filefd結構體的第一個元素就是Fd結構體,因此這樣的輕質轉化的目的是將Filefd的f_fd域進行賦值。
C語言是一種弱類型語言,指針指向的是一個地址,只要能取出合法的元素,就可以在各種類型之間相互進行轉化。
思考9:
請解釋 Fd, Filefd, Open 結構體及其各個域的作用。比如各個結構體會在哪些過程中被使用,是否對應磁盤上的物理實體還是單純的內存數據等。說明形式自定,要求簡潔明了,可大致勾勒出文件系統數據結構與物理實體的對應關系與設計框架。
答:
Fd為文件描述符結構體,其中:fd_dev_id:指外設id,也就是外設類型。fd_offset:讀寫的當前位置(偏移量),類似“流”的當前位置。fd_omode:指文件打開方式,如只讀,只寫等。
Filefd為記錄文件詳細信息的結構體,其中:f_fd:即文件描述符。f_fileid:指文件本身的id。f_file:指文件本身。
Open用於抽象化記錄打開文件這一行為,其中:o_file:指向具體的file。o_fileid:即file的id。o_mode:打開方式,指只讀,只寫等。o_ff:讀或寫的當前位置,指偏移量。
思考10:
閱讀serv.c/serve函數的代碼,我們注意到函數中包含了一個死循環for (;;) {...},為什么這段代碼不會導致整個內核進入 panic 狀態?
答:該函數是一個后台進程,在有文件請求時才會繼續執行死循環,其余時間在等待,因此不會讓整個內核panic。
二、實驗難點圖示
難點1:設備驅動,即進行設備讀寫
在我們的實驗中,要實現的設備驅動主要有三種:即console,IDE和rtc。在內存中的布局如下表所示。
device |
start addr |
length |
|---|---|---|
console |
0x10000000 |
0x20 |
IDE |
0x13000000 |
0x4200 |
rtc |
0x15000000 |
0x200 |
其中:
console為控制台終端,即我們在編寫程序時用於輸入輸出的地方。IDE即為磁盤,用於存儲文件等。rtc為實時時鍾終端,用於獲取當前時間等信息。
在用戶態對上述外部設備進行讀寫等操作時,需要用到系統調用,在本次實驗中實現的系統調用為syscall_write_dev與syscall_read_dev。這兩個函數實現的是將某段內存中的信息拷貝到外部設備的相應內存區域或將外部設備內存中的信息拷貝到某段內存中。
難點2:IDE磁盤IO
不同於console與rtc,IDE磁盤的讀寫不能簡單將內容往特定區域直接放置,而是有一套嚴格的IO驅動程序操作。
#define IDE_START_A 0x13000000
#define IDE_OFFSET_A (IDE_START_A + 0x0000)
#define IDE_OFFSET_HIGH_A (IDE_START_A + 0x0008)
#define IDE_ID_A (IDE_START_A + 0x0010)
#define IDE_OP_A (IDE_START_A + 0x0020)
#define IDE_STATUS_A (IDE_START_A + 0x0030)
#define IDE_BUFFER_A (IDE_START_A + 0x4000)
#define BUFFER_SIZE 0x0200
其中:
IDE_START_A是磁盤的起始地址。IDE_OFFSET_A用於存儲讀寫的偏移位置。IDE_OFFSET_HIGH_A用於存儲讀寫偏移的高位。IDE_ID_A用於存儲磁盤ID。IDE_OP_A用於存儲操作符,即“讀”或“寫”。IDE_STATUS_A用於存儲讀寫的狀態,即操作是否成功。IDE_BUFFER_A用於存儲緩沖數據。BUFFER_SIZE指一塊緩沖區的大小,與一個扇區的大小相同。
在讀/寫磁盤時,首先應通過系統調用函數將偏移,磁盤ID以及OP寫入,再對BUFFER來進行讀/寫,最后讀取STATUS來判斷磁盤讀/寫是否成功。
難點3:文件系統結構
這一部分與lab2中內存管理的部分有相似處,有較多的結構體等,且都有特定的結構特征。
-
Block結構體:struct Block { uint8_t data[BY2BLK]; uint32_t type; };指磁盤中的磁盤塊,其中包含的元素為此磁盤塊存儲的數據
data以及此磁盤塊的類型type。其中
type包含以下的種類:enum { BLOCK_FREE = 0, BLOCK_BOOT = 1, BLOCK_BMAP = 2, BLOCK_SUPER = 3, BLOCK_DATA = 4, BLOCK_FILE = 5, BLOCK_INDEX = 6, };類型 具體涵義 BLOCK_FREE空閑磁盤塊 BLOCK_BOOT磁盤的啟動磁盤塊和分區 BLOCK_BMAP位圖表,存儲位圖信息 BLOCK_SUPER超級塊,存儲文件系統基本信息 BLOCK_DATA文件數據塊,存儲文件或目錄的數據 BLOCK_FILE存儲文件指針的磁盤塊 BLOCK_INDEX索引磁盤塊,其中的數據是指向其他磁盤塊的指針 有關的參數:
BY2BLK:每個磁盤塊的大小,為4096,即4KB。BIT2BLK:每個磁盤塊有多少位,為8*BY2BLK。BY2SECT:每個扇區的大小,為512。SECT2BLK:每個磁盤塊包含幾個扇區,為8。
-
disk數組:struct Block { uint8_t data[BY2BLK]; uint32_t type; } disk[NBLOCK];一個存放有
Block結構體的數組,即為本次實驗中的磁盤。有關的參數:
NBLOCK:每個磁盤有多少個磁盤塊,為1024。DISKMAP:磁盤的空間地址,為0x10000000。DIXKMAX:磁盤大小,為0x40000000。
-
File結構體:struct File { u_char f_name[MAXNAMELEN]; // filename u_int f_size; // file size in bytes u_int f_type; // file type u_int f_direct[NDIRECT]; u_int f_indirect; struct File *f_dir; // the pointer to the dir where this file is in, valid only in memory. u_int f_printcount; u_int f_modifycount; u_char f_pad[BY2FILE - MAXNAMELEN - 4 - 4 - NDIRECT * 4 - 4 - 4 - 4 - 4]; };f_name:文件名。f_size:文件大小。f_type:文件類型,包括FTYPE_REG與FTYPE_DIR。f_direct[]:文件的直接指針。f_indirect:文件的間接指針。f_dir:文件所在文件夾。f_pad:填充符。
此為文件結構體,其中
f_direct中存儲的是10個磁盤塊編號。f_indirect存儲一個磁盤塊編號,該磁盤塊是INDEX型的磁盤塊,用於存儲1024個磁盤塊編號。有關的參數:
FILE2BLK:一個磁盤塊中可存儲多少個File結構體,為16。BY2FILE:一個File結構體的大小,為256。NDIRECT:File結構體直接指針的個數,為10。NINDIRECT:File結構體間接指針的個數,為1024,與NBLOCK相當。
難點4:文件系統用戶接口
在file.c中定義了許多用戶態對文件的操作,這些操作主要分為兩種,一種通過系統調用IPC機制實現,例如open,另一種不通過系統調用實現,如file_read。
文件系統的用戶請求IPC主要有以下幾種:
#define FSREQ_OPEN 1
#define FSREQ_MAP 2
#define FSREQ_SET_SIZE 3
#define FSREQ_CLOSE 4
#define FSREQ_DIRTY 5
#define FSREQ_REMOVE 6
#define FSREQ_SYNC 7
請求的發送由file/fsipc.c函數fsipc實現,其接受的參數type即為請求的類型。
當用戶態需要進行文件操作請求時,進入file/fsipc中的fsipc_xxx,在進行一系列准備操作后,將相應請求碼傳入fsipc函數中,進行ipc操作。

在發送這些請求時,與用戶進程進行IPC交互的進程為fs_serv,其核心代碼在serv.c中,其會根據傳遞來的各種請求執行相應的操作。
三、體會與感想
lab5(×)
lab2plus(√)
雖然之前聽學長說lab5的難度在后面幾個lab中是最低的,但對於我個人而言,實際操作起來還是很艱難的。
主要在於這次的實驗又開始涉及許多有關存儲的知識體系,而我在前面的幾個lab中,對於lab2的掌握是最差的,因此在填寫lab5的部分代碼時,有一種“夢回lab2”的感覺,對於磁盤,磁盤塊,扇區,文件結構等等概念梳理不清,導致沒有什么頭緒。尤其是在create_file函數的填寫中十分艱難,幾度不知道該從何下手。之后花了很長時間閱讀各種代碼,宏定義以及指導書,才大致勾勒出這一個版塊各個結構之間的關系。
這一lab的內容綜合性較強,將前面幾個lab的知識體系串聯在一起,從lab2的內存結構到lab4的IPC,每一步都需要深入理解才能幫助我們順利完成lab5的實驗。在此次實驗中又發現了一個lab2的祖傳bug,讓我的程序出現“加上無關緊要的代碼后行為突變”的情況,最后通過修復lab2來解決可能是一個代碼段加載的問題。
此外,這次的課上測試讓我認識到我們不能只關注實驗中提供的代碼文件,對於我們的小操作系統的實現也要進行掌握。在lab5-1-exam中,對Gxemul官方文檔的不熟悉讓我在讀取時間時出現的很大的阻礙。
四、殘留難點
實驗在對文件分類時僅僅分成了目錄和普通文件兩種,而在實際的計算機中文件往往有很多中,有些還能作為一段程序來運行等等,對於用戶態如何按照規則使用這些特殊文件的功能不了解。
