Linux ext2文件系統小結


本文來源:http://learn.akae.cn/media/ch29s02.html 有修改

 

1.ext2文件系統整體布局

一個磁盤可以划分成多個分區,每個分區必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的文件系統,然后才能存儲文件,格式化的過程會在磁盤上寫一些管理存儲布局的信息。下圖是一個磁盤分區格式化成ext2文件系統后的存儲布局。

文件系統中存儲的最小單位是塊(Block),一個塊究竟多大是在格式化時確定的,例如mke2fs-b選項可以設定塊大小為1024、2048或4096字節,這些 blocks 被聚在一起分成幾個大的 block group。每個 block group 中有多少個 block 是固定的。而上圖中啟動塊(Boot Block)的大小是確定的,就是1KB,啟動塊是由PC標准規定的,用來存儲磁盤分區信息和啟動信息,任何文件系統都不能使用啟動塊。啟動塊之后才是ext2文件系統的開始,ext2文件系統將整個分區划成若干個同樣大小的塊組(Block Group),每個塊組都由以下部分組成

 

1).超級塊(Super Block)

描述整個分區的文件系統信息,例如塊大小、文件系統版本號、上次mount的時間等等。超級塊在每個塊組的開頭都有一份拷貝。

 

2).塊組描述符表(GDT,Group Descriptor Table)

由很多塊組描述符組成,整個分區分成多少個塊組就對應有多少個塊組描述符。每個塊組描述符(Group Descriptor)存儲一個塊組的描述信息,例如在這個塊組中從哪里開始是inode表,從哪里開始是數據塊,空閑的inode和數據塊還有多少個等等。和超級塊類似,塊組描述符表在每個塊組的開頭也都有一份拷貝,這些信息是非常重要的,一旦超級塊意外損壞就會丟失整個分區的數據,一旦塊組描述符意外損壞就會丟失整個塊組的數據,因此它們都有多份拷貝。通常內核只用到第0個塊組中的拷貝,當執行e2fsck檢查文件系統一致性時,第0個塊組中的超級塊和塊組描述符表就會拷貝到其它塊組,這樣當第0個塊組的開頭意外損壞時就可以用其它拷貝來恢復,從而減少損失。

 

3).塊位圖(Block Bitmap)

一個塊組中的塊是這樣利用的:數據塊存儲所有文件的數據,比如某個分區的塊大小是1024字節,某個文件是2049字節,那么就需要三個數據塊來存,即使第三個塊只存了一個字節也需要占用一個整塊;超級塊、塊組描述符表、塊位圖、inode位圖、inode表這幾部分存儲該塊組的描述信息。那么如何知道哪些塊已經用來存儲文件數據或其它描述信息,哪些塊仍然空閑可用呢?塊位圖就是用來描述整個塊組中哪些塊已用哪些塊空閑的,它本身占一個塊,其中的每個bit代表本塊組中的一個塊,這個bit為1表示該塊已用,這個bit為0表示該塊空閑可用

為什么用df命令統計整個磁盤的已用空間非常快呢?因為只需要查看每個塊組的塊位圖即可,而不需要搜遍整個分區。相反,用du命令查看一個較大目錄的已用空間就非常慢,因為不可避免地要搜遍整個目錄的所有文件。

與此相聯系的另一個問題是:在格式化一個分區時究竟會划出多少個塊組呢?主要的限制在於塊位圖本身必須只占一個塊。用mke2fs格式化時默認塊大小是1024字節,可以用-b參數指定塊大小,現在設塊大小指定為b字節,那么一個塊可以有8b個bit,這樣大小的一個塊位圖就可以表示8b個塊的占用情況,因此一個塊組最多可以有8b個塊,如果整個分區有s個塊,那么就可以有s/(8b)個塊組。格式化時可以用-g參數指定一個塊組有多少個塊,但是通常不需要手動指定,mke2fs工具會計算出最優的數值。

 

4).inode位圖(inode Bitmap)

和塊位圖類似,本身占一個塊,其中每個bit表示一個inode是否空閑可用

 

5).inode表(inode Table)

我們知道,一個文件除了數據需要存儲之外,一些描述信息也需要存儲,例如文件類型(常規、目錄、符號鏈接等),權限,文件大小,創建/修改/訪問時間等,也就是ls -l命令看到的那些信息,這些信息存在inode中而不是數據塊中。每個文件都有一個inode,一個塊組中的所有inode組成了inode表

inode表占多少個塊在格式化時就要決定並寫入塊組描述符中,mke2fs格式化工具的默認策略是一個塊組有多少個8KB就分配多少個inode。由於數據塊占了整個塊組的絕大部分,也可以近似認為數據塊有多少個8KB就分配多少個inode,換句話說,如果平均每個文件的大小是8KB,當分區存滿的時候inode表會得到比較充分的利用,數據塊也不浪費如果這個分區存的都是很大的文件(比如電影),則數據塊用完的時候inode會有一些浪費,如果這個分區存的都是很小的文件(比如源代碼),則有可能數據塊還沒用完inode就已經用完了,數據塊可能有很大的浪費。如果用戶在格式化時能夠對這個分區以后要存儲的文件大小做一個預測,也可以用mke2fs-i參數手動指定每多少個字節分配一個inode。

 

6).數據塊(Data Block)

根據不同的文件類型有以下幾種情況

  • 對於常規文件,文件的數據存儲在數據塊中。

  • 對於目錄,該目錄下的所有文件名和目錄名存儲在數據塊中,注意文件名保存在它所在目錄的數據塊中,除文件名之外,ls -l命令看到的其它信息都保存在該文件的inode中。注意這個概念:目錄也是一種文件,是一種特殊類型的文件。

  • 對於符號鏈接,如果目標路徑名較短則直接保存在inode中以便更快地查找,如果目標路徑名較長則分配一個數據塊來保存

  • 設備文件、FIFO和socket等特殊文件沒有數據塊,設備文件的主設備號和次設備號保存在inode中。

 

2.數據塊尋址

如果一個文件有多個數據塊,這些數據塊很可能不是連續存放的,應該如何尋址到每個塊呢?實際上,根目錄的數據塊是通過其inode中的索引項Blocks[0]找到的,事實上,這樣的索引項一共有15個,從Blocks[0]Blocks[14],每個索引項占4字節。前12個索引項都表示塊編號,例如上面的例子中Blocks[0]字段保存着24,就表示第24個塊是該文件的數據塊,如果塊大小是1KB,這樣可以表示從0字節到12KB的文件。如果剩下的三個索引項Blocks[12]Blocks[14]也是這么用的,就只能表示最大15KB的文件了,這是遠遠不夠的,事實上,剩下的三個索引項都是間接索引。

索引項Blocks[12]所指向的塊並非數據塊,而是稱為間接尋址塊(Indirect Block),其中存放的都是類似Blocks[0]這種索引項,再由索引項指向數據塊。設塊大小是b,那么一個間接尋址塊中可以存放b/4個索引項,指向b/4個數據塊。所以如果把Blocks[0]Blocks[12]都用上,最多可以表示b/4+12個數據塊,對於塊大小是1K的情況,最大可表示268K的文件。如下圖所示,注意文件的數據塊編號是從0開始的,Blocks[0]指向第0個數據塊,Blocks[11]指向第11個數據塊,Blocks[12]所指向的間接尋址塊的第一個索引項指向第12個數據塊,依此類推。

從上圖可以看出,索引項Blocks[13]指向兩級的間接尋址塊,最多可表示(b/4)2+b/4+12個數據塊,對於1K的塊大小最大可表示64.26MB的文件。索引項Blocks[14]指向三級的間接尋址塊,最多可表示(b/4)3+(b/4)2+b/4+12個數據塊,對於1K的塊大小最大可表示16.06GB的文件。

可見,這種尋址方式對於訪問不超過12個數據塊的小文件是非常快的,訪問文件中的任意數據只需要兩次讀盤操作,一次讀inode(也就是讀索引項)一次讀數據塊。而訪問大文件中的數據則需要最多五次讀盤操作:inode、一級間接尋址塊、二級間接尋址塊、三級間接尋址塊、數據塊。實際上,磁盤中的inode和數據塊往往已經被內核緩存了,讀大文件的效率也不會太低。

 

3.文件和目錄操作的系統函數(這里面的"(n)"應該表示參數的個數)

1).stat(2)函數讀取文件的inode,然后把inode中的各種文件屬性填入一個struct stat結構體傳出給調用者。stat(1)命令是基於stat函數實現的。stat需要根據傳入的文件路徑找到inode,假設一個路徑是/opt/file,則查找的順序是:

  • 讀出inode表中第2項,也就是根目錄的inode,從中找出根目錄數據塊的位置

  • 從根目錄的數據塊中找出文件名為opt的記錄,從記錄中讀出它的inode號

  • 讀出opt目錄的inode,從中找出它的數據塊的位置

  • opt目錄的數據塊中找出文件名為file的記錄,從記錄中讀出它的inode號

  • 讀出file文件的inode

還有另外兩個類似stat的函數:fstat(2)函數傳入一個已打開的文件描述符,傳出inode信息,lstat(2)函數也是傳入路徑傳出inode信息,但是和stat函數有一點不同,當文件是一個符號鏈接時,stat(2)函數傳出的是它所指向的目標文件的inode,而lstat函數傳出的就是符號鏈接文件本身的inode。

 

2).access(2)函數檢查執行當前進程的用戶是否有權限訪問某個文件,傳入文件路徑和要執行的訪問操作(讀/寫/執行),access函數取出文件inode中的st_mode字段,比較一下訪問權限,然后返回0表示允許訪問,返回-1表示錯誤或不允許訪問。

 

3).chmod(2)fchmod(2)函數改變文件的訪問權限,也就是修改inode中的st_mode字段。這兩個函數的區別類似於stat/fstatchmod(1)命令是基於chmod函數實現的。

 

4).chown(2)/fchown(2)/lchown(2)改變文件的所有者和組,也就是修改inode中的UserGroup字段,只有超級用戶才能正確調用這幾個函數,這幾個函數之間的區別類似於stat/fstat/lstatchown(1)命令是基於chown函數實現的。

 

5).utime(2)函數改變文件的訪問時間和修改時間,也就是修改inode中的atimemtime字段。touch(1)命令是基於utime函數實現的。

 

6).truncate(2)ftruncate(2)函數把文件截斷到某個長度,如果新的長度比原來的長度短,則后面的數據被截掉了,如果新的長度比原來的長度長,則后面多出來的部分用0填充,這需要修改inode中的Blocks索引項以及塊位圖中相應的bit。這兩個函數的區別類似於stat/fstat

 

7).link(2)函數創建硬鏈接,其原理是在目錄的數據塊中添加一條新記錄,其中的inode號字段和原文件相同。symlink(2)函數創建一個符號鏈接,這需要創建一個新的inode,其中st_mode字段的文件類型是符號鏈接,原文件的路徑保存在inode中或者分配一個數據塊來保存。ln(1)命令是基於linksymlink函數實現的。

 

8).unlink(2)函數刪除一個鏈接。如果是符號鏈接則釋放這個符號鏈接的inode和數據塊,清除inode位圖和塊位圖中相應的位。如果是硬鏈接則從目錄的數據塊中清除一條文件名記錄,如果當前文件的硬鏈接數已經是1了還要刪除它,就同時釋放它的inode和數據塊,清除inode位圖和塊位圖中相應的位,這樣就真的刪除文件了。unlink(1)命令和rm(1)命令是基於unlink函數實現的。

 

9).rename(2)函數改變文件名,需要修改目錄數據塊中的文件名記錄,如果原文件名和新文件名不在一個目錄下則需要從原目錄數據塊中清除一條記錄然后添加到新目錄的數據塊中。mv(1)命令是基於rename函數實現的,因此在同一分區的不同目錄中移動文件並不需要復制和刪除文件的inode和數據塊,只需要一個改名操作,即使要移動整個目錄,這個目錄下有很多子目錄和文件也要隨着一起移動,移動操作也只是對頂級目錄的改名操作,很快就能完成。但是,如果在不同的分區之間移動文件就必須復制和刪除inode和數據塊,如果要移動整個目錄,所有子目錄和文件都要復制刪除,這就很慢了。

 

10)readlink(2)函數讀取一個符號鏈接所指向的目標路徑,其原理是從符號鏈接的inode或數據塊中讀出保存的數據,這就是目標路徑。

 

11).mkdir(2)函數創建新的目錄,要做的操作是在它的父目錄數據塊中添加一條記錄,然后分配新的inode和數據塊,inode的st_mode字段的文件類型是目錄,在數據塊中填兩個記錄,分別是...,由於..表示父目錄,因此父目錄的硬鏈接數要加1。mkdir(1)命令是基於mkdir函數實現的。

 

12).rmdir(2)函數刪除一個目錄,這個目錄必須是空的(只包含...)才能刪除,要做的操作是釋放它的inode和數據塊,清除inode位圖和塊位圖中相應的位,清除父目錄數據塊中的記錄,父目錄的硬鏈接數要減1。rmdir(1)命令是基於rmdir函數實現的。

 

13).opendir(3)/readdir(3)/closedir(3)用於遍歷目錄數據塊中的記錄。opendir打開一個目錄,返回一個DIR *指針代表這個目錄,它是一個類似FILE *指針的句柄,closedir用於關閉這個句柄,把DIR *指針傳給readdir讀取目錄數據塊中的記錄,每次返回一個指向struct dirent的指針,反復讀就可以遍歷所有記錄,所有記錄遍歷完之后readdir返回NULL。結構體struct dirent的定義如下:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM