第4章 ext文件系統機制

来源:http://www.cnblogs.com/f-ck-need-u/archive/2017/06/15/7016077.html
-Advertisement-
Play Games

本文目錄: 4.1 文件系統的組成部分 4.2 文件系統的完整結構 4.3 Data Block 4.4 inode基礎知識 4.5 inode深入 4.6 單文件系統中文件操作的原理 4.7 多文件系統關聯 4.8 ext3文件系統的日誌功能 4.9 ext4文件系統 4.10 ext類文件系統的 ...



本文目錄:

4.1 文件系統的組成部分

4.2 文件系統的完整結構

4.3 Data Block

4.4 inode基礎知識

4.5 inode深入

4.6 單文件系統中文件操作的原理

4.7 多文件系統關聯

4.8 ext3文件系統的日誌功能

4.9 ext4文件系統

4.10 ext類文件系統的缺點

4.11 虛擬文件系統VFS


將磁碟進行分區,分區是將磁碟按柱面進行物理上的劃分。劃分好分區後還要進行格式化,然後再掛載才能使用(不考慮其他方法)。格式化分區的過程其實就是創建文件系統。

文件系統的類型有很多種,如CentOS 5和CentOS 6上預設使用的ext2/ext3/ext4,CentOS 7上預設使用的xfs,windows上的NTFS,光碟類的文件系統ISO9660,MAC上的混合文件系統HFS,網路文件系統NFS,Oracle研發的btrfs,還有老式的FAT/FAT32等。

本文將非常全面且詳細地對ext家族的文件系統進行介紹。有ext2/ext3/ext4,ext3是有日誌的ext2改進版,ext4對相比ext3做了非常多的改進。雖然xfs/btrfs等文件系統有所不同,但它們只是在實現方式上不太同,再加上屬於自己的特性而已。

4.1 文件系統的組成部分

4.1.1 block的出現

硬碟的讀寫IO一次是一個扇區512位元組,如果要讀寫大量文件,以扇區為單位肯定很慢很消耗性能,所以Linux中通過文件系統控制使用"塊"為讀寫單元。現在的文件系統上,塊的大小一般為1024bytes(1K)或2048bytes(2K)或4096bytes(4K)。比如需要讀一個或多個塊時,文件系統的IO管理器通知磁碟控制器要讀取哪些塊的數據,硬碟控制器將這些塊按扇區讀取出來,再通過硬碟控制器將這些扇區數據重組返回給電腦。

block的出現使得在文件系統層面上讀寫性能大大提高,也大量減少了碎片。但是它的副作用是可能造成空間浪費。由於文件系統以block為讀寫單元,即使存儲的文件只有1K大小也將占用一個block,剩餘的空間完全是浪費的。在某些業務需求下可能大量存儲小文件,這會浪費大量的空間。

儘管有缺點,但是其優點足夠明顯,在當下硬碟容量廉價且追求性能的時代,使用block是一定的。

4.1.2 inode的出現

如果存儲的1個文件占用了大量的block讀取時會如何?假如block大小為1KB,僅僅存儲一個10M的文件就需要10240個block,而且這些blocks很可能在位置上是不連續在一起的(不相鄰),讀取該文件時難道要從前向後掃描整個文件系統的塊,然後找出屬於該文件的塊嗎?顯然是不應該這麼做的,因為太慢太傻瓜式了。再考慮一下,讀取一個只占用1個block的文件,難道只讀取一個block就結束了嗎?並不是,仍然是掃描整個文件系統的所有block,因為它不知道什麼時候掃描到,掃描到了它也不知道這個文件是不是已經完整而不需要再掃描其他的block。

另外,每個文件都有屬性(如許可權、大小、時間戳等),這些屬性類的元數據存儲在哪裡呢?難道也和文件的數據部分存儲在塊中嗎?如果一個文件占用多個block那是不是每個屬於該文件的block都要存儲一份文件元數據?但是如果不在每個block中存儲元數據文件系統又怎麼知道某一個block是不是屬於該文件呢?但是顯然,每個數據block中都存儲一份元數據太浪費空間。

文件系統設計者當然知道這樣的存儲方式很不理想,所以需要優化存儲方式。如何優化?對於這種類似的問題的解決方法是使用索引,通過掃描索引找到對應的數據,而且索引可以存儲部分數據。

在文件系統上索引技術具體化為索引節點(index node),在索引節點上存儲的部分數據即為文件的屬性元數據及其他少量信息。一般來說索引占用的空間相比其索引的文件數據而言占用的空間就小得多,掃描它比掃描整個數據要快得多,否則索引就沒有存在的意義。這樣一來就解決了前面所有的問題。

在文件系統上的術語中,索引節點稱為inode。在inode中存儲了inode號、文件類型、許可權、文件所有者、大小、時間戳等元數據信息,最重要的是還存儲了指向屬於該文件block的指針,這樣讀取inode就可以找到屬於該文件的block,進而讀取這些block並獲得該文件的數據。由於後面還會介紹一種指針,為了方便稱呼和區分,暫且將這個inode記錄中指向文件data block的指針稱之為block指針,。

一般inode大小為128位元組或256位元組,相比那些MB或GB計算的文件數據而言小得多的多,但也要知道可能一個文件大小小於inode大小,例如只占用1個位元組的文件。

4.1.3 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數量有1024*1024個,所以在點陣圖中使用1024*1024個位共1024*1024/8=131072位元組=128K,即1G的文件只需要128個block做點陣圖就能完成一一對應。通過掃描這100多個block就能知道哪些block是空閑的,速度提高了非常多。

但是要註意,bmap的優化針對的是寫優化,因為只有寫才需要找到空閑block並分配空閑block。對於讀而言,只要通過inode找到了block的位置,cpu就能迅速計算出block在物理磁碟上的地址,cpu的計算速度是極快的,計算block地址的時間幾乎可以忽略,那麼讀速度基本認為是受硬碟本身性能的影響而與文件系統無關了。

雖然bmap已經極大的優化了掃描,但是仍有其瓶頸:如果文件系統是100G呢?100G的文件系統要使用128*100=12800個1KB大小的block,這就占用了12.5M的空間了。試想完全掃描12800個很可能不連續的block這也是需要占用一些時間的,雖然快但是扛不住每次存儲文件都要掃描帶來的巨大開銷。

所以需要再次優化,如何優化?簡而言之就是將文件系統劃分開形成塊組,至於塊組的介紹放在後文。

4.1.4 inode表的出現

回顧下inode相關信息:inode存儲了inode號、文件屬性元數據、指向文件占用的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 table中了,只不過被使用的inode號所在的行還有文件屬性的元數據信息和block位置信息,而未被使用的inode號只有一個inode號而已而沒有其他信息而已。

再細細一思考,就能發現一個大的文件系統仍將占用大量的塊來存儲inode,想要找到其中的一個inode記錄也需要不小的開銷,儘管它們已經形成了一張邏輯上的表,但扛不住表太大記錄太多。那麼如何快速找到inode,這同樣是需要優化的,優化的方法是將文件系統的block進行分組劃分,每個組中都存有本組inode table範圍、bmap等。

4.1.5 imap的出現

前面說bmap是塊點陣圖,用於標識文件系統中哪些block是空閑哪些block是占用的。

對於inode也一樣,在存儲文件(Linux中一切皆文件)時需要為其分配一個inode號。但是在格式化創建文件系統後所有的inode號都是被事先設定好存放在inode table中的,因此產生了問題:要為文件分配哪一個inode號呢?又如何知道某一個inode號是否已經被分配了呢?

既然是"是否被占用"的問題,使用點陣圖是最佳方案,像bmap記錄block的占用情況一樣。標識inode號是否被分配的點陣圖稱為inodemap簡稱為imap。這時要為一個文件分配inode號只需掃描imap即可知道哪一個inode號是空閑的。

imap存在著和bmap和inode table一樣需要解決的問題:如果文件系統比較大,imap本身就會很大,每次存儲文件都要進行掃描,回導致效率不夠高。同樣,優化的方式是將文件系統占用的block劃分成塊組,每個塊組有自己的imap範圍。

4.1.6 塊組的出現

前面一直提到的優化方法是將文件系統占用的block劃分成塊組(block group),解決bmap、inode table和imap太大的問題。

在物理層面上的劃分是將磁碟按柱面劃分為多個分區,即多個文件系統;在邏輯層面上的劃分是將文件系統劃分成塊組。每個文件系統包含多個塊組,每個塊組包含多個元數據區和數據區:元數據區就是存儲bmap、inode table、imap等的數據;數據區就是存儲文件數據的區域。註意塊組是邏輯層面的概念,所以並不會真的在磁碟上按柱、按扇區、按磁軌等概念進行劃分。

4.1.7 塊組的劃分

塊組在文件系統創建完成後就已經劃分完成了,也就是說元數據區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。如下圖所示是最後一個塊組的信息。

4.2 文件系統的完整結構

將上文描述的bmap、inode table、imap、數據區的blocks和塊組的概念組合起來就形成了一個文件系統,當然這還不是完整的文件系統。完整的文件系統如下圖。

首先,該圖中多了Boot Block、Super Block、GDT、Reserver GDT這幾個概念。下麵會分別介紹它們。

然後,圖中指明瞭塊組中每個部分占用的block數量,除了superblock、bmap、imap能確定占用1個block,其他的部分都不能確定占用幾個block。

最後,圖中指明瞭Superblock、GDT和Reserved GDT是同時出現且不一定存在於每一個塊組中的,也指明瞭bmap、imap、inode table和data blocks是每個塊組都有的。

4.2.1 引導塊

即上圖中的Boot Block部分,也稱為boot sector。它位於分區上的第一個塊,占用1024位元組,並非所有分區都有這個boot sector。裡面存放的也是boot loader,這段boot loader成為VBR,這裡的Boot loader和mbr上的boot loader是存在交錯關係的。開機啟動的時候,首先載入mbr中的bootloader,然後定位到操作系統所在分區的boot serctor上載入此處的boot loader。如果是多系統,載入mbr中的bootloader後會列出操作系統菜單,菜單上的各操作系統指向它們所在分區的boot sector上。它們之間的關係如下圖所示。

4.2.2 超級塊(superblock)

既然一個文件系統會分多個塊組,那麼文件系統怎麼知道分了多少個塊組呢?每個塊組又有多少block多少inode號等等信息呢?還有,文件系統本身的屬性信息如各種時間戳、block總數量和空閑數量、inode總數量和空閑數量、當前文件系統是否正常、什麼時候需要自檢等等,它們又存儲在哪裡呢?

毫無疑問,這些信息必須要存儲在block中。存儲這些信息占用1024KB,所以也要一個block,這個block稱為超級塊(superblock),它的block號可能為0也可能為1。如果block大小為1024K,則引導塊正好占用一個block,這個block號為0,所以superblock的號為1;如果block大小大於1024K,則引導塊和超級塊同置在一個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獲取。

4.2.3 塊組描述符表(GDT)

既然文件系統劃分了塊組,那麼每個塊組的信息和屬性元數據又保存在哪裡呢?

ext文件系統每一個塊組信息使用32位元組描述,這32個位元組稱為塊組描述符,所有塊組的塊組描述符組成塊組描述符表GDT(group descriptor table)。

雖然每個塊組都需要塊組描述符來記錄塊組的信息和屬性元數據,但是不是每個塊組中都存放了塊組描述符。ext文件系統的存儲方式是:將它們組成一個GDT,並將該GDT存放於某些塊組中,存放GDT的塊組和存放superblock和備份superblock的塊相同,也就是說它們是同時出現在某一個塊組中的。

假如block大小為4KB的文件系統劃分了143個塊組,每個塊組描述符32位元組,那麼GDT就需要143*32=4576位元組即兩個block來存放。這兩個GDT block中記錄了所有塊組的塊組信息,且存放GDT的塊組中的GDT都是完全相同的。

下圖是一個塊組描述符的信息(通過dumpe2fs獲取)。

4.2.4 保留GDT(Reserved GDT)

保留GDT用於以後擴容文件系統使用,防止擴容後塊組太多,使得塊組描述符超出當前存儲GDT的blocks。保留GDT和GDT總是同時出現,當然也就和superblock同時出現了。

例如前面143個塊組使用了2個block來存放GDT,但是此時第二個block還空餘很多空間,當擴容到一定程度時2個block已經無法再記錄塊組描述符了,這時就需要分配一個或多個Reserverd GDT的block來存放超出的塊組描述符。

由於新增加了GDT block,所以應該讓每一個保存GDT的塊組都同時增加這一個GDT block,所以將保留GDT和GDT存放在同一個塊組中可以直接將保留GDT變換為GDT而無需使用低效的複製手段備份到每個存放GDT的塊組。

同理,新增加了GDT需要修改每個塊組中superblock中的文件系統屬性,所以將superblock和Reserverd GDT/GDT放在一起又能提升效率。

4.3 Data Block

如上圖,除了Data Blocks其他的部分都解釋過了。data block是直接存儲數據的block,但事實上並非如此簡單。

數據所占用的block由文件對應inode記錄中的block指針找到,不同的文件類型,數據block中存儲的內容是不一樣的。以下是Linux中不同類型文件的存儲方式。

  • 對於常規文件,文件的數據正常存儲在數據塊中。
  • 對於目錄,該目錄下的所有文件和一級子目錄的目錄名存儲在數據塊中。

文件名不是存儲在其自身的inode中,而是存儲在其所在目錄的data block中。

  • 對於符號鏈接,如果目標路徑名較短則直接保存在inode中以便更快地查找,如果目標路徑名較長則分配一個數據塊來保存。
  • 設備文件、FIFO和socket等特殊文件沒有數據塊,設備文件的主設備號和次設備號保存在inode中。

常規文件的存儲就不解釋了,下麵分別解釋特殊文件的存儲方式。

4.3.1 目錄文件的data block

對於目錄文件,其inode記錄中存儲的是目錄的inode號、目錄的屬性元數據和目錄文件的block指針,這裡面沒有存儲目錄自身文件名的信息。

而其data block的存儲方式則如下圖所示。

由圖可知,在目錄文件的數據塊中存儲了其下的文件名、目錄名、目錄本身的相對名稱"."和上級目錄的相對名稱"..",還存儲了指向inode table中這些文件名對應的inode號的指針(並非直接存儲inode號碼)、目錄項長度rec_len、文件名長度name_len和文件類型file_type。註意到除了文件本身的inode記錄了文件類型,其所在的目錄的數據塊也記錄了文件類型。由於rec_len只能是4的倍數,所以需要使用"\0"來填充name_len不夠湊滿4倍數的部分。至於rec_len具體是什麼,只需知道它是一種偏移即可。

目錄的data block中並沒有直接存儲目錄中文件的inode號,它存儲的是指向inode table中對應文件inode號的指針,暫且稱之為inode指針(至此,已經知道了兩種指針:一種是inode table中每個inode記錄指向其對應data block的block指針,一個此處的inode指針)。一個很有說服力的例子,在目錄只有讀而沒有執行許可權的時候,使用"ls -l"是無法獲取到其內文件inode號的,這就表明沒有直接存儲inode號。實際上,因為在創建文件系統的時候,inode號就已經全部劃分好併在每個塊組的inode table中存放好,inode table在塊組中是有具體位置的,如果使用dumpe2fs查看文件系統,會發現每個塊組的inode table占用的block數量是完全相同的,如下圖是某分區上其中兩個塊組的信息,它們都占用249個block。

除了inode指針,目錄的data block中還使用數字格式記錄了文件類型,數字格式和文件類型的對應關係如下圖。

註意到目錄的data block中前兩行存儲的是目錄本身的相對名稱"."和上級目錄的相對名稱"..",它們實際上是目錄本身的硬鏈接和上級目錄的硬鏈接。硬鏈接的本質後面說明。

由此也就容易理解目錄許可權的特殊之處了。目錄文件的讀許可權(r)和寫許可權(w),都是針對目錄文件的數據塊本身。由於目錄文件內只有文件名、文件類型和inode指針,所以如果只有讀許可權,只能獲取文件名和文件類型信息,無法獲取其他信息,儘管目錄的data block中也記錄著文件的inode指針,但定位指針是需要x許可權的,因為其它信息都儲存在文件自身對應的inode中,而要讀取文件inode信息需要有目錄文件的執行許可權通過inode指針定位到文件對應的inode記錄上。以下是沒有目錄x許可權時的查詢狀態,可以看到除了文件名和文件類型,其餘的全是"?"。

[lisi4@xuexi tmp]$ ll -i d
ls: cannot access d/hehe: Permission denied
ls: cannot access d/haha: Permission denied
total 0
? d????????? ? ? ? ?            ? haha
? -????????? ? ? ? ?            ? hehe

註意,xfs文件系統和ext文件系統不一樣,它連文件類型都無法獲取。

4.3.2 符號鏈接存儲方式

符號鏈接即為軟鏈接,類似於Windows操作系統中的快捷方式,它的作用是指向原文件或目錄。

軟鏈接之所以也被稱為特殊文件的原因是:它一般情況下不占用data block,僅僅通過它對應的inode記錄就能將其信息描述完成;符號鏈接的大小是其指向目標路徑占用的字元個數,例如某個符號鏈接的指向方式為"rmt --> ../sbin/rmt",則其文件大小為11位元組;只有當符號鏈接指向的目標的路徑名較長(60個位元組)時文件系統才會劃分一個data block給它;它的許可權如何也不重要,因它只是一個指向原文件的"工具",最終決定是否能讀寫執行的許可權由原文件決定,所以很可能ls -l查看到的符號鏈接許可權為777。

註意,軟鏈接的block指針存儲的是目標文件名。也就是說,鏈接文件的一切都依賴於其目標文件名。這就解釋了為什麼/mnt的軟鏈接/tmp/mnt在/mnt掛載文件系統後,通過軟鏈接就能進入/mnt所掛載的文件系統。究其原因,還是因為其目標文件名"/mnt"並沒有改變。

例如以下篩選出了/etc/下的符號鏈接,註意觀察它們的許可權和它們占用的空間大小。

[root@xuexi ~]# ll /etc/ | grep '^l'
lrwxrwxrwx.  1 root root     56 Feb 18  2016 favicon.png -> /usr/share/icons/hicolor/16x16/apps/system-logo-icon.png
lrwxrwxrwx.  1 root root     22 Feb 18  2016 grub.conf -> ../boot/grub/grub.conf
lrwxrwxrwx.  1 root root     11 Feb 18  2016 init.d -> rc.d/init.d
lrwxrwxrwx.  1 root root      7 Feb 18  2016 rc -> rc.d/rc
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc0.d -> rc.d/rc0.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc1.d -> rc.d/rc1.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc2.d -> rc.d/rc2.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc3.d -> rc.d/rc3.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc4.d -> rc.d/rc4.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc5.d -> rc.d/rc5.d
lrwxrwxrwx.  1 root root     10 Feb 18  2016 rc6.d -> rc.d/rc6.d
lrwxrwxrwx.  1 root root     13 Feb 18  2016 rc.local -> rc.d/rc.local
lrwxrwxrwx.  1 root root     15 Feb 18  2016 rc.sysinit -> rc.d/rc.sysinit
lrwxrwxrwx.  1 root root     14 Feb 18  2016 redhat-release -> centos-release
lrwxrwxrwx.  1 root root     11 Apr 10  2016 rmt -> ../sbin/rmt
lrwxrwxrwx.  1 root root     14 Feb 18  2016 system-release -> centos-release

4.3.3 設備文件、FIFO、套接字文件

關於這3種文件類型的文件只需要通過inode就能完全保存它們的信息,它們不占用任何數據塊,所以它們是特殊文件。

設備文件的主設備號和次設備號也保存在inode中。以下是/dev/下的部分設備信息。註意到它們的第5列和第6列信息,它們分別是主設備號和次設備號,主設備號標識每一種設備的類型,次設備號標識同種設備類型的不同編號;也註意到這些信息中沒有大小的信息,因為設備文件不占用數據塊所以沒有大小的概念。

[root@xuexi ~]# ll /dev | tail
crw-rw---- 1 vcsa tty       7, 129 Oct  7 21:26 vcsa1
crw-rw---- 1 vcsa tty       7, 130 Oct  7 21:27 vcsa2
crw-rw---- 1 vcsa tty       7, 131 Oct  7 21:27 vcsa3
crw-rw---- 1 vcsa tty       7, 132 Oct  7 21:27 vcsa4
crw-rw---- 1 vcsa tty       7, 133 Oct  7 21:27 vcsa5
crw-rw---- 1 vcsa tty       7, 134 Oct  7 21:27 vcsa6
crw-rw---- 1 root root     10,  63 Oct  7 21:26 vga_arbiter
crw------- 1 root root     10,  57 Oct  7 21:26 vmci
crw-rw-rw- 1 root root     10,  56 Oct  7 21:27 vsock
crw-rw-rw- 1 root root      1,   5 Oct  7 21:26 zero

4.4 inode基礎知識

每個文件都有一個inode,在將inode關聯到文件後系統將通過inode號來識別文件,而不是文件名。並且訪問文件時將先找到inode,通過inode中記錄的block位置找到該文件。

4.4.1 硬鏈接

雖然每個文件都有一個inode,但是存在一種可能:多個文件的inode相同,也就即inode號、元數據、block位置都相同,這是一種什麼樣的情況呢?能夠想象這些inode相同的文件使用的都是同一條inode記錄,所以代表的都是同一個文件,這些文件所在目錄的data block中的inode指針目的地都是一樣的,只不過各指針對應的文件名互不相同而已。這種inode相同的文件在Linux中被稱為"硬鏈接"。

硬鏈接文件的inode都相同,每個文件都有一個"硬鏈接數"的屬性,使用ls -l的第二列就是被硬鏈接數,它表示的就是該文件有幾個硬鏈接。

[root@xuexi ~]# ls -l
total 48
drwxr-xr-x  5 root root  4096 Oct 15 18:07 700
-rw-------. 1 root root  1082 Feb 18  2016 anaconda-ks.cfg
-rw-r--r--  1 root root   399 Apr 29  2016 Identity.pub
-rw-r--r--. 1 root root 21783 Feb 18  2016 install.log
-rw-r--r--. 1 root root  6240 Feb 18  2016 install.log.syslog

例如下圖描述的是dir1目錄中的文件name1及其硬鏈接dir2/name2,右邊分別是它們的inode和datablock。這裡也看出了硬鏈接文件之間唯一不同的就是其所在目錄中的記錄不同。註意下圖中有一列Link Count就是標記硬鏈接數的屬性。

每創建一個文件的硬鏈接,實質上是多一個指向該inode記錄的inode指針,並且硬鏈接數加1。

刪除文件的實質是刪除該文件所在目錄data block中的對應的inode指針,所以也是減少硬鏈接次數,由於block指針是存儲在inode中的,所以不是真的刪除數據,如果仍有其他指針指向該inode,那麼該文件的block指針仍然是可用的。當硬鏈接次數為1時再刪除文件就是真的刪除文件了,此時inode記錄中block指針也將被刪除。

不能跨分區創建硬鏈接,因為不同文件系統的inode號可能會相同,如果允許創建硬鏈接,複製到另一個分區時inode可能會和此分區已使用的inode號衝突。

硬鏈接只能對文件創建,無法對目錄創建硬鏈接。之所以無法對目錄創建硬鏈接,是因為文件系統已經把每個目錄的硬鏈接創建好了,它們就是相對路徑中的"."和"..",分別標識當前目錄的硬鏈接和上級目錄的硬鏈接。每一個目錄中都會包含這兩個硬鏈接,它包含了兩個信息:(1)一個沒有子目錄的目錄文件的硬鏈接數是2,其一是目錄本身,其二是".";(2)一個包含子目錄的目錄文件,其硬鏈接數是2+子目錄數,因為每個子目錄都關聯一個父目錄的硬鏈接".."。很多人在計算目錄的硬鏈接數時認為由於包含了"."和"..",所以空目錄的硬鏈接數是2,這是錯誤的,因為".."不是本目錄的硬鏈接。另外,還有一個特殊的目錄應該納入考慮,即"/"目錄,它自身是一個文件系統的入口,是自引用(下文中會解釋自引用)的,所以"/"目錄下的"."和".."的inode號相同,硬鏈接數除去其內的子目錄後應該為3,但結果是2,不知為何?

[root@xuexi ~]# ln /tmp /mydata
ln: `/tmp': hard link not allowed for directory

為什麼文件系統自己創建好了目錄的硬鏈接就不允許人為創建呢?從"."和".."的用法上考慮,如果當前目錄為/usr,我們可以使用"./local"來表示/usr/local,但是如果我們人為創建了/usr目錄的硬鏈接/tmp/husr,難道我們也要使用"/tmp/husr/local"來表示/usr/local嗎?這其實已經是軟鏈接的作用了。若要將其認為是硬鏈接的功能,這必將導致硬鏈接維護的混亂。

不過,通過mount工具的"--bind"選項,可以將一個目錄掛載到另一個目錄下,實現偽"硬鏈接",它們的內容和inode號是完全相同的。

硬鏈接的創建方法:ln file_target link_name。

4.4.2 軟鏈接

軟鏈接就是字元鏈接,鏈接文件預設指的就是字元文件,使用"l"表示其類型。

軟鏈接在功能上等價與Windows系統中的快捷方式,它指向原文件,原文件損壞或消失,軟鏈接文件就損壞。可以認為軟鏈接inode記錄中的指針內容是目標路徑的字元串

創建方式:ln –s file_target  softlink_name

查看軟鏈接的值:readlink  softlink_name

在設置軟鏈接的時候,target雖然不要求是絕對路徑,但建議給絕對路徑。是否還記得軟鏈接文件的大小?它是根據軟鏈接所指向路徑的字元數計算的,例如某個符號鏈接的指向方式為"rmt --> ../sbin/rmt",它的文件大小為11位元組,也就是說只要建立了軟鏈接後,軟鏈接的指向路徑是不會改變的,仍然是"../sbin/rmt"。如果此時移動軟鏈接文件本身,它的指向是不會改變的,仍然是11個字元的"../sbin/rmt",但此時該軟鏈接父目錄下可能根本就不存在/sbin/rmt,也就是說此時該軟鏈接是一個被破壞的軟鏈接。

4.5 inode深入

4.5.1 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占用的空間也浪費很多。但是沒辦法,文件系統又不知道你這個文件系統是用來存什麼樣的數據,多大的數據,多少數據。

當然inodesize、inode分配比例、blocksize都可以在創建文件系統的時候人為指定。

4.5.2 ext文件系統預留的inode號

Ext預留了一些inode做特殊特性使用,如下:某些可能並非總是準確,具體的inode號對應什麼文件可以使用"find / -inum NUM"查看。

  • Ext4的特殊inode
  • Inode號    用途
  • 0      不存在0號inode
  • 1      虛擬文件系統,如/proc和/sys
  • 2      根目錄
  • 3      ACL索引
  • 4      ACL數據
  • 5      Boot  loader
  • 6      未刪除的目錄
  • 7      預留的塊組描述符inode
  • 8      日誌inode
  • 11     第一個非預留的inode,通常是lost+found目錄

所以在ext4文件系統的dumpe2fs信息中,能觀察到fisrt inode號可能為11也可能為12。

並且註意到"/"的inode號為2,這個特性在文件訪問時會用上。

需要註意的是,每個文件系統都會分配自己的inode號,不同文件系統之間是可能會出現使用相同inode號文件的。例如:

[root@xuexi ~]# find / -ignore_readdir_race -inum 2 -ls
     2    4 dr-xr-xr-x  22 root     root         4096 Jun  9 09:56 /
     2    2 dr-xr-xr-x   5 root     root         1024 Feb 25 11:53 /boot
     2    0 c---------   1 root     root              Jun  7 02:13 /dev/pts/ptmx
     2    0 -rw-r--r--   1 root     root            0 Jun  6 18:13 /proc/sys/fs/binfmt_misc/status
     2    0 drwxr-xr-x   3 root     root            0 Jun  6 18:13 /sys/fs

從結果中可見,除了根的Inode號為2,還有幾個文件的inode號也是 2,它們都屬於獨立的文件系統,有些是虛擬文件系統,如/proc和/sys。

4.5.3 ext2/3的inode直接、間接定址

前文說過,inode中保存了blocks指針,但是一條inode記錄中能保存的指針數量是有限的,否則就會超出inode大小(128位元組或256位元組)。

在ext2和ext3文件系統中,一個inode中最多只能有15個指針,每個指針使用i_block[n]表示。

前12個指針i_block[0]到i_block[11]是直接定址指針,每個指針指向一個數據區的block。如下圖所示。

13個指針i_block[12]是一級間接定址指針,它指向一個仍然存儲了指針的block即i_block[13] --> Pointerblock --> datablock。

14個指針i_block[13]是二級間接定址指針,它指向一個仍然存儲了指針的block,但是這個block中的指針還繼續指向其他存儲指針的block,即i_block[13] --> Pointerblock1 --> PointerBlock2 --> datablock。

15個指針i_block[14]是三級間接定址指針,它指向一個任然存儲了指針的block,這個指針block下還有兩次指針指向。即i_block[13] --> Pointerblock1 --> PointerBlock2 --> PointerBlock3 --> datablock。

其中由於每個指針大小為4位元組,所以每個指針block能存放的指針數量為BlockSize/4byte。例如blocksize為4KB,那麼一個Block可以存放4096/4=1024個指針。

如下圖。

為什麼要分間接和直接指針呢?如果一個inode中15個指針全是直接指針,假如每個block的大小為1KB,那麼15個指針只能指向15個block即15KB的大小,由於每個文件對應一個inode號,所以就限制了每個文件最大為15*1=15KB,這顯然是不合理的。

如果存儲大於15KB的文件而又不太大的時候,就占用一級間接指針i_block[12],這時可以存放指針數量為1024/4+12=268,所以能存放268KB的文件。

如果存儲大於268K 的文件而又不太大的時候,就繼續占用二級指針i_block[13],這時可以存放指針數量為[1024/4]^2+1024/4+12=65804,所以能存放65804KB=64M左右的文件。

如果存放的文件大於64M,那麼就繼續使用三級間接指針i_block[14],存放的指針數量為[1024/4]^3+[1024/4]^2+[1024/4]+12=16843020個指針,所以能存放16843020KB=16GB左右的文件。

如果blocksize=4KB呢?那麼最大能存放的文件大小為([4096/4]^3+[4096/4]^2+[4096/4]+12)*4/1024/1024/1024=4T左右。

當然這樣計算出來的不一定就是最大能存放的文件大小,它還受到另一個條件的限制。這裡的計算只是表明一個大文件是如何定址和分配的。

其實看到這裡的計算數值,就知道ext2和ext3對超大文件的存取效率是低下的,它要核對太多的指針,特別是4KB大小的blocksize時。而ext4針對這一點就進行了優化,ext4使用extent的管理方式取代ext2和ext3的塊映射,大大提高了效率也降低了碎片。

4.6 單文件系統中文件操作的原理

在Linux上執行刪除、複製、重命名、移動等操作時,它們是怎麼進行的呢?還有訪問文件時是如何找到它的呢?其實只要理解了前文中介紹的幾個術語以及它們的作用就很容易知道文件操作的原理了。

註:在這一小節所解釋的都是在單個文件系統下的行為,在多個文件系統中如何請看下一個小節:多文件系統關聯。

4.6.1 讀取文件

當執行"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 table,並根據該inode記錄找到log的data block。
  • 在log目錄文件的data block中記錄了messages文件名和對應的inode指針,通過該指針定位到該inode所在的塊組及所在的inode table,並根據該inode記錄找到messages的data block。
  • 最後讀取messages對應的datablock。

將上述步驟中GDT部分的步驟簡化後比較容易理解。如下:找到GDT-->找到"/"的inode-->找到/的數據塊讀取var的inode-->找到var的數據塊讀取log的inode-->找到log的數據塊讀取messages的inode-->找到messages的數據塊並讀取它們。

4.6.2 刪除、重命名和移動文件

註意這裡是不跨越文件系統的操作行為。

  • 刪除文件分為普通文件和目錄文件,知道了這兩種類型的文件的刪除原理,就知道了其他類型特殊文件的刪除方法。

對於刪除普通文件:找到文件的inode和data block(根據前一個小節中的方法尋找);在imap中將該文件的inode號標記為未使用;將bmap中data block對應的block號標記為未使用;在其所在目錄的data block中將該文件名所在的記錄行刪除,刪除了記錄就丟失了指向Inode的指針。

對於刪除目錄文件:找到目錄和目錄下所有文件、子目錄、子文件的inode和data block;在imap中將這些inode號標記為未使用;將bmap中將這些文件占用的 block號標記為未使用;在該目錄的父目錄的data block中將該目錄名所在的記錄行刪除。需要註意的是,刪除父目錄data block中的記錄是最後一步,如果該步驟提前,將報目錄非空的錯誤,因為在該目錄中還有文件占用。

  • 重命名文件分為同目錄內重命名和非同目錄內重命名。非同目錄內重命名實際上是移動文件的過程,見下文。

同目錄內重命名文件的動作僅僅只是修改所在目錄data block中該文件記錄的文件名部分,不是刪除再重建的過程。

如果重命名時有文件名衝突(該目錄內已經存在該文件名),則提示是否覆蓋。覆蓋的過程是覆蓋目錄data block中衝突文件的記錄。例如/tmp/下有a.txt和a.log,若將a.txt重命名為a.log,則提示覆蓋,若選擇覆蓋,則/tmp的data block中關於a.log的記錄被覆蓋,此時它的指針是指向a.txt的inode。

  • 移動文件

同文件系統下移動文件實際上是修改目標文件所在目錄的data block,向其中添加一行指向inode table中待移動文件的inode指針,如果目標路徑下有同名文件,則會提示是否覆蓋,實際上是覆蓋目錄data block中衝突文件的記錄,由於同名文件的inode記錄指針被覆蓋,所以無法再找到該文件的data block,也就是說該文件被標記為刪除(如果多個硬鏈接數,則另當別論)。

所以在同文件系統內移動文件相當快,僅僅在所在目錄data block中添加或覆蓋了一條記錄而已。也因此,移動文件時,文件的inode號是不會改變的。

對於不同文件系統內的移動,相當於先複製再刪除的動作。見後文。

4.6.1 存儲和複製文件

  • 對於文件存儲

(1).讀取GDT,找到各個(或部分)塊組imap中未使用的inode號,併為待存儲文件分配inode號;

(2).在inode table中完善該inode號所在行的記錄;

(3).在目錄的data block中添加一條該文件的相關記錄;

(4).將數據填充到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中進行了優化。

(5)填充完之後,去inode table中更新該

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • sed 用法 1,替換符號: 2,使用sed文件輸入命令: 3,列印: 4,替換第n次匹配: 註意:在每一行內第二次匹配到in,才會替換成AAAAA 5,替換第n行的第m次匹配: 註意:可以使用-n -p 組合 註意:可以使用$表示到結尾 6,將替換結果保存到文件中: 7,將替換結果備份,並且修改源 ...
  • 1、修改遠程linux機器的配置 [root@zender ~]#vim /etc/sysconfig/i18n 把LANG改成支持UTF-8的字元集 如: LANG="zh_CN.UTF-8″ 或者是 LANG="en_US.UTF-8″ 本文修改為後者 2、修改Secure CRT的Sessio ...
  • 執行qmake時報錯,如下圖所示: 解決方法: 將***.pro文件夾的屬主改為當前用戶,具體操作為: 1、切換登錄用戶為:root 2、#chown -R ies:ies /usr/appsoft 請將ies:ies改為你的用戶,/usr/appsoft改為你的文件夾。 經過以上修改qmake執行 ...
  • 信號 fly@UBT:~/$ kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR111) SIGSEGV 12) SIGUSR ...
  • UBUNTU /boot/grub/grub.conf文件 ## DO NOT EDIT THIS FILE## It is automatically generated by grub-mkconfig using templates# from /etc/grub.d and settings ...
  • 1,linux安裝網路自動配置: 2,linux硬碟分配 1,/boot 用來存放與 Linux 系統啟動有關的程式,比如啟動引導裝載程式等,建議大小為 100-200MB 。 2,swap 實現虛擬記憶體,建議大小是物理記憶體的 1~2 倍。 3,/ 使用全部的磁碟空間 4,linux最小化安裝 英文 ...
  • 在centos上使用別名和是用密鑰登錄: vim /root/.ssh/config #輸入下列內容 Host * User root #以root登錄 ServerAliveInterval 60 #存活時間 #test1 Host q0 #登錄的別名 HostName 10.10.10.1 #i ...
  • 首先需要在centos系統下安裝好mysql,這個我已經安裝好了,這裡就不把過程貼出來了。 第一步:使用root用戶登錄到mysql資料庫: 第二步:創建一個mysql資料庫。當第一步登錄成功後,便會出現mysql命令:mysql> 第三步:在已經創建好的資料庫裡面創建一個簡單的數據表 第四步:為數 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...