核心必須懂(二): 檔案系統初探

Sorrower發表於2018-10-01

目錄

  • 前言
  • 檔案系統結構
  • 新建檔案和inode
  • 檔案建立過程
  • inode解析
  • 開啟檔案
  • 參考
  • 最後

前言

這次來說檔案系統. 檔案系統是非常重要的, 提高磁碟使用率, 減小磁碟磨損等等都是檔案系統要解決的問題. 市面上的檔案系統也是數不勝數, 比較常用的像ext4, xfs以及ntfs等等, 國內的像鵝廠的tfs, 然後還有sun號稱"last word in file system"的ZFS, 學習ZFS而來的btrfs.

下面上一張Linux檔案系統元件的體系結構圖, 是我整合了多方文獻並結合自己的經驗畫出來的. 可以看出, 最重要的就是vfs, 正是因為它, 才讓Linux可以同時支援多種的檔案系統. 舉個例子, 比如你裝了雙系統mint+windows, 在mint中, 你可以看到windows的ntfs磁碟, 但是返回了windows, 你就看不到mint的磁碟了.

Linux檔案系統元件的體系結構圖

那Linux支援哪些檔案系統呢? 來到原始碼的fs資料夾, Linux支援的檔案系統可多了去了, 注意看藍色的.

Linux支援檔案系統


檔案系統結構

磁碟扇區什麼的就不多說了. 也許會出一篇談儲存介質的文章, 說說ssd結構啥的. 直接跳過硬體從檔案系統結構開始. 注意, 我說的是通用模型, 每個fs的具體實現有差異, 而且差異蠻大的. ext家族是Linux預設的fs了, 事實上ext2/ext3和ext4差異也很大.

  • superblock: 記錄此fs的整體資訊, 包括inode/block的總量、使用量、剩餘量, 以及檔案系統的格式與相關資訊等;
  • inode table: superblock之後就是inode table, 儲存了全部inode.
  • data block: inode table之後就是data block. 檔案的內容儲存在這個區域. 磁碟上所有塊的大小都一樣.
  • inode: 記錄檔案的屬性, 同時記錄此檔案的資料所在的block號碼. 每個inode對應一個檔案/目錄的結構, 這個結構它包含了一個檔案的長度、建立及修改時間、許可權、所屬關係、磁碟中的位置等資訊.
  • block: 實際記錄檔案的內容. 一個較大的檔案很容易分佈上千個獨產的磁碟塊中, 而且, 一般都不連續, 太散會導致讀寫效能急劇下降.

好, 我猜你和我一樣是右腦思維, 上圖就好:

檔案系統結構

可以看出來, 這是多層索引結構的檔案系統. 用b+樹是最佳解決方案, 比如btrfs. inode table指向inode, inode指向一個或者多個block, 注意, 圖中還是直接指向, 後面還會講述多層指向. 最怕的就是inode指向的block太散. 一個比較好的解決辦法就是在檔案末尾不斷新增資料, 而不是新建檔案.


新建檔案和inode

新建一個檔案和資料夾, 用stat指令檢視檔案資訊.

touch hello
stat hello
mkdir hellodir
stat hellodir
複製程式碼

新建檔案

可以看到一些資訊. 例如一個目錄初始大小就是4KB, 8個block, 一個扇區就是512B, 一個io block是4KB, 對應第一幅圖的General Block Device Layer層. 這些其實不看也知道, 前提是這是常規的fs.


檔案建立過程

建立成功一個檔案有4步:

  • 儲存屬性: 也就是檔案屬性的儲存, 核心先找到一塊空的inode. 例如, 之前的1049143. 核心把檔案的資訊記錄其中. 如檔案的大小、檔案所有者、和建立時間等, 用stat指令都可以看到.
  • 儲存資料: 即檔案內容的儲存, 比方建立一個1B的檔案, 那一個block, 8個扇區, 核心把資料放到一個空閒邏輯塊中也就是空閒block中. 很明顯, 碎片化的問題已經呈現在這裡了. 1B它也要用4K對吧.
  • 記錄分配情況: 假如資料儲存到了3個block中, 位置要記錄到inode的磁碟序號列表中. 這3個編號分別放在最開始的3個位置. 然後讀的時候會一次性讀, 可以看我的第二張圖. 當然了fat就沒有inode, 它在一個塊中放了下一個塊的位置, 形成鏈, u盤就是這種fs.
  • 新增檔名到目錄: 檔名和inode之間的對應關係將檔名和檔案以及檔案的內容屬性連線起來, 找到檔名就找到檔案的inode,通過inode就能找到檔案的屬性和內容. 換句話說, 就是機器看的和人看的做銜接, 例如網址和ip. 當然, 如果你看inode就能區分檔案, 第四步可以不要(手動滑稽).

目錄的話, 就是多了.檔案(指向自己), ..檔案(指向上級目錄). 然後新增自己的inode到上級目錄. 看圖就秒懂了.

stat

檔案建立過程

inode解析

用df指令可以看inode的總數和使用量.

df -i
複製程式碼

dumpe2fs開啟指定磁碟可以看inode的大小, 這裡是256.

指令

inode如何記錄檔案並且最大是多少呢? inode記錄block號碼的區域定義為12個直接, 一個間接, 一個雙間接與一個三間接記錄區. 一個inode是4B, 這樣用4K的block可以有1K的inode.

  • 直接: 12 * 4K
  • 間接: (4K / 4) * 4K
  • 雙間接: (4K / 4) * (4K / 4) * 4K
  • 三間接: (4K / 4) * (4K / 4) * (4K / 4) * 4K

所以的話, 4T, are you OK? 算歸算, fs在不斷髮展, 這是過時的大小了. ext4的話單個檔案可以到達16TB, fs可達1EB. 但是注意, ext4的作者都說了, ext4只是過渡, btrfs會更棒, 那事實上, cent os用的xfs也很很棒.


開啟檔案

建立之後當然要開啟了, 開啟檔案也是有一系列過程的. 先來看看兩個指令:

sysctl -a | grep fs.file-max
複製程式碼
ulimit -n
複製程式碼

開啟檔案最大數

  • 第一個指令檢視os最大開啟數, 這是系統級限制.
  • 第二個指令檢視單程式最大開啟數, 這是使用者級限制.
  • 程式描述符(task_struct):
  • 為了管理程式, 作業系統要對每個程式所做的事情進行清楚地描述, 為此, 作業系統使用資料結構來代表處理不同的實體, 這個資料結構就是通常所說的程式描述符程式控制塊(PCB). 通俗來講就是作業系統中描述程式的結構體叫做PCB.
  • Linux核心通過一個被稱為程式描述符task_struct結構體來管理程式, 這個結構體包含了一個程式所需的所有資訊. 它定義在include/linux/sched.h檔案中. 這不是這次的重點, 但是這個task_struct結構體確實很重要, 也很複雜.
  • 每個程式都會被分配一個task_struct結構, 它包含了這個程式的所有資訊, 在任何時候作業系統都能跟蹤這個結構的資訊.
  • 檔案描述符表(file_struct): 該表記錄程式開啟的檔案. 它的表項裡面有一個指標, 指向存放在核心空間的檔案表中的一個表項. 它向使用者提供一個簡單的檔案描述符(fd), 使得使用者可以通過方便地訪問一個檔案. 例如, 當程式使用open開啟一個檔案時, 核心就會在這個表中新增一個表項. 如果對同一個檔案開啟多次, 那麼將有多個表項. 使用dup時, 也會增加一個表項.

  • 檔案表: 檔案表儲存了程式對檔案讀寫的偏移量. 該表還儲存了程式對檔案的存取許可權等等. 比如, 程式以O_RDONLY方式開啟檔案, 這將記錄到對應的檔案表表項中. 然後每個表有一個指向inode table中inode的指標. 結合之前的圖片看, 所有結構就聯絡起來了, 所以inode是核心點.

上圖上圖:

開啟檔案

  • 在程式A中, 檔案描述符1和2都指向了同一個開啟的檔案表A. 這可能是通過呼叫dup()、dup2()、fcntl()或者對同一個檔案多次呼叫了open()函式而形成的.
  • 程式A的檔案描述符0和程式B的檔案描述符2都指向了同一個開啟的檔案表A. 這種情形可能是在呼叫fork()後出現的(即, 程式A、B是父子程式關係), 或者當某程式通過UNIX域套接字將一個開啟的檔案描述符傳遞給另一個程式時, 也會發生. 再者是不同的程式獨自去呼叫open函式開啟了同一個檔案, 此時程式內部的描述符正好分配到與其他程式開啟該檔案的描述符一樣.
  • 此外, 程式A的描述符0和程式B的描述符255分別指向不同的開啟檔案表, 但這些檔案表均指向inode table的相同條目(假設), 也就是指向同一個檔案. 發生這種情況是因為每個程式各自對同一個檔案發起了open()呼叫。同一個程式兩次開啟同一個檔案, 也會發生類似情況.

為什麼要說這些情況呢? 因為如果沒有理解清楚這些, 在做多程式多執行緒read和write的時候很有可能會導致讀取和寫入混亂.


參考

看了非常多很棒的文章, 這裡也分享給大家.


最後

這次從結構上逐步往內解剖檔案系統, inode是核心點. 當然還有兩篇甚至更多的後續文章, 最後會寫個簡單的使用者態檔案系統, 喜歡記得點個贊或者關注我哦~


相關文章