目錄
前言
文件系統結構
新建文件和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支持哪些文件系統呢? 來到源碼的fs文件夾, 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到上級目錄. 看圖就秒懂了.
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是核心點. 當然還有兩篇甚至更多的后續文章, 最后會寫個簡單的用戶態文件系統, 喜歡記得點個贊或者關注我哦~