您好,登錄后才能下訂單哦!
近日有人求助,要寫一個UNIX文件系統作為暑假作業。這種事情基本是學操作系統的必須要做的或者是做過的,畢竟文件系統是操作系統課程的一個重要組成部分。要實現這個UNIX文件系統,很多人就扎進了UNIX V6的的系統源碼,以及《萊昂氏UNIX源代碼分析》和《返璞歸真:UNIX技術內幕》這兩本書,很多人出來了,很多人在里面迷失了...最終忘了自己只是要實現一個UNIX文件系統而已。
為何會迷失,因為代碼不是自己寫的,而且年代久遠,編程理念不同了,作者為何那樣寫不一定就能理解,實際上對于任何別人寫的代碼,總是會有一些不易理解的地方,當然,如果作者水平超級高,那么代碼也就相對容易理解。因此,寫代碼永遠比讀代碼要容易!既然是要寫一個文件系統,為何要用現成的UNIX V6代碼呢?如果理解了UNIX文件的布局和結構,自己從零開始不參考任何現有的代碼做一個也不是什么難事,最根本的是UNIX文件系統本身,至于說代碼,僅僅是一個實現與用戶操作的一個接口而已。如果代碼是自己一點一點寫的,那么你肯定能徹底明白每一行的每一個語句的精確含義,至于為何這么寫,你當然及其明了!
本文留下我倉促間幾個小時寫的一個類UNIX文件系統的代碼,不是讓別人看的,是為了自己留檔,因為本文已經說了,看別人的代嗎只能學習經驗,不能理解本質,更何況,你看的還得是超級一流的代碼,而我寫的,則是超級垃圾的代碼。我只想說,理解問題的本質要比代碼重要得多,代碼,碼,并不是很多人想象中的那般重要!本文的實現基于Linux系統,即在Linux系統上編寫一個用戶態程序,實現UNIX文件的IO接口以及操作。
我一向堅持的原則,那就是任何東西的根本性的,本質上的原理以及背后的思想都是及其簡單的,所謂的復雜性都是優化與策略化的擴展帶來的,正如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;
在給出實現代碼之前,要說明的是,在刪除文件的時候,我并沒有實現文件塊區以及i節點的清除操作,眾所周知,那樣很耗時,和很多實現一樣,我只是記錄了一些信息,表示這個文件塊或者inode字段是可以隨時覆蓋的。
//同步i節點,將其寫入“磁盤” void syncinode(struct inode *inode) { int ino = -1, ipos = -1; ino = inode->i_ino; //ipos為inode節點表在文件系統塊中的偏移 ipos = IBPOS + ino*sizeof(struct finode); //從模擬塊的指定偏移位置讀取inode信息 lseek(g_fake_disk, ipos, SEEK_SET); write(g_fake_disk, (void *)&inode->i_finode, sizeof(struct finode)); } //同步超級塊信息 int syncsuper(struct filesys *super) { int pos = -1, size = -1; struct dir dir = {0}; pos = BOOTBSIZE; size = SUPERBSIZE; lseek(g_fake_disk, pos, SEEK_SET); write(g_fake_disk, (void *)super, size); syncinode(g_root); breadwrite(g_root->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 1); breadwrite(g_root->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 0); } //關鍵的將路徑名轉換為i節點的函數,暫不支持相對路徑 struct inode *namei(char *filepath, char flag, int *match, char *ret_name) { int in = 0; int repeat = 0; char *name = NULL; char *path = calloc(1, MAXLEN*10); char *back = path; struct inode *root = iget(0); struct inode *parent = root; struct dir dir = {0}; strncpy(path, filepath, MAXLEN*10); if (path[0] != '/') return NULL; breadwrite(root->i_finode.fi_addr[0], &dir, sizeof(struct dir), 0, 1); while((name=strtok(path, "/")) != NULL) { int i = 0; repeat = 0; *match = 0; path = NULL; if (ret_name) { strcpy(ret_name, name); } for (; i<dir.size; i++) { if (!strncmp(name, dir.direct[i].d_name, strlen(name))) { parent = root; iput(root); root = iget(dir.direct[i].d_ino); root->i_parent = parent; *match = 1; if (root->i_finode.fi_mode == MODE_DIR) { memset(&dir, 0, sizeof(struct dir)); breadwrite(root->i_finode.fi_addr[0], &dir, sizeof(struct dir), 0, 1); } else { free(back); return root; } repeat = 1; } } if (repeat == 0) { break; } } if (*match != 1) { *match = 0; } if (*match == 0) { if (ret_name) { strcpy(ret_name, name); } } free(back); return root; } //通過i節點號獲取內存i節點的函數 struct inode *iget(int ino) { int ibpos = 0; int ipos = 0; int ret = 0; //傾向于直接從內存i節點獲取 if (g_usedinode[ino].i_users) { g_usedinode[ino].i_users ++; return &g_usedinode[ino]; } if (g_fake_disk < 0) { return NULL; } //實在不行則從模擬磁盤塊讀入 ipos = IBPOS + ino*sizeof(struct finode); lseek(g_fake_disk, ipos, SEEK_SET); ret = read(g_fake_disk, &g_usedinode[ino], sizeof(struct finode)); if (ret == -1) { return NULL; } if (g_super->s_freeinode[ino] == 0) { return NULL; } //如果是一個已經被刪除的文件或者從未被分配過的i節點,則初始化其link值以及size值 if (g_usedinode[ino].i_finode.fi_nlink == 0) { g_usedinode[ino].i_finode.fi_nlink ++; g_usedinode[ino].i_finode.fi_size = 0; syncinode(&g_usedinode[ino]); } g_usedinode[ino].i_users ++; g_usedinode[ino].i_ino = ino; return &g_usedinode[ino]; } //釋放一個占有的內存i節點 void iput(struct inode *ip) { if (ip->i_users > 0) ip->i_users --; } //分配一個未使用的i節點。注意,我并沒有使用超級塊的s_freeinodesize字段, //因為還會有一個更好更快的分配算法 struct inode* ialloc() { int ino = -1, nowpos = -1; ino = g_super->s_nextfreeinode; if (ino == -1) { return NULL; } nowpos = ino + 1; g_super->s_nextfreeinode = -1; //尋找下一個空閑i節點,正如上述,這個算法并不好 for (; nowpos < NUM; nowpos++) { if (g_super->s_freeinode[nowpos] == 0) { g_super->s_nextfreeinode = nowpos; break; } } g_super->s_freeinode[ino] = 1; return iget(ino); } //試圖刪除一個文件i節點 int itrunc(struct inode *ip) { iput(ip); if (ip->i_users == 0 && g_super) { syncinode(ip); g_super->s_freeinode[ip->i_ino] = 0; g_super->s_nextfreeinode = ip->i_ino; return 0; } return ERR_BUSY; } //分配一個未使用的磁盤塊 int balloc() { int bno = -1, nowpos = -1; bno = g_super->s_nextfreeblock; if (bno == -1) { return bno; } nowpos = bno + 1; g_super->s_nextfreeblock = -1; for (; nowpos < NUM; nowpos++) { if (g_super->s_freeblock[nowpos] == 0) { g_super->s_nextfreeblock = nowpos; break; } } g_super->s_freeblock[bno] = 1; return bno; } //讀寫操作 int breadwrite(int bno, char *buf, int size, int offset, int type) { int pos = BOOTBSIZE+SUPERBSIZE+g_super->s_itsize + bno*BSIZE; int rs = -1; if (offset + size > BSIZE) { return ERR_EXCEED; } lseek(g_fake_disk, pos + offset, SEEK_SET); rs = type ? read(g_fake_disk, buf, size):write(g_fake_disk, buf, size); return rs; } //IO讀接口 int mfread(int fd, char *buf, int length) { struct file *fs = g_opened[fd]; struct inode *inode = fs->f_inode; int baddr = fs->f_curpos; int bondary = baddr%BSIZE; int max_block = (baddr+length)/BSIZE; int size = 0; int i = inode->i_finode.fi_addr[baddr/BSIZE+1]; for (; i < max_block+1; i ++,bondary = size%BSIZE) { size += breadwrite(inode->i_finode.fi_addr[i], buf+size, (length-size)%BSIZE, bondary, 1); } return size; } //IO寫接口 int mfwrite(int fd, char *buf, int length) { struct file *fs = g_opened[fd]; struct inode *inode = fs->f_inode; int baddr = fs->f_curpos; int bondary = baddr%BSIZE; int max_block = (baddr+length)/BSIZE; int curr_blocks = inode->i_finode.fi_size/BSIZE; int size = 0; int sync = 0; int i = inode->i_finode.fi_addr[baddr/BSIZE+1]; //如果第一次寫,先分配一個塊 if (inode->i_finode.fi_size == 0) { int nbno = balloc(); if (nbno == -1) { return -1; } inode->i_finode.fi_addr[0] = nbno; sync = 1; } //如果必須擴展,則再分配塊,可以和上面的合并優化 if (max_block > curr_blocks) { int j = curr_blocks + 1; for (; j < max_block; j++) { int nbno = balloc(); if (nbno == -1) { return -1; } inode->i_finode.fi_addr[j] = nbno; } sync = 1; } for (; i < max_block+1; i ++,bondary = size%BSIZE) { size += breadwrite(inode->i_finode.fi_addr[i], buf+size, (length-size)%BSIZE, bondary, 0); } if (size) { inode->i_finode.fi_size += size; sync = 1; } if (sync) { syncinode(inode); } return size; } //IO的seek接口 int mflseek(int fd, int pos) { struct file *fs = g_opened[fd]; fs->f_curpos = pos; return pos; } //IO打開接口 int mfopen(char *path, int mode) { struct inode *inode = NULL; struct file *file = NULL; int match = 0; inode = namei(path, 0, &match, NULL); if (match == 0) { return ERR_NOEXIST; } file = (struct file*)calloc(1, sizeof(struct file)); file->f_inode = inode; file->f_curpos = 0; g_opened[g_fd] = file; g_fd++; return g_fd-1; } //IO關閉接口 void mfclose(int fd) { struct inode *inode = NULL; struct file *file = NULL; file = g_opened[fd]; inode = file->f_inode; iput(inode); free(file); } //IO創建接口 int mfcreat(char *path, int mode) { int match = 0; struct dir dir; struct inode *new = NULL; char name[MAXLEN] = {0};; struct inode *inode = namei(path, 0, &match, name); if (match == 1) { return ERR_EXIST; } breadwrite(inode->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 1); strcpy(dir.direct[dir.size].d_name, name); new = ialloc(); if (new == NULL) { return -1; } dir.direct[dir.size].d_ino = new->i_ino; new->i_finode.fi_mode = mode; if (mode == MODE_DIR) { //不允許延遲分配目錄項 int nbno = balloc(); if (nbno == -1) { return -1; } new->i_finode.fi_addr[0] = nbno; } new->i_parent = inode; syncinode(new); dir.size ++; breadwrite(inode->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 0); syncinode(inode); iput(inode); syncinode(new); iput(new); return ERR_OK; } //IO刪除接口 int mfdelete(char *path) { int match = 0; struct dir dir; struct inode *del = NULL; struct inode *parent = NULL; char name[MAXLEN]; int i = 0; struct inode *inode = namei(path, 0, &match, name); if (match == 0 || inode->i_ino == 0) { return ERR_NOEXIST; } match = -1; parent = inode->i_parent; breadwrite(parent->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 1); for (; i < dir.size; i++) { if (!strncmp(name, dir.direct[i].d_name, strlen(name))) { del = iget(dir.direct[i].d_ino); iput(del); if (itrunc(del) == 0) { memset(dir.direct[i].d_name, 0, strlen(dir.direct[i].d_name)); match = i; break; } else { return ERR_BUSY; } } } for (i = match; i < dir.size - 1 && match != -1; i++) { strcpy(dir.direct[i].d_name, dir.direct[i+1].d_name); } dir.size--; breadwrite(parent->i_finode.fi_addr[0], (char *)&dir, sizeof(struct dir), 0, 0); return ERR_OK; } //序列初始化接口,從模擬塊設備初始化內存結構 int initialize(char *fake_disk_path) { g_fake_disk = open(fake_disk_path, O_RDWR); if (g_fake_disk == -1) { return ERR_NOEXIST; } g_super = (struct filesys*)calloc(1, sizeof(struct filesys)); lseek(g_fake_disk, BOOTBSIZE, SEEK_SET); read(g_fake_disk, g_super, sizeof(struct filesys)); g_super->s_size = 1024*1024; g_super->s_itsize = INODEBSIZE; g_super->s_freeinodesize = NUM; g_super->s_freeblocksize = (g_super->s_size - (BOOTBSIZE+SUPERBSIZE+INODEBSIZE))/BSIZE; g_root = iget(0); //第一次的話要分配ROOT if (g_root == NULL) { g_root = ialloc(); g_root->i_finode.fi_addr[0] = balloc(); } return ERR_OK; }
下面是一個測試程序:
int main() { int fd = -1,ws = -1; char buf[16] = {0}; initialize("bigdisk"); mfcreat("/aa", MODE_FILE); fd = mfopen("/aa", 0); ws = mfwrite(fd, "abcde", 5); mfread(fd, buf, 5); mfcreat("/bb", MODE_DIR); mfcreat("/bb/cc", MODE_FILE); fd = mfopen("/bb/cc", 0); ws = mfwrite(fd, "ABCDEFG", 6); mfread(fd, buf, 5); mflseek(0, 4); ws = mfwrite(0, "ABCDEFG", 6); mflseek(0, 0); mfread(0, buf, 10); mfclose(0); mfdelete("/aa"); fd = mfopen("/aa", 0); mfcreat("/aa", MODE_FILE); fd = mfopen("/aa", 0); syncsuper(g_super); }
這個文件系統實現得超級簡單,除去了很多額外的非本質的東西,并且也繞開了煩人的內存管理問題!于是,我的這個實現也就顯示了UNIX文件系統的本質。那么再看一下,還有什么東西雖然是額外的,但是卻是必不可少或者起碼說是很有意思的?答案很顯然,那就是空閑塊或者空閑inode的組織以及分配算法,然而這個算法可以單獨抽象出來。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。