近日有人求助,要寫一個UNIX檔案系統作為暑假作業。這種事情基本是學作業系統的必須要做的或者是做過的,畢竟檔案系統是作業系統課程的一個重要組成部分。要實現這個UNIX檔案系統,很多人就紮進了UNIX V6的的系統源碼,以及《萊昂氏UNIX原始碼分析》和《返璞歸真:UNIX技術內幕》這兩本書,很多人出來了,很多人在裡面迷失了...最終忘了自己只是要實現一個UNIX檔案系統而已。
為何會迷失,因為代碼不是自己寫的,而且年代久遠,編程理念不同了,作者為何那樣寫不一定就能理解,實際上對於任何別人寫的代碼,總是會有一些不易理解的地方,當然,如果作者水平超級高,那麼代碼也就相對容易理解。因此,寫代碼永遠比讀代碼要容易!既然是要寫一個檔案系統,為何要用現成的UNIX V6代碼呢?如果理解了UNIX檔案的布局和結構,自己從零開始不參考任何現有的代碼做一個也不是什麼難事,最根本的是UNIX檔案系統本身,至於說代碼,僅僅是一個實現與使用者操作的一個介面而已。如果代碼是自己一點一點寫的,那麼你肯定能徹底明白每一行的每一個語句的精確含義,至於為何這麼寫,你當然及其明了!
本文留下我倉促間幾個小時寫的一個類UNIX檔案系統的代碼,不是讓別人看的,是為了自己留檔,因為本文已經說了,看別人的代嗎只能學習經驗,不能理解本質,更何況,你看的還得是超級一流的代碼,而我寫的,則是超級垃圾的代碼。我只想說,理解問題的本質要比代碼重要得多,代碼,碼,並不是很多人想象中的那般重要!本文的實現基於Linux系統,即在Linux系統上編寫一個使用者態程式,實現UNIX檔案的IO介面以及操作。
推薦閱讀:
UNIX/Linux 系統管理技術手冊(第四版)高清中文PDF 下載:
UNIX/Linux 系統管理技術手冊(第四版)高清英文PDF 下載見
我一向堅持的原則,那就是任何東西的根本性的,本質上的原理以及背後的思想都是及其簡單的,所謂的複雜性都是最佳化與策略化的擴充帶來的,正如TCP一樣,UNIX的檔案系統也不例外!我們必須知道,什麼是最根本的,什麼是次要的。對於UNIX檔案系統,最根本的就是其布局以及其系統調用介面,一個處在最低層,一個在最上層開放給使用者,如下所示:
系統調用介面:open,write,read,close...
檔案系統布局:引導快,超級塊,inode區表,資料區塊區
所有的在二者中間的部分都是次要的,也就是說那些東西不要也行,比如高速緩衝,快取,VFS層,塊層...因此在我的實現代碼中,並沒有這些東西,我所做到的,僅僅是UNIX檔案系統所要求必須做到的最小集,那就是:
面對一個按照UNIX檔案系統標準布局的“塊裝置”,可以使用open,read,write等介面進行IO操作。
在實現中,我用一個標準的Linux大檔案來類比磁碟塊,這樣塊的操作基本都映射到了Linux標準的write,read等系統調用了。
首先定義必要的結構體:
//超級塊結構
struct filesys {
unsigned int s_size; //總大小
unsigned int s_itsize; //inode表大小
unsigned int s_freeinodesize; //空閑i節點的數量
unsigned int s_nextfreeinode; //下一個空閑i節點
unsigned int s_freeinode[NUM]; //空閑i節點數組
unsigned int s_freeblocksize; //空閑塊的數量
unsigned int s_nextfreeblock; //下一個空閑塊
unsigned int s_freeblock[NUM]; //空閑塊數組
unsigned int s_pad[]; //填充到512位元組
};
//磁碟inode結構
struct finode {
int fi_mode; //類型:檔案/目錄
int fi_uid_unused; //uid,由於和進程無關聯,僅僅是類比一個FS,未使用,下同
int fi_gid_unused;
int fi_nlink; //連結數,當連結數為0,意味著被刪除
long int fi_size; //檔案大小
long int fi_addr[BNUM]; //檔案塊一級指標,並未實現多級指標
time_t fi_atime_unused; //未實現
time_t fi_mtime_unused;
};
//記憶體inode結構
struct inode {
struct finode i_finode;
struct inode *i_parent; //所屬的目錄i節點
int i_ino; //i節點號
int i_users; //引用計數
};
//目錄項結構(非Linux核心的目錄項)
struct direct
{
char d_name[MAXLEN]; //檔案或者目錄的名字
unsigned short d_ino; //檔案或者目錄的i節點號
};
//目錄結構
struct dir
{
struct direct direct[DIRNUM]; //包含的目錄項數組
unsigned short size; //包含的目錄項大小
};
//抽象的檔案結構
struct file {
struct inode *f_inode; //檔案的i節點
int f_curpos; //檔案的當前讀寫指標
};
之所以叫做類UNIX檔案系統,是因為我並沒有去精確確認當時的UNIX檔案系統的超級塊以及inode表的結構,只是大致的模仿其布局,比如超級塊中欄位,以及欄位的順序可能和標準的UNIX檔案系統並不完全一致。但是不管怎麼說,當時的UNIX檔案系統基本就是這個一個樣子。另外,可以看到file結構體內容及其少,因為本質上,我只是想表示“一個inode節點相對於一個讀寫者來說,就是一個file”,僅此而已。接下來就是具體的實現了,我的方式是自下而上的,這樣做的好處在於便於今後的擴充。那麼首先要完成的就是i節點的分配和釋放了,我的實現中,是將檔案i節點映射到了記憶體i節點,這樣或許違背了我的初衷,我不是說過不要那麼多“額外”的東西來擾亂視聽嗎?是的,然而比起那些所謂的額外的最佳化,我更不喜歡頻繁的調用read和write。反正,只要自己能控制住局面即可。
在實現中,還有一個大事就是記憶體的分配與釋放,這些也不是本質的,記住,要實現的僅僅是一個UNIX檔案系統,其它的能繞開則繞開!顯然malloc,free等也是我們要繞開的,於是我基本都使用預分配空間的東西-全域數組。以下是全域變數:
//記憶體i節點數組,NUM為該檔案系統容納的檔案數
struct inode g_usedinode[NUM];
//ROOT的記憶體i節點
struct inode *g_root;
//已經開啟檔案的數組
struct file* g_opened[OPENNUM];
//超級塊
struct filesys *g_super;
//類比二級檔案系統的Linux大檔案的檔案描述符
int g_fake_disk = -1;