個人學習-Linux文件系統架構 1. 參考文章 [1]https://blog.csdn.net/Holy_666/article/details/86532671 [2]CSDN博主土豆西瓜大芝麻:[Linux的VFS詳解]:https://blog.csdn.net/jinking01/art ...
個人學習-Linux文件系統架構
1. 參考文章
[1]https://blog.csdn.net/Holy_666/article/details/86532671
[2]CSDN博主土豆西瓜大芝麻:[Linux的VFS詳解]:https://blog.csdn.net/jinking01/article/details/90669534
[3]深入理解 Linux的 I/O 系統:https://z.itpub.net/article/detail/9595A9A27188FF73810F07F00DAA08ED
[4]Linux嵌入式的知乎專欄:https://zhuanlan.zhihu.com/p/505338841
[5]博客園博主[李大嘴]:[字元設備和塊設備的區別]https://www.cnblogs.com/qlee/archive/2011/07/27/2118406.html
[6]博客園博主[賽艇隊長]:[Linux文件系統詳述]https://www.cnblogs.com/bellkosmos/p/detail_of_linux_file_system.html
[7]StackExchange:[What are directories, if everything on Linux is a file?]
[8] [The Linux Documentation Project: Filesystem]http://www.tldp.org/LDP/tlk/fs/filesystem.html#tth_sEc9.1.4)
2. 概述:
本文主要從四個角度對Linux文件系統進行總結整理;
-
Linux文件系統的組織方式;
-
Linux的VFS機制和統一文件模型(common file model);
-
Linux系統IO緩衝機制;
3. Linux文件組織方式:
3.1 基礎知識:
老調重彈:Linux設計的思路,就是一切皆文件,因此Linux中的一切文件都以文件格式保存,而文件根據各自不同的功能分為一下幾類:
-
普通文件:
-,常規的文件類型,在Linux中,以.開頭的文件為隱藏文件;
-
目錄文件;
d, 目錄文件,目錄文件實際包含兩部分Inode和entry(實際所有文件都是這樣,不同的是,目錄中保存了相關文件的信息);
Linux通過Inode結構體存儲目錄的基礎信息,系統調用stat(),用來訪問這個結構和相應信息。
(圖1 源自引用文獻[7])
-
字元設備文件;
c, character device,字元設備是以字元為最小單位進行數據交互的文件,用以驅動字元交互的應用,如鍵盤(有文章提到oracle是以字元為格式進行數據傳輸的,記個ToDo);
而對於字元設備而言,僅支持順序訪問數據,不支持隨機訪問。
-
塊設備文件;
B,block device driver
塊設備傳輸,實際是以固定大小進行數據傳輸的文件類型,和字元設備不同的是,block支持我們對設備隨機訪問。最典型的例子即硬碟,操作系統/資料庫/其他和磁碟交互應用,實際可以根據自己的規則,去隨機訪問磁碟的位置,去在該位置寫入數據。而通過塊設備進行讀寫時,也需要以bolck為最小單位進行操作(實際上,無論是OS還是DBS,都是以Page去容納多個Block,然後通過Page進行數據載入,然後以block進行數據解析的)
-
符號鏈接;(軟鏈接,硬鏈接實際是生成了一個相應文件,該文件名和原始文件名指向同一個inode):
符號鏈接可以理解成一種快捷方式,允許我們通過符號鏈接快速訪問目標文件。
-
套接字;
S,socket,用於網路通信的文件,通過套接字API形成的一個簡單協議族,成對出現進行通信。
-
管道;
P,進行進程間通信的文件,將一個進程的輸出,通過管道,輸入到另一個文件。
3.2 文件系統的基本組成
(Note: 本部分僅介紹基礎部分,磁碟分區,初始化,數據在磁碟上的組織後續再進行補充)
Linux操作系統支持很多不同的文件系統,ext2,ext3,XFS,FAT等等,而Linux把對不同文件系統的訪問,交給VFS(virtual file system)來進行。
Linux的文件系統會為每個文件分配兩個數據結構:
索引節點(index node) 和 目錄項(directory entry),用來記錄文件的元信息(meta data)如inode編號,文件大小,訪問許可權,修改時間,磁碟位置等。索引節點和磁碟上的每個物理文件相對應,而索引節點本身,也存儲在磁碟上。
目錄項(directory entry)用來記錄文件的名字,索引節點指針,和其他目錄項的層次關係。多個目錄項關聯起來,就形成了目錄結構,和索引節點不同,目錄項是由內核維護的數據結構,緩存在記憶體中;(note:實際上系統啟動時,會將相應的數據,載入到樹形結構中,維護在記憶體中)
目錄項的結構體,dentry
struct dentry {
atomic_t d_count; /* 目錄項引用計數器 */
unsigned int d_flags; /* 目錄項標誌 */
struct inode * d_inode; /* 與文件名關聯的索引節點 */
struct dentry * d_parent; /* 父目錄的目錄項 */
struct list_head d_hash; /* 目錄項形成的哈希表 */
struct list_head d_lru; /*未使用的 LRU 鏈表 */
struct list_head d_child; /*父目錄的子目錄項所形成的鏈表 */
struct list_head d_subdirs; /* 該目錄項的子目錄所形成的鏈表*/
struct list_head d_alias; /* 索引節點別名的鏈表*/
int d_mounted; /* 目錄項的安裝點 */
struct qstr d_name; /* 目錄項名(可快速查找) */
unsigned long d_time; /* 由 d_revalidate函數使用 */
struct dentry_operations *d_op; /* 目錄項的函數集*/
struct super_block * d_sb; /* 目錄項樹的根 (即文件的超級塊)*/
unsigned long d_vfs_flags;
void * d_fsdata; /* 具體文件系統的數據 */
unsigned char d_iname[DNAME_INLINE_LEN]; /* 短文件名 */
};
通過stat訪問得到的Inode信息
struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is NOT defined */
dev_t st_dev; /* device inode resides on */
ino_t st_ino; /* inode's number */
mode_t st_mode; /* inode protection mode */
nlink_t st_nlink; /* number of hard links to the file */
uid_t st_uid; /* user-id of owner */
gid_t st_gid; /* group-id of owner */
dev_t st_rdev; /* device type, for special file inode */
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last file status change */
off_t st_size; /* file size, in bytes */
quad_t st_blocks; /* blocks allocated for file */
u_long st_blksize;/* optimal file sys I/O ops blocksize */
u_long st_flags; /* user defined flags for file */
u_long st_gen; /* file generation number */
};
而索引節點,目錄項,以及文件數據間的關係可以用如下結構來表示
(圖2 源自引用4)
4. 虛擬文件系統(Virtual File Switch)
當一個用戶應用,通過系統調用函數進行數據讀寫時會發生什麼?
fopen("~");
fwrite("~");
fclose("~");
在我們的直覺上,會認為用戶空間的代碼,通過調用庫函數喚起系統調用,然後交於OS File System直接寫入磁碟(在考慮buffer,page,block,機制後)。但是實際上Linux實際上是將系統調用交付於VFS,即虛擬文件系統,然後由VFS執行後續操作的。
(圖3 源自引用2)
這樣做有什麼價值呢?如上文所述,Linux存在不同的file system operator,這些不同的文件系統暴漏給上層的介面可能是不同的,如果將這些介面都提供給OS SCI(stsrem call interface),系統調用會過於複雜。而VFS在文件系統和系統調用間提供了一個抽象層,讓系統調用的POSIX API和不同存儲設備的具體介面實現了分離,實現了一次解耦。
(圖4 源自引用2)
4.1 統一文件模型(common file model)
由於VFS將不同的底層介面抽象了統一的標準給系統調用,系統調用層便可以用上文提到的Inode,entry模型將所有文件的模型進行統一。實際上正是VFS層提供了統一的文件模型。
VFS通過四種標準模型來構建統一文件模型:
(1)superblock: 存儲文件系統的基本元數據(可以理解成 meta of meta,這詞兒沒查過,是我現編的)。如文件系統的類型,大小,狀態,一起其他元數據的相關信息。
(2)index node(inode):一個用來保存文件相關的元數據。
(3)directory entry: 保存文件名稱和inode的對應關係;
每個dentry存在三種狀態:
- Used: 和一個inode關聯,正被使用,不能被損壞和丟棄;
- Unused: 和inode關聯,處於被緩存狀態,沒有被vfs使用;
- negative: 沒有和具體的inode關聯(實際相當於一個無效路徑);
由於dentry實際是載入在記憶體里的,系統會對dentry存在優化策略:
1.used dentrie list:把使用的dentry串成一個鏈表;
2.LRU鏈表:(least recently used)鏈表,實際就是最常見的頁面置換演算法,找出最久沒有使用的entry,把它從記憶體中釋放。
3.hash table:用哈希表,來維持dentry的高速查詢;
(4) file: 一組邏輯上相關聯的數據,實際就是我們通過open函數返回的數據類型,在我們使用open打開函數後,就從磁碟中載入了對於的數據至記憶體,VFS將數據保存至File模型中,和進程,用戶強關聯。其中包含了打開的flag,文件名稱,當前的便宜。最重要的欄位就是f_op,指向了當前文件所支持的操作集合;
struct file {
struct dentry *f_dentry;
struct vfsmount *f_vfsmnt;
struct file_operations *f_op;
mode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
unsigned int f_uid, f_gid;
unsigned long f_version;
...
}
5. 系統IO
本部分主要討論系統IO和緩衝區之間的交互;
傳統的(無緩存)文件讀寫方式
![截屏2022-09-15 22.12.33](/Users/alberthaoluchen/Library/Application Support/typora-user-images/截屏2022-09-15 22.12.33.png)
1.用戶進程通過read()向kernel進行系統調用,切換上下文,到內核空間。
2.CPU將數據從硬碟or主存拷貝至kernel到讀緩衝區;
3.CPU將讀緩衝區的數據拷貝回用戶緩衝區;
4.上下文從kernelspace 切換回UserSpace,read調用執行返回;
用戶態 <---> 內核態切換兩次;
拷貝操作,兩次;
用戶態的切換和系統拷貝,被切開,相較於連續的操作,這種間斷式的操作更耗時;
高性能優化的I/O
PageCache
頁緩存技術,通過每次從磁碟讀取一個Page單位的數據至緩存,來減少對磁碟的讀寫,來提高系統效率;
當我們進行順序讀寫時,頁緩存能極大提高我們的讀寫速度(實際就是直接在memory上讀寫);
頁緩存的優化策略:
讀策略:
- 當我們執行read操作時,判斷數據在PageCache上嗎?如果在,我們就不對磁碟進行讀操作;
- 如果不在,調度I/O去讀磁碟數據,除了目標文件所在的Page外,還會讀多個連續頁到頁緩存中;
寫策略:
當我們執行寫操作時,先寫進頁緩存,此時我們把目標頁標記為臟頁(dirty page),並把這個頁加入臟頁鏈表;
flusher會周期性的回寫臟頁到磁碟,當磁碟數據和記憶體一致時,清楚臟頁標記,
當滿足以下條件時,臟頁會被寫入記憶體:
- 空閑記憶體低於一個特定閾值;
- 臟頁在記憶體駐留時間超過閾值時(LRU)
- 用戶進程調用sync()和fsync()時;