ext2文件系統

ext2文件系統

整體存儲佈局

咱們知道,一個磁盤能夠劃分紅多個分區,每一個分區必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的文件系統,而後才能存儲文件,格式化的過程會在磁盤上寫一些管理存儲佈局的信息。下圖是一個磁盤分區格式化成ext2文件系統後的存儲佈局。html

圖 29.2. ext2文件系統的整體存儲佈局node

ext2文件系統的整體存儲佈局

啓動快(1KB)+塊組(格式化時肯定)linux

文件系統中存儲的最小單位是塊(Block),一個塊究竟多大是在格式化時肯定的,例如mke2fs-b選項能夠設定塊大小爲102四、2048或4096字節。而上圖中啓動塊(Boot Block)的大小是肯定的,就是1KB,啓動塊是由PC標準規定的,用來存儲磁盤分區信息和啓動信息,任何文件系統都不能使用啓動塊。啓動塊以後纔是ext2文件系統的開始,ext2文件系統將整個分區劃成若干個一樣大小的塊組(Block Group)每一個塊組都由如下部分組成。緩存

超級塊(Super Block)

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

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

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

 

上面的是對整個分區的描述,只是在每一個塊組中有備份。ide

 

塊位圖(Block Bitmap)只是描述塊用沒用(總體的,包括inode 和數據塊)

一個塊組中的塊是這樣利用的:數據塊(Data Block)存儲全部文件的數據,好比某個分區的塊大小是1024字節,某個文件是2049字節,那麼就須要三個數據塊來存,即便第三個塊只存了一個字節也須要佔用一個整塊;超級塊、塊組描述符表、塊位圖、inode位圖、inode表這幾部分存儲該塊組的描述信息。那麼如何知道哪些塊已經用來存儲文件數據或其它描述信息,哪些塊仍然空閒可用呢?塊位圖就是用來描述整個塊組中哪些塊已用哪些塊空閒的,它自己佔一個塊,其中的每一個bit表明本塊組中的一個塊,這個bit爲1表示該塊已用,這個bit爲0表示該塊空閒可用。函數

爲何用df命令統計整個磁盤的已用空間很是快呢?由於只須要查看每一個塊組的塊位圖便可,而不須要搜遍整個分區。相反,用du命令查看一個較大目錄的已用空間就很是慢,由於不可避免地要搜遍整個目錄的全部文件。工具

與此相聯繫的另外一個問題是:在格式化一個分區時究竟會劃出多少個塊組呢?主要的限制在於塊位圖自己必須只佔一個塊。用mke2fs格式化時默認塊大小是1024字節,能夠用-b參數指定塊大小,如今設塊大小指定爲b字節,那麼一個塊能夠有8b個bit,這樣大小的一個塊位圖就能夠表示8b個塊的佔用狀況,所以一個塊組最多能夠有8b個塊,若是整個分區有s個塊,那麼就能夠有s/(8b)個塊組。格式化時能夠用-g參數指定一個塊組有多少個塊,可是一般不須要手動指定,mke2fs工具會計算出最優的數值。oop

 

inode位圖(inode Bitmap)

和塊位圖相似,自己佔一個塊,其中每一個bit表示一個inode是否空閒可用。

 

inode表(inode Table)

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

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

數據塊

根據不一樣的文件類型有如下幾種狀況

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

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

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

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

如今作幾個小實驗來理解這些概念。例如在home目錄下ls -l

$ ls -l
total 32
drwxr-xr-x 114 akaedu akaedu 12288 2008-10-25 11:33 akaedu
drwxr-xr-x 114 ftp    ftp     4096 2008-10-25 10:30 ftp
drwx------   2 root   root   16384 2008-07-04 05:58 lost+found

爲何各目錄的大小都是4096的整數倍?由於這個分區的塊大小是4096,目錄的大小老是數據塊的整數倍。爲何有的目錄大有的目錄小?由於目錄的數據塊保存着它下邊全部文件和目錄的名字,若是一個目錄中的文件不少,一個塊裝不下這麼多文件名,就可能分配更多的數據塊給這個目錄。再好比:

$ ls -l /dev
......
prw-r-----  1 syslog adm            0 2008-10-25 11:39 xconsole
crw-rw-rw-  1 root   root      1,   5 2008-10-24 16:44 zero

xconsole文件的類型是p(表示pipe),是一個FIFO文件,後面會講到它實際上是一塊內核緩衝區的標識,不在磁盤上保存數據,所以沒有數據塊,文件大小是0。zero文件的類型是c,表示字符設備文件,它表明內核中的一個設備驅動程序,也沒有數據塊,本來應該寫文件大小的地方寫了1, 5這兩個數字,表示主設備號和次設備號,訪問該文件時,內核根據設備號找到相應的驅動程序。再好比:

$ touch hello
$ ln -s ./hello halo
$ ls -l
total 0
lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:04 halo -> ./hello
-rw-r--r-- 1 akaedu akaedu 0 2008-10-25 15:04 hello

文件hello是剛建立的,字節數爲0,符號連接文件halo指向hello,字節數倒是7,爲何呢?其實7就是「./hello」這7個字符,符號連接文件就保存着這樣一個路徑名。再試試硬連接:

$ ln ./hello hello2
$ ls -l
total 0
lrwxrwxrwx 1 akaedu akaedu 7 2008-10-25 15:08 halo -> ./hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello
-rw-r--r-- 2 akaedu akaedu 0 2008-10-25 15:04 hello2

hello2hello除了文件名不同以外,別的屬性都如出一轍,而且hello的屬性發生了變化,第二欄的數字本來是1,如今變成2了。從根本上說,hellohello2是同一個文件在文件系統中的兩個名字,ls -l第二欄的數字是硬連接數,表示一個文件在文件系統中有幾個名字(這些名字能夠保存在不一樣目錄的數據塊中,或者說能夠位於不一樣的路徑下),硬連接數也保存在inode中。既然是同一個文件,inode固然只有一個,因此用ls -l看它們的屬性是如出一轍的,由於都是從這個inode裏讀出來的。再研究一下目錄的硬連接數:

$ mkdir a
$ mkdir a/b
$ ls -ld a
drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 a
$ ls -la a
total 20
drwxr-xr-x   3 akaedu akaedu  4096 2008-10-25 16:15 .
drwxr-xr-x 115 akaedu akaedu 12288 2008-10-25 16:14 ..
drwxr-xr-x   2 akaedu akaedu  4096 2008-10-25 16:15 b
$ ls -la a/b
total 8
drwxr-xr-x 2 akaedu akaedu 4096 2008-10-25 16:15 .
drwxr-xr-x 3 akaedu akaedu 4096 2008-10-25 16:15 ..

首先建立目錄a,而後在它下面建立子目錄a/b。目錄a的硬連接數是3,這3個名字分別是當前目錄下的aa目錄下的.b目錄下的..。目錄b的硬連接數是2,這兩個名字分別是a目錄下的bb目錄下的.。注意,目錄的硬連接只能這種方式建立,用ln命令能夠建立目錄的符號連接,但不能建立目錄的硬連接。

實例剖析

若是要格式化一個分區來研究文件系統格式則必須有一個空閒的磁盤分區,爲了方便實驗,咱們把一個文件看成分區來格式化,而後分析這個文件中的數據來印證上面所講的要點。首先建立一個1MB的文件並清零:

$ dd if=/dev/zero of=fs count=256 bs=4K

咱們知道cp命令能夠把一個文件拷貝成另外一個文件,而dd命令能夠把一個文件的一部分拷貝成另外一個文件。這個命令的做用是把/dev/zero文件開頭的1M(256×4K)字節拷貝成文件名爲fs的文件。剛纔咱們看到/dev/zero是一個特殊的設備文件,它沒有磁盤數據塊,對它進行讀操做傳給設備號爲1, 5的驅動程序。/dev/zero這個文件能夠看做是無窮大的,無論從哪裏開始讀,讀出來的都是字節0x00。所以這個命令拷貝了1M個0x00到fs文件。ifof參數表示輸入文件和輸出文件,countbs參數表示拷貝多少次,每次拷多少字節。

作好以後對文件fs進行格式化,也就是把這個文件的數據塊合起來當作一個1MB的磁盤分區,在這個分區上再劃分出塊組。

$ mke2fs fs
mke2fs 1.40.2 (12-Jul-2007)
fs is not a block special device.
Proceed anyway? (y,n) (輸入y回車)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
128 inodes, 1024 blocks
51 blocks (4.98%) reserved for the super user
First data block=1
Maximum filesystem blocks=1048576
1 block group
8192 blocks per group, 8192 fragments per group
128 inodes per group

Writing inode tables: done                            
Writing superblocks and filesystem accounting information: done

This filesystem will be automatically checked every 27 mounts or
180 days, whichever comes first.  Use tune2fs -c or -i to override.

格式化一個真正的分區應該指定塊設備文件名,例如/dev/sda1,而這個fs是常規文件而不是塊設備文件,mke2fs認爲用戶有多是誤操做了,因此給出提示,要求確認是否真的要格式化,輸入y回車完成格式化。

如今fs的大小仍然是1MB,但再也不是全0了,其中已經有了塊組和描述信息。用dumpe2fs工具能夠查看這個分區的超級塊和塊組描述符表中的信息:

$ dumpe2fs fs
dumpe2fs 1.40.2 (12-Jul-2007)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          8e1f3b7a-4d1f-41dc-8928-526e43b2fd74
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      resize_inode dir_index filetype sparse_super
Filesystem flags:         signed directory hash 
Default mount options:    (none)
Filesystem state:         clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              128
Block count:              1024
Reserved block count:     51
Free blocks:              986
Free inodes:              117
First block:              1
Block size:               1024
Fragment size:            1024
Reserved GDT blocks:      3
Blocks per group:         8192
Fragments per group:      8192
Inodes per group:         128
Inode blocks per group:   16
Filesystem created:       Sun Dec 16 14:56:59 2007
Last mount time:          n/a
Last write time:          Sun Dec 16 14:56:59 2007
Mount count:              0
Maximum mount count:      30
Last checked:             Sun Dec 16 14:56:59 2007
Check interval:           15552000 (6 months)
Next check after:         Fri Jun 13 14:56:59 2008
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               128
Default directory hash:   tea
Directory Hash Seed:      6d0e58bd-b9db-41ae-92b3-4563a02a5981


Group 0: (Blocks 1-1023)
  Primary superblock at 1, Group descriptors at 2-2
  Reserved GDT blocks at 3-5
  Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
  Inode table at 8-23 (+7)
  986 free blocks, 117 free inodes, 2 directories
  Free blocks: 38-1023
  Free inodes: 12-128

128 inodes per group, 8 inodes per block, so: 16 blocks for inode table

根據上面講過的知識簡單計算一下,塊大小是1024字節,1MB的分區共有1024個塊,第0個塊是啓動塊,啓動塊以後纔算ext2文件系統的開始,所以Group 0佔據第1個到第1023個塊,共1023個塊。塊位圖佔一個塊,共有1024×8=8192個bit,足夠表示這1023個塊了,所以只要一個塊組就夠了。默認是每8KB分配一個inode,所以1MB的分區對應128個inode,這些數據都和dumpe2fs的輸出吻合。

用常規文件製做而成的文件系統也能夠像磁盤分區同樣mount到某個目錄,例如:

$ sudo mount -o loop fs /mnt
$ cd /mnt/
$ ls -la
total 17
drwxr-xr-x  3 akaedu akaedu  1024 2008-10-25 12:20 .
drwxr-xr-x 21 root    root     4096 2008-08-18 08:54 ..
drwx------  2 root    root    12288 2008-10-25 12:20 lost+found

-o loop選項告訴mount這是一個常規文件而不是一個塊設備文件。mount會把它的數據塊中的數據看成分區格式來解釋。文件系統格式化以後在根目錄下自動生成三個子目錄:...lost+found。其它子目錄下的.表示當前目錄,..表示上一級目錄,而根目錄的...都表示根目錄自己。lost+found目錄由e2fsck工具使用,若是在檢查磁盤時發現錯誤,就把有錯誤的塊掛在這個目錄下,由於這些塊不知道是誰的,找不到主,就放在這裏「失物招領」了。

如今能夠在/mnt目錄下添加刪除文件,這些操做會自動保存到文件fs中。而後把這個分區umount下來,以確保全部的改動都保存到文件中了。

$ sudo umount /mnt

注意,下面的實驗步驟是對新建立的文件系統作的,若是你在文件系統中添加刪除過文件,跟着作下面的步驟時結果可能和我寫的不太同樣,不過也不影響理解。

如今咱們用二進制查看工具查看這個文件系統的全部字節,而且同dumpe2fs工具的輸出信息相比較,就能夠很好地理解文件系統的存儲佈局了。

$ od -tx1 -Ax fs
000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
000400 80 00 00 00 00 04 00 00 33 00 00 00 da 03 00 00
000410 75 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00
......

其中以*開頭的行表示這一段數據全是零所以省略了。下面詳細分析od輸出的信息。

從000000開始的1KB是啓動塊,因爲這不是一個真正的磁盤分區,啓動塊的內容所有爲零。從000400到0007ff的1KB是超級塊,對照着dumpe2fs的輸出信息,詳細分析以下:

圖 29.3. 超級塊

超級塊

超級塊中從0004d0到末尾的204個字節是填充字節,保留未用,上圖未畫出。注意,ext2文件系統中各字段都是按小端存儲的,若是把字節在文件中的位置看做地址,那麼靠近文件開頭的是低地址,存低字節。各字段的位置、長度和含義詳見[ULK]

從000800開始是塊組描述符表,這個文件系統較小,只有一個塊組描述符,對照着dumpe2fs的輸出信息分析以下:

...
Group 0: (Blocks 1-1023)
  Primary superblock at 1, Group descriptors at 2-2
  Reserved GDT blocks at 3-5
  Block bitmap at 6 (+5), Inode bitmap at 7 (+6)
  Inode table at 8-23 (+7)
  986 free blocks, 117 free inodes, 2 directories
  Free blocks: 38-1023
  Free inodes: 12-128
...

圖 29.4. 塊組描述符

塊組描述符

整個文件系統是1MB,每一個塊是1KB,應該有1024個塊,除去啓動塊還有1023個塊,分別編號爲1-1023,它們全都屬於Group 0。其中,Block 1是超級塊,接下來的塊組描述符指出,塊位圖是Block 6,所以中間的Block 2-5是塊組描述符表,其中Block 3-5保留未用。塊組描述符還指出,inode位圖是Block 7,inode表是從Block 8開始的,那麼inode表到哪一個塊結束呢?因爲超級塊中指出每一個塊組有128個inode,每一個inode的大小是128字節,所以共佔16個塊,inode表的範圍是Block 8-23。

從Block 24開始就是數據塊了。塊組描述符中指出,空閒的數據塊有986個,因爲文件系統是新建立的,空閒塊是連續的Block 38-1023,用掉了前面的Block 24-37。從塊位圖中能夠看出,前37位(前4個字節加最後一個字節的低5位)都是1,就表示Block 1-37已用:

001800 ff ff ff ff 1f 00 00 00 00 00 00 00 00 00 00 00
001810 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
001870 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80
001880 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
*

在塊位圖中,Block 38-1023對應的位都是0(一直到001870那一行最後一個字節的低7位),接下來的位已經超出了文件系統的空間,無論是0仍是1都沒有意義。可見,塊位圖每一個字節中的位應該按從低位到高位的順序來看。之後隨着文件系統的使用和添加刪除文件,塊位圖中的1就變得不連續了。

塊組描述符指出,空閒的inode有117個,因爲文件系統是新建立的,空閒的inode也是連續的,inode編號從1到128,空閒的inode編號從12到128。從inode位圖能夠看出,前11位都是1,表示前11個inode已用:

001c00 ff 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00
001c10 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
*

之後隨着文件系統的使用和添加刪除文件,inode位圖中的1就變得不連續了。

001c00這一行的128位就表示了全部inode,所以下面的行無論是0仍是1都沒有意義。已用的11個inode中,前10個inode是被ext2文件系統保留的,其中第2個inode是根目錄,第11個inode是lost+found目錄,塊組描述符也指出該組有兩個目錄,就是根目錄和lost+found

探索文件系統還有一個頗有用的工具debugfs,它提供一個命令行界面,能夠對文件系統作各類操做,例如查看信息、恢復數據、修正文件系統中的錯誤。下面用debugfs打開fs文件,而後在提示符下輸入help看看它都能作哪些事情:

$ debugfs fs
debugfs 1.40.2 (12-Jul-2007)
debugfs:  help

debugfs的提示符下輸入stat /命令,這時在新的一屏中顯示根目錄的inode信息:

Inode: 2   Type: directory    Mode:  0755   Flags: 0x0   Generation: 0
User:  1000   Group:  1000   Size: 1024
File ACL: 0    Directory ACL: 0
Links: 3   Blockcount: 2
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
atime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
mtime: 0x4764cc3b -- Sun Dec 16 14:56:59 2007
BLOCKS:
(0):24
TOTAL: 1

按q退出這一屏,而後用quit命令退出debugfs

debugfs:  quit

把以上信息和od命令的輸出對照起來分析:

圖 29.5. 根目錄的inode

根目錄的inode

上圖中的st_mode以八進制表示,包含了文件類型和文件權限,最高位的4表示文件類型爲目錄(各類文件類型的編碼詳見stat(2)),低位的755表示權限。Size是1024,說明根目錄如今只有一個數據塊。Links爲3表示根目錄有三個硬連接,分別是根目錄下的...,以及lost+found子目錄下的..。注意,雖然咱們一般用/表示根目錄,可是並無名爲/的硬連接,事實上,/是路徑分隔符,不能在文件名中出現。這裏的Blockcount是以512字節爲一個塊來數的,並不是格式化文件系統時所指定的塊大小,磁盤的最小讀寫單位稱爲扇區(Sector),一般是512字節,因此Blockcount是磁盤的物理塊數量,而非分區的邏輯塊數量。根目錄數據塊的位置由上圖中的Blocks[0]指出,也就是第24個塊,它在文件系統中的位置是24×0x400=0x6000,從od命令的輸出中找到006000地址,它的格式是這樣:

圖 29.6. 根目錄的數據塊

根目錄的數據塊

目錄的數據塊由許多不定長的記錄組成,每條記錄描述該目錄下的一個文件,在上圖中用框表示。第一條記錄描述inode號爲2的文件,也就是根目錄自己,該記錄的總長度爲12字節,其中文件名的長度爲1字節,文件類型爲2(見下表,注意此處的文件類型編碼和st_mode不一致),文件名是.

表 29.1. 目錄中的文件類型編碼

編碼 文件類型
0 Unknown
1 Regular file
2 Directory
3 Character device
4 Block device
5 Named pipe
6 Socket
7 Symbolic link

第二條記錄也是描述inode號爲2的文件(根目錄),該記錄總長度爲12字節,其中文件名的長度爲2字節,文件類型爲2,文件名字符串是..。第三條記錄一直延續到該數據塊的末尾,描述inode號爲11的文件(lost+found目錄),該記錄的總長度爲1000字節(和前面兩條記錄加起來是1024字節),文件類型爲2,文件名字符串是lost+found,後面全是0字節。若是要在根目錄下建立新的文件,能夠把第三條記錄截短,在原來的0字節處建立新的記錄。若是該目錄下的文件名太多,一個數據塊不夠用,則會分配新的數據塊,塊編號會填充到inode的Blocks[1]字段。

debugfs也提供了cdls等命令,不須要mount就能夠查看這個文件系統中的目錄,例如用ls查看根目錄:

 2  (12) .    2  (12) ..    11  (1000) lost+found

列出了inode號、記錄長度和文件名,這些信息都是從根目錄的數據塊中讀出來的。

習題

一、請讀者仿照對根目錄的分析,本身分析lost+found目錄的inode和數據塊的格式。

二、mount這個文件系統,在裏面添加刪除文件,而後umount下來,再次分析它的格式,和原來的結果比較一下看哪些字節發生了變化。

數據塊尋址

若是一個文件有多個數據塊,這些數據塊極可能不是連續存放的,應該如何尋址到每一個塊呢?根據上面的分析,根目錄的數據塊是經過其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個數據塊,依此類推。

圖 29.7. 數據塊的尋址

數據塊的尋址

從上圖能夠看出,索引項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和數據塊每每已經被內核緩存了,讀大文件的效率也不會過低。

文件和目錄操做的系統函數

本節簡要介紹一下文件和目錄操做經常使用的系統函數,經常使用的文件操做命令如lscpmv等也是基於這些函數實現的。本節的側重點在於講解這些函數的工做原理,而不是如何使用它們,理解了實現原理以後再看這些函數的用法就很簡單了,請讀者本身查閱Man Page瞭解其用法。

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

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

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

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

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

  5. 讀出file文件的inode

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

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

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

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

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

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

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

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

rename(2)函數改變文件名,須要修改目錄數據塊中的文件名記錄,若是原文件名和新文件名不在一個目錄下則須要從原目錄數據塊中清除一條記錄而後添加到新目錄的數據塊中。mv(1)命令是基於rename函數實現的,所以在同一分區的不一樣目錄中移動文件並不須要複製和刪除文件的inode和數據塊,只須要一個更名操做,即便要移動整個目錄,這個目錄下有不少子目錄和文件也要隨着一塊兒移動,移動操做也只是對頂級目錄的更名操做,很快就能完成。可是,若是在不一樣的分區之間移動文件就必須複製和刪除inode和數據塊,若是要移動整個目錄,全部子目錄和文件都要複製刪除,這就很慢了。

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

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

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

opendir(3)/readdir(3)/closedir(3)用於遍歷目錄數據塊中的記錄。opendir打開一個目錄,返回一個DIR *指針表明這個目錄,它是一個相似FILE *指針的句柄,closedir用於關閉這個句柄,把DIR *指針傳給readdir讀取目錄數據塊中的記錄,每次返回一個指向struct dirent的指針,反覆讀就能夠遍歷全部記錄,全部記錄遍歷完以後readdir返回NULL。結構體struct dirent的定義以下:

struct dirent {
	ino_t          d_ino;       /* inode number */
	off_t          d_off;       /* offset to the next dirent */
	unsigned short d_reclen;    /* length of this record */
	unsigned char  d_type;      /* type of file */
	char           d_name[256]; /* filename */
};

這些字段和圖 29.6 「根目錄的數據塊」基本一致。這裏的文件名d_name被庫函數處理過,已經在結尾加了'\0',而圖 29.6 「根目錄的數據塊」中的文件名字段不保證是以'\0'結尾的,須要根據前面的文件名長度字段肯定文件名到哪裏結束。

下面這個例子出自[K&R],做用是遞歸地打印出一個目錄下的全部子目錄和文件,相似ls -R

例 29.1. 遞歸列出目錄中的文件列表

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>

#define MAX_PATH 1024

/* dirwalk:  apply fcn to all files in dir */
void dirwalk(char *dir, void (*fcn)(char *))
{
	char name[MAX_PATH];
	struct dirent *dp;
	DIR *dfd;

	if ((dfd = opendir(dir)) == NULL) {
		fprintf(stderr, "dirwalk: can't open %s\n", dir);
		return;
	}
	while ((dp = readdir(dfd)) != NULL) {
		if (strcmp(dp->d_name, ".") == 0
		    || strcmp(dp->d_name, "..") == 0)
			continue;    /* skip self and parent */
		if (strlen(dir)+strlen(dp->d_name)+2 > sizeof(name))
			fprintf(stderr, "dirwalk: name %s %s too long\n",
				dir, dp->d_name);
		else {
			sprintf(name, "%s/%s", dir, dp->d_name);
			(*fcn)(name);
		}
	}
	closedir(dfd);
}

/* fsize:  print the size and name of file "name" */
void fsize(char *name)
{
	struct stat stbuf;

	if (stat(name, &stbuf) == -1) {
		fprintf(stderr, "fsize: can't access %s\n", name);
		return;
	}
	if ((stbuf.st_mode & S_IFMT) == S_IFDIR)
		dirwalk(name, fsize);
	printf("%8ld %s\n", stbuf.st_size, name);
}

int main(int argc, char **argv)
{
	if (argc == 1)  /* default: current directory */
		fsize(".");
	else
		while (--argc > 0)
			fsize(*++argv);
	return 0;
}

然而這個程序仍是不如ls -R健壯,它有可能死循環,思考一下什麼狀況會致使死循環。

相關文章
相關標籤/搜索