說明:本文為本人學習總結,非原創,很多圖片和內容出處都是從其他文章上copy,參考文章在文末有列舉。
ext文件系統
文件系統的類型有很多種,如CentOS 5和CentOS 6上默認使用的ext2/ext3/ext4,CentOS 7上默認使用的xfs,windows上的NTFS,光盤類的文件系統ISO9660,MAC上的混合文件系統HFS,網絡文件系統NFS,Oracle研發的btrfs,還有老式的FAT/FAT32等。
本文將非常全面且詳細地介紹ext家族的文件系統,中間還非常詳細地介紹了inode、軟鏈接、硬鏈接、數據存儲方式以及操作文件的理論,基本上看完本文,對文件系統的宏觀理解將再無疑惑。ext家族的文件系統有ext2/ext3/ext4,ext3是有日志的ext2改進版,ext4對相比ext3做了非常多的改進。雖然xfs/btrfs等文件系統有所不同,但它們只是在實現方式上不太同,再加上屬于自己的特性而已。
文件系統完整結構
文件系統完整結構圖如下:
首先,一個ext文件系統會分成Boot Block
加多個 Block Group
。
其次,在每個Block Group
中又分為 Super Block、GDT、Reserver GDT、bmap、inode table、imap、數據區的blocks。
圖中指明了塊組中每個部分占用的block數量,除了superblock、bmap、imap能確定占用1個block,其他的部分都不能確定占用幾個block。
最后,圖中指明了Superblock、GDT和Reserved GDT是同時出現且不一定存在于每一個塊組中的,也指明了bmap、imap、inode table和data blocks是每個塊組都有的。
引導塊
即上圖中的Boot Block部分,也稱為boot sector。它位于分區上的第一個塊,占用1024字節,并非所有分區都有這個boot sector,只有裝了操作系統的主分區和裝了操作系統的邏輯分區才有。里面存放的也是boot loader,這段boot loader稱為VBR(主分區裝操作系統時)或EBR(擴展分區裝操作系統時),這里的Boot loader和mbr上的boot loader是存在交錯關系的。開機啟動的時候,首先加載mbr中的bootloader,然后定位到操作系統所在分區的boot serctor上加載此處的boot loader。如果是多系統,加載mbr中的bootloader后會列出操作系統菜單,菜單上的各操作系統指向它們所在分區的boot sector上。它們之間的關系如下圖所示。
但是,這種方式的操作系統菜單早已經棄之不用了,而是使用grub來管理啟動菜單。盡管如此,在安裝操作系統時,仍然有一步是選擇boot loader安裝位置的步驟。
超級塊
既然一個文件系統會分多個塊組,那么文件系統怎么知道分了多少個塊組呢?每個塊組又有多少block多少inode號等等信息呢?還有,文件系統本身的屬性信息如各種時間戳、block總數量和空閑數量、inode總數量和空閑數量、當前文件系統是否正常、什么時候需要自檢等等,它們又存儲在哪里呢?
毫無疑問,這些信息必須要存儲在block中。存儲這些信息占用1024字節,所以也要一個block,這個block稱為超級塊(superblock),它的block號可能為0也可能為1。如果block大小為1K,則引導塊正好占用一個block,這個block號為0,所以superblock的號為1;如果block大小大于1K,則引導塊和超級塊同置在一個block中,這個block號為0。總之superblock的起止位置是第二個1024(1024-2047)字節。
使用df命令讀取的就是每個文件系統的superblock,所以它的統計速度非常快。相反,用du命令查看一個較大目錄的已用空間就非常慢,因為不可避免地要遍歷整個目錄的所有文件。
[root@xuexi ~]# df -hT
Filesystem Type Size Used Avail Use% Mounted on
/dev/sda3 ext4 18G 1.7G 15G 11% /
tmpfs tmpfs 491M 0 491M 0% /dev/shm
/dev/sda1 ext4 190M 32M 149M 18% /boot
superblock對于文件系統而言是至關重要的,超級塊丟失或損壞必將導致文件系統的損壞。所以舊式的文件系統將超級塊備份到每一個塊組中,但是這又有所空間浪費,所以ext2文件系統只在塊組0、1和3、5、7冪次方的塊組中保存超級塊的信息,如Group9、Group25等。盡管保存了這么多的superblock,但是文件系統只使用第一個塊組即Group0中超級塊信息來獲取文件系統屬性,只有當Group0上的superblock損壞或丟失才會找下一個備份超級塊復制到Group0中來恢復文件系統。
下圖是一個ext4文件系統的superblock的信息,ext家族的文件系統都能使用dumpe2fs -h獲取。
GDT & Reserved GDT
GDT:既然文件系統劃分了塊組,那么每個塊組的信息和屬性元數據又保存在哪里呢?
ext文件系統每一個塊組信息使用32字節描述,這32個字節稱為塊組描述符,所有塊組的塊組描述符組成塊組描述符表GDT(group descriptor table)。
雖然每個塊組都需要塊組描述符來記錄塊組的信息和屬性元數據,但是不是每個塊組中都存放了塊組描述符。ext文件系統的存儲方式是:將它們組成一個GDT,并將該GDT存放于某些塊組中,存放GDT的塊組和存放superblock和備份superblock的塊相同,也就是說它們是同時出現在某一個塊組中的。讀取時也總是讀取Group0中的塊組描述符表信息。
假如block大小為4KB的文件系統劃分了143個塊組,每個塊組描述符32字節,那么GDT就需要143*32=4576字節即兩個block來存放。這兩個GDT block中記錄了所有塊組的塊組信息,且存放GDT的塊組中的GDT都是完全相同的。
下圖是一個塊組描述符的信息(通過dumpe2fs獲取)。
Reserved GDT:保留GDT用于以后擴容文件系統使用,防止擴容后塊組太多,使得塊組描述符超出當前存儲GDT的blocks。保留GDT和GDT總是同時出現,當然也就和superblock同時出現了。
例如前面143個塊組使用了2個block來存放GDT,但是此時第二個block還空余很多空間,當擴容到一定程度時2個block已經無法再記錄塊組描述符了,這時就需要分配一個或多個Reserved GDT的block來存放超出的塊組描述符。
由于新增加了GDT block,所以應該讓每一個保存GDT的塊組都同時增加這一個GDT block,所以將保留GDT和GDT存放在同一個塊組中可以直接將保留GDT變換為GDT而無需使用低效的復制手段備份到每個存放GDT的塊組。
同理,新增加了GDT需要修改每個塊組中superblock中的文件系統屬性,所以將superblock和Reserved GDT/GDT放在一起又能提升效率。
inode
如果存儲的1個文件占用了大量的block讀取時會如何?假如block大小為1KB,僅僅存儲一個10M的文件就需要10240個block,而且這些blocks很可能在位置上是不連續在一起的(不相鄰),讀取該文件時難道要從前向后掃描整個文件系統的塊,然后找出屬于該文件的塊嗎?顯然是不應該這么做的,因為太慢太傻瓜式了。再考慮一下,讀取一個只占用1個block的文件,難道只讀取一個block就結束了嗎?并不是,仍然是掃描整個文件系統的所有block,因為它不知道什么時候掃描到,掃描到了它也不知道這個文件是不是已經完整而不需要再掃描其他的block。
另外,每個文件都有屬性(如權限、大小、時間戳等),這些屬性類的元數據存儲在哪里呢?難道也和文件的數據部分存儲在塊中嗎?如果一個文件占用多個block那是不是每個屬于該文件的block都要存儲一份文件元數據?但是如果不在每個block中存儲元數據文件系統又怎么知道某一個block是不是屬于該文件呢?但是顯然,每個數據block中都存儲一份元數據太浪費空間。
文件系統設計者當然知道這樣的存儲方式很不理想,所以需要優化存儲方式。如何優化?對于這種類似的問題的解決方法是使用索引,通過掃描索引找到對應的數據,而且索引可以存儲部分數據。
在文件系統上索引技術具體化為索引節點(index node),在索引節點上存儲的部分數據即為文件的屬性元數據及其他少量信息。一般來說索引占用的空間相比其索引的文件數據而言占用的空間就小得多,掃描它比掃描整個數據要快得多,否則索引就沒有存在的意義。這樣一來就解決了前面所有的問題。
在文件系統上的術語中,索引節點稱為inode。在inode中存儲了文件類型、權限、文件所有者、大小、時間戳等元數據信息,最重要的是還存儲了指向屬于該文件block的指針,這樣讀取inode就可以找到屬于該文件的block,進而讀取這些block并獲得該文件的數據。由于后面還會介紹一種指針,為了方便稱呼和區分,暫且將這個inode記錄中指向文件data block的指針稱之為block指針。以下是ext2文件系統中inode包含的信息示例:
Inode: 12 Type: regular Mode: 0644 Flags: 0x0
Generation: 1454951771 Version: 0x00000000:00000001
User: 0 Group: 0 Size: 5
File ACL: 0 Directory ACL: 0
Links: 1 Blockcount: 8
Fragment: Address: 0 Number: 0 Size: 0
ctime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
atime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
mtime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
crtime: 0x5b628db2:15e0aff4 -- Thu Aug 2 12:50:58 2018
Size of extra inode fields: 28
BLOCKS:
(0):1024
TOTAL: 1
一般inode大小為128字節或256字節,相比那些MB或GB計算的文件數據而言小得多的多,但也要知道可能一個文件大小小于inode大小,例如只占用1個字節的文件。
在目錄下使用ls -i
可以查看當前目錄下文件名和inode num。
ls -i
135840 Applications 114330 Library 114326 Pictures 23143634 cursor-tutor 541718 software
114328 Desktop 114371 Movies 114367 Public
inode 大小和劃分
inode大小為128字節的倍數,最小為128字節。它有默認值大小,它的默認值由/etc/mke2fs.conf文件中指定。不同的文件系統默認值可能不同。
[root@xuexi ~]# cat /etc/mke2fs.conf
[defaults]
base_features = sparse_super,filetype,resize_inode,dir_index,ext_attr
enable_periodic_fsck = 1
blocksize = 4096
inode_size = 256
inode_ratio = 16384
[fs_types]
ext3 = {
features = has_journal
}
ext4 = {
features = has_journal,extent,huge_file,flex_bg,uninit_bg,dir_nlink,extra_isize
inode_size = 256
}
同樣觀察到這個文件中還記錄了blocksize的默認值和inode分配比率inode_ratio。inode_ratio=16384表示每16384個字節即16KB就分配一個inode號,由于默認blocksize=4KB,所以每4個block就分配一個inode號。當然分配的這些inode號只是預分配,并不真的代表會全部使用,畢竟每個文件才會分配一個inode號。但是分配的inode自身會占用block,而且其自身大小256字節還不算小,所以inode號的浪費代表著空間的浪費。
既然知道了inode分配比率,就能計算出每個塊組分配多少個inode號,也就能計算出inode table占用多少個block。
如果文件系統中大量存儲電影等大文件,inode號就浪費很多,inode占用的空間也浪費很多。但是沒辦法,文件系統又不知道你這個文件系統是用來存什么樣的數據,多大的數據,多少數據。
當然inode size、inode分配比例、block size都可以在創建文件系統的時候人為指定。
bmap
在向硬盤存儲數據時,文件系統需要知道哪些塊是空閑的,哪些塊是已經占用了的。最笨的方法當然是從前向后掃描,遇到空閑塊就存儲一部分,繼續掃描直到存儲完所有數據。
優化的方法當然也可以考慮使用索引,但是僅僅1G的文件系統就有1KB的block共1024*1024=1048576個,這僅僅只是1G,如果是100G、500G甚至更大呢,僅僅使用索引索引的數量和空間占用也將極大,這時就出現更高一級的優化方法:使用塊位圖(bitmap簡稱bmap)。
位圖只使用0和1標識對應block是空閑還是被占用,0和1在位圖中的位置和block的位置一一對應,第一位標識第一個塊,第二個位標識第二個塊,依次下去直到標記完所有的block。
考慮下為什么塊位圖更優化。在位圖中1個字節8個位,可以標識8個block。對于一個block大小為1KB、容量為1G的文件系統而言,block數量有10241024個,所以在位圖中使用10241024個位共1024*1024/8=131072字節=128K,即1G的文件只需要128個block做位圖就能完成一一對應。通過掃描這100多個block就能知道哪些block是空閑的,速度提高了非常多。
但是要注意,bmap的優化針對的是寫優化,因為只有寫才需要找到空閑block并分配空閑block
。對于讀而言,只要通過inode找到了block的位置,cpu就能迅速計算出block在物理磁盤上的地址,cpu的計算速度是極快的,計算block地址的時間幾乎可以忽略,那么讀速度基本認為是受硬盤本身性能的影響而與文件系統無關。大多數稍大一點的文件可能都會存儲在不連續的block上,而且使用了一段時間的文件系統可能會有不少碎片,這時硬盤的隨機讀取性能直接決定讀數據的速度,這也是機械硬盤速度相比固態硬盤慢的多的多的原因之一,而且固態硬盤的隨機讀和連續讀取速度幾乎是一致的,對它來說,文件系統碎片的多少并不會影響讀取速度。
雖然bmap已經極大的優化了掃描,但是仍有其瓶頸:如果文件系統是100G呢?100G的文件系統要使用128*100=12800個1KB大小的block,這就占用了12.5M的空間了。試想完全掃描12800個很可能不連續的block這也是需要占用一些時間的,雖然快但是扛不住每次存儲文件都要掃描帶來的巨大開銷。
imap
前面說bmap是塊位圖,用于標識文件系統中哪些block是空閑哪些block是占用的。
對于inode也一樣,在存儲文件(Linux中一切皆文件)時需要為其分配一個inode號。但是在格式化創建文件系統后所有的inode號都已被事先計算好(創建文件系統時會為每個塊組計算好該塊組擁有哪些inode號),因此產生了問題:要為文件分配哪一個inode號呢?又如何知道某一個inode號是否已經被分配了呢?
既然是"是否被占用"的問題,使用位圖是最佳方案,像bmap記錄block的占用情況一樣。標識inode號是否被分配的位圖稱為inodemap簡稱為imap。這時要為一個文件分配inode號只需掃描imap即可知道哪一個inode號是空閑的。
imap存在著和bmap和inode table一樣需要解決的問題:如果文件系統比較大,imap本身就會很大,每次存儲文件都要進行掃描,會導致效率不夠高。同樣,優化的方式是將文件系統占用的block劃分成塊組,每個塊組有自己的imap范圍。
inode table
回顧下inode相關信息:inode存儲了inode號(注,同前文,inode中并未存儲inode num)、文件屬性元數據、指向文件占用的block的指針;每一個inode占用128字節或256字節。
現在又出現問題了,一個文件系統中可以說有無數多個文件,每一個文件都對應一個inode,難道每一個僅128字節的inode都要單獨占用一個block進行存儲嗎?這太浪費空間了。
所以更優的方法是將多個inode合并存儲在block中,對于128字節的inode,一個block存儲8個inode,對于256字節的inode,一個block存儲4個inode。這就使得每個存儲inode的塊都不浪費。
在ext文件系統上,將這些物理上存儲inode的block組合起來,在邏輯上形成一張inode表(inode table)來記錄所有的inode。
舉個例子,每一個家庭都要向派出所登記戶口信息,通過戶口本可以知道家庭住址,而每個鎮或街道的派出所將本鎮或本街道的所有戶口整合在一起,要查找某一戶地址時,在派出所就能快速查找到。inode table就是這里的派出所。它的內容如下圖所示。
再細細一思考,就能發現一個大的文件系統仍將占用大量的塊來存儲inode,想要找到其中的一個inode記錄也需要不小的開銷,盡管它們已經形成了一張邏輯上的表,但扛不住表太大記錄太多。那么如何快速找到inode,這同樣是需要優化的,優化的方法是將文件系統的block進行分組劃分,每個組中都存有本組inode table范圍、bmap等。
塊組
將文件系統占用的block劃分成塊組(block group),解決bmap、inode table和imap太大的問題。
在物理層面上的劃分是將磁盤按柱面劃分為多個分區,即多個文件系統;在邏輯層面上的劃分是將文件系統劃分成塊組。每個文件系統包含多個塊組,每個塊組包含多個元數據區和數據區:元數據區就是存儲bmap、inode table、imap等的數據;數據區就是存儲文件數據的區域。注意塊組是邏輯層面的概念,所以并不會真的在磁盤上按柱面、按扇區、按磁道等概念進行劃分。
塊組如何劃分?
塊組在文件系統創建完成后就已經劃分完成了,也就是說元數據區bmap、inode table和imap等信息占用的block以及數據區占用的block都已經劃分好了。那么文件系統如何知道一個塊組元數據區包含多少個block,數據區又包含多少block呢?
它只需確定一個數據——每個block的大小,再根據bmap至多只能占用一個完整的block的標準就能計算出塊組如何劃分。如果文件系統非常小,所有的bmap總共都不能占用完一個block,那么也只能空閑bmap的block了。
每個block的大小在創建文件系統時可以人為指定,不指定也有默認值。
假如現在block的大小是1KB,一個bmap完整占用一個block能標識1024*8= 8192個block(當然這8192個block是數據區和元數據區共8192個,因為元數據區分配的block也需要通過bmap來標識)。每個block是1K,每個塊組是8192K即8M,創建1G的文件系統需要劃分1024/8=128個塊組,如果是1.1G的文件系統呢?128+12.8=128+13=141個塊組。
每個組的block數目是劃分好了,但是每個組設定多少個inode號呢?inode table占用多少block呢?這需要由系統決定了,因為描述"每多少個數據區的block就為其分配一個inode號"的指標默認是我們不知道的,當然創建文件系統時也可以人為指定這個指標或者百分比例。見后文"inode深入"。
使用dumpe2fs可以將ext類的文件系統信息全部顯示出來,當然bmap是每個塊組固定一個block的不用顯示,imap比bmap更小所以也只占用1個block不用顯示。
下圖是一個文件系統的部分信息,在這些信息的后面還有每個塊組的信息,其實這里面的很多信息都可以通過幾個比較基本的元數據推導出來。
從這張表中能計算出文件系統的大小,該文件系統共4667136個blocks,每個block大小為4K,所以文件系統大小為4667136*4/1024/1024=17.8GB。
也能計算出分了多少個塊組,因為每一個塊組的block數量為32768,所以塊組的數量為4667136/32768=142.4即143個塊組。由于塊組從0開始編號,所以最后一個塊組編號為Group 142。如下圖所示是最后一個塊組的信息。
data blocks
data block是直接存儲數據的block,但事實上并非如此簡單。
數據所占用的block由文件對應inode記錄中的block指針找到,不同的文件類型,數據block中存儲的內容是不一樣的。以下是Linux中不同類型文件的存儲方式。
- 對于常規文件,文件的數據正常存儲在數據塊中。
- 對于目錄,該目錄下的所有文件和一級子目錄的目錄名存儲在數據塊中。
- 文件名和inode號不是存儲在其自身的inode中,而是存儲在其所在目錄的data block中。
- 對于符號鏈接,如果目標路徑名較短則直接保存在inode中以便更快地查找,如果目標路徑名較長則分配一個數據塊來保存。
- 設備文件、FIFO和socket等特殊文件沒有數據塊,設備文件的主設備號和次設備號保存在inode中。
常規文件的存儲就不解釋了,下面分別解釋特殊文件的存儲方式。
目錄文件的data block
目錄的data block的內容如下圖所示:
由圖可知,在目錄文件的數據塊中存儲了其下的文件名、目錄名、目錄本身的相對名稱"."和上級目錄的相對名稱"..",還存儲了這些文件名對應的inode號、目錄項長度rec_len、文件名長度name_len和文件類型file_type。注意到除了文件本身的inode記錄了文件類型,其所在的目錄的數據塊也記錄了文件類型。由于rec_len只能是4的倍數,所以需要使用"\0"來填充name_len不夠湊滿4倍數的部分。至于rec_len具體是什么,只需知道它是一種偏移即可。
需要注意的是,inode table中的inode自身并沒有存儲每個inode的inode號,它是存儲在目錄的data block中的,通過inode號可以計算并索引到inode table中該inode號對應的inode記錄,可以認為這個inode號是一個inode指針 (當然,并非真的是指針,但有助于理解通過inode號索引找到對應inode的這個過程,后文將在需要的時候使用inode指針這個詞來表示inode號。至此,已經知道了兩種指針:一種是inode table中每個inode記錄指向其對應data block的block指針,一個此處的“inode指針”)。
除了inode號,目錄的data block中還使用數字格式記錄了文件類型,數字格式和文件類型的對應關系如下圖:
注意到目錄的data block中前兩行存儲的是目錄本身的相對名稱"."和上級目錄的相對名稱"..",它們實際上是目錄本身的硬鏈接和上級目錄的硬鏈接。硬鏈接的本質后面說明。
如何根據inode號找到inode
前面提到過,inode結構自身并沒有保存inode號(同樣,也沒有保存文件名),那么inode號保存在哪里呢?目錄的data block中保存了該目錄中每個文件的inode號。
另一個問題,既然inode中沒有inode號,那么如何根據目錄data block中的inode號找到inode table中對應的inode呢?
實際上,只要有了inode號,就可以計算出inode表中對應該inode號的inode結構。在創建文件系統的時候,每個塊組中的起始inode號以及inode table的起始地址都已經確定了,所以只要知道inode號,就能知道這個inode號和該塊組起始inode號的偏移數量,再根據每個inode結構的大小(256字節或其它大小),就能計算出來對應的inode結構。
所以,目錄的data block中的inode number和inode table中的inode是通過計算的方式一一映射起來的。從另一個角度上看,目錄data block中的inode number是找到inode table中對應inode記錄的唯一方式。
考慮一種比較特殊的情況:目錄data block的記錄已經刪除,但是該記錄對應的inode結構仍然存在于inode table中。這種inode稱為孤兒inode(orphan inode):存在于inode table中,但卻無法再索引到它。因為目錄中已經沒有該inode對應的文件記錄了,所以其它進程將無法找到該inode,也就無法根據該inode找到該文件之前所占用的data block,這正是創建便刪除所實現的真正臨時文件,該臨時文件只有當前進程和子進程才能訪問。
文件操作
文件讀取
當執行"cat /var/log/messages"命令在系統內部進行了什么樣的步驟呢?該命令能被成功執行涉及了cat命令的尋找、權限判斷以及messages文件的尋找和權限判斷等等復雜的過程。這里只解釋和本節內容相關的如何尋找到被cat的/var/log/messages文件。
- 找到根文件系統的塊組描述符表所在的blocks,讀取GDT(已在內存中)找到inode table的block號。
因為GDT總是和superblock在同一個塊組,而superblock總是在分區的第1024-2047個字節,所以很容易就知道第一個GDT所在的塊組以及GDT在這個塊組中占用了哪些block。
其實GDT早已經在內存中了,在系統開機的時候會掛載根文件系統,掛載的時候就已經將所有的GDT放進內存中。
- 在inode table的block中定位到根"/"的inode,找出"/"指向的data block。
前文說過,ext文件系統預留了一些inode號,其中"/"的inode號為2,所以可以根據inode號直接定位根目錄文件的data block。
- 在"/"的datablock中記錄了var目錄名和var的inode號,找到該inode記錄,inode記錄中存儲了指向var的block指針,所以也就找到了var目錄文件的data block。
通過var目錄的inode號,可以尋找到var目錄的inode記錄,但是在尋找的過程中,還需要知道該inode記錄所在的塊組以及所在的inode table,所以需要讀取GDT,同樣,GDT已經緩存到了內存中。
在var的data block中記錄了log目錄名和其inode號,通過該inode號定位到該inode所在的塊組及所在的inode table,并根據該inode記錄找到log的data block。
在log目錄文件的data block中記錄了messages文件名和對應的inode號,通過該inode號定位到該inode所在的塊組及所在的inode table,并根據該inode記錄找到messages的data block。
最后讀取messages對應的datablock。
將上述步驟中GDT部分的步驟簡化后比較容易理解。如下:找到GDT-->找到"/"的inode-->找到/的數據塊讀取var的inode-->找到var的數據塊讀取log的inode-->找到log的數據塊讀取messages的inode-->找到messages的數據塊并讀取它們。
當然,在每次定位到inode記錄后,都會先將inode記錄加載到內存中,然后查看權限,如果權限允許,將根據block指針找到對應的data block。
文件創建過程
- 讀取GDT,找到各個(或部分)塊組imap中未使用的inode號,并為待存儲文件分配inode號;
- 在inode table中完善該inode號所在行的記錄;
- 在目錄的data block中添加一條該文件的相關記錄;
- 將數據填充到data block中。
注意,填充到data block中的時候會調用block分配器:一次分配4KB大小的block數量,當填充完4KB的data block后會繼續調用block分配器分配4KB的block,然后循環直到填充完所有數據。也就是說,如果存儲一個100M的文件需要調用block分配器100*1024/4=25600次。
另一方面,在block分配器分配block時,block分配器并不知道真正有多少block要分配,只是每次需要分配時就分配,在每存儲一個data block前,就去bmap中標記一次該block已使用,它無法實現一次標記多個bmap位。這一點在ext4中進行了優化。 - 填充完之后,去inode table中更新該文件inode記錄中指向data block的尋址指針。
文件copy過程
文件拷貝的過程和創建過程基本一致;
文件移動過程
同文件系統下移動文件實際上是修改目標文件所在目錄的data block,向其中添加一行指向inode table中待移動文件的inode指針,如果目標路徑下有同名文件,則會提示是否覆蓋,實際上是覆蓋目錄data block中沖突文件的記錄,由于同名文件的inode記錄指針被覆蓋,所以無法再找到該文件的data block,也就是說該文件被標記為刪除(如果多個硬鏈接數,則另當別論)。
所以在同文件系統內移動文件相當快,僅僅在所在目錄data block中添加或覆蓋了一條記錄而已。也因此,移動文件時,文件的inode號是不會改變的。
對于不同文件系統內的移動,相當于先復制再刪除的動作。
文件刪除過程
注意這里是不跨越文件系統的操作行為。
刪除文件分為普通文件和目錄文件,知道了這兩種類型的文件的刪除原理,就知道了其他類型特殊文件的刪除方法。
對于刪除普通文件:
- 找到文件的inode和data block(根據前一個小節中的方法尋找);
- 將inode table中該inode記錄中的data block指針刪除;
- 在imap中將該文件的inode號標記為未使用;
- 在其所在目錄的data block中將該文件名所在的記錄行刪除,刪除了記錄就丟失了指向inode的指針(實際上不是真的刪除,直接刪除的話會在目錄data block的數據結構中產生空洞,所以實際的操作是將待刪除文件的inode號設置為特殊的值0,這樣下次新建文件時就可以重用該行記錄);
- 將bmap中data block對應的block號標記為未使用。
對于刪除目錄文件:
- 找到目錄和目錄下所有文件、子目錄、子文件的inode和data block;
- 在imap中將這些inode號標記為未使用;
- 將bmap中將這些文件占用的 block號標記為未使用;
- 在該目錄的父目錄的data block中將該目錄名所在的記錄行刪除。需要注意的是,刪除父目錄data block中的記錄是最后一步,如果該步驟提前,將報目錄非空的錯誤,因為在該目錄中還有文件占用。
關于上面的(2)-(5):當(2)中刪除data block指針后,將無法再找到這個文件的數據;當(3)標記inode號未使用,表示該inode號可以被后續的文件重用;當(4)刪除目錄data block中關于該文件的記錄,真正的刪除文件,外界再也定位也無法看到這個文件了;當(5)標記data block為未使用后,表示開始釋放空間,這些data block可以被其他文件重用。
注意,在第(5)步之前,由于data block還未被標記為未使用,在superblock中仍然認為這些data block是正在使用中的。這表示盡管文件已經被刪除了,但空間卻還沒有釋放,df也會將其統計到已用空間中(df是讀取superblock中的數據塊數量,并計算轉換為空間大小)。
什么時候會發生這種情況呢?當一個進程正在引用文件時將該文件刪除,就會出現文件已刪除但空間未釋放的情況。這時步驟已經進行到(4),外界無法再找到該文件,但由于進程在加載該文件時已經獲取到了該文件所有的data block指針,該進程可以獲取到該文件的所有數據,但卻暫時不會釋放該文件空間。直到該進程結束,文件系統才將未執行的步驟(5)繼續完成。這也是為什么有時候du的統計結果比df小的原因,關于du和df統計結果的差別,詳細內容見:詳細分析du和df的統計結果為什么不一樣。