1. 程式人生 > >Linux 虛擬檔案系統四大物件:超級塊、inode、dentry、file之間關係

Linux 虛擬檔案系統四大物件:超級塊、inode、dentry、file之間關係

更多嵌入式原創文章,請關注公眾號:一口Linux # 一:檔案系統 ## 1. 什麼是檔案系統? 作業系統中負責管理和儲存檔案資訊的軟體機構稱為檔案管理系統,簡稱檔案系統。 通常檔案系統是用於儲存和組織檔案的一種機制,便於對檔案進行方便的查詢與訪問。 檔案系統是對檔案儲存裝置的空間進行組織和分配,負責檔案儲存並對存入的檔案進行保護和檢索的系統。 它負責為使用者建立檔案,存入、讀出、修改、轉儲檔案,控制檔案的存取,當用戶不再使用時撤銷檔案等。 隨著檔案種類的增多,擴增了更多的檔案系統,為了對各種檔案系統進行統一的管理與組織。 ## 2. Linux檔案系統 Linux將檔案系統分為了兩層:VFS(虛擬檔案系統)、具體檔案系統,如下圖所示: ![VFS](https://img-blog.csdnimg.cn/20210117204418327.png) VFS(Virtual Filesystem Switch)稱為虛擬檔案系統或虛擬檔案系統轉換,是一個核心軟體層,在具體的檔案系統之上抽象的一層,用來處理與Posix檔案系統相關的所有呼叫,表現為能夠給各種檔案系統提供一個通用的介面,使上層的應用程式能夠使用通用的介面訪問不同檔案系統,同時也為不同檔案系統的通訊提供了媒介。 VFS並不是一種實際的檔案系統,它只存在於記憶體中,不存在任何外存空間,VFS在系統啟動時建立,在系統關閉時消亡。 VFS由**超級塊、inode、dentry、vfsmount**等結構來組成。 Linux系統中存在很多的檔案系統,例如常見的**ext2,ext3,ext4,sysfs,rootfs,proc...**等等。 # 二 、VFS ## 1. VFS在linux架構中的位置 從使用者的使用角度,Linux下的檔案系統中巨集觀上主要分為三層: - 1.上層的檔案系統的系統呼叫(System-call ); - 2.虛擬檔案系統VFS(Virtual File System)層, - 3.掛載到VFS中的各種實際檔案系統。 VFS在整個Linux系統中的架構檢視如下: ![VFS](https://img-blog.csdnimg.cn/20210117205911807.png) Linux系統的User使用GLIBC(POSIX標準、GUN C執行時庫)作為應用程式的執行時庫,然後通過作業系統,將其轉換為系統呼叫SCI(system-call interface),SCI是作業系統核心定義的系統呼叫介面,這層抽象允許使用者程式的I/O操作轉換為核心的介面呼叫。 ## 2. 使用者如何透明的去處理檔案? 我們知道每個檔案系統是獨立的,有自己的組織方法,操作方法。那麼對於使用者來說,不可能所有的檔案系統都瞭解,那麼怎麼做到讓使用者透明的去處理檔案呢? 例如:我想寫檔案,那就直接read就OK,不管你是什麼檔案系統,具體怎麼去讀!這裡就需要引入虛擬檔案系統。 所以虛擬檔案系統就是:對於一個system,可以存在多個“實際的檔案系統”,例如:ext2,ext3,fat32,ntfs...例如我現在有多個分割槽,對於每一個分割槽我們知道可以是不同的“實際檔案系統”。 例如現在三個磁碟分割槽分別是:ext2,ext3,fat32,那麼每個“實際的檔案系統”的操作和資料結構肯定不一樣,那麼,使用者怎麼能透明使用它們呢? 這個時候就需要VFS作為中間一層!使用者直接和VFS打交道。 VFS是一種軟體機制,只存在於記憶體中,每次系統初始化期間Linux都會先在記憶體中構造一棵VFS的目錄樹(也就是原始碼中的namespace)。 VFS主要的作用是對上層應用遮蔽底層不同的呼叫方法,提供一套統一的呼叫介面,二是便於對不同的檔案系統進行組織管理。 VFS提供了一個抽象層,將POSIX API介面與不同儲存裝置的具體介面實現進行了分離,使得底層的檔案系統型別、裝置型別對上層應用程式透明。 例如read,write,那麼對映到VFS中就是sys_read,sys_write,那麼VFS可以根據你操作的是哪個“實際檔案系統”(哪個分割槽)來進行不同的實際的操作!這個技術也是很熟悉的“鉤子結構”技術來處理的。 其實就是VFS中提供一個抽象的struct結構體,然後對於每一個具體的檔案系統要把自己的欄位和函式填充進去,這樣就解決了異構問題(核心很多子系統都大量使用了這種機制)。 # 三、Linux虛擬檔案系統四大物件 為了對檔案系統進行統一的管理與組織,Linux建立了一個公共根目錄和全域性檔案系統樹。要訪問一個檔案系統中的檔案,必須先將這個檔案系統掛載在全域性檔案系統樹的某個根目錄下,這一掛載過程被稱作檔案系統的掛載,所掛載的目錄稱為掛載點。 傳統的檔案系統在磁碟上的佈局如下: ![ ](https://img-blog.csdnimg.cn/20210117142325788.png) 由上圖可知,檔案系統的開頭通常是由一個磁碟扇區所組成的引導塊,該部分的主要目的是用於對作業系統的引導。一般只在啟動作業系統時使用。 隨後是超級塊,超級塊主要存放了該物理磁碟中檔案系統結構的相關資訊,並且對各個部分的大小進行說明。 最後由i節點點陣圖,邏輯塊點陣圖、i節點、邏輯塊這幾部分分佈在物理磁碟上。 Linux為了對超級塊,i節點,邏輯塊這三部分進行高效的管理,Linux建立了幾種不同的資料結構,分別是**檔案系統型別、inode、dentry**等幾種。 其中,檔案系統型別規定了某種檔案系統的行為,利用該資料結構可以構造某種檔案系統型別的例項,另外,該例項也被稱為超級塊例項。 超級塊則是反映了檔案系統整體的控制資訊。超級塊能夠以多種的方式存在,對於基於磁碟的檔案系統,它以特定的格式存在於磁碟的固定區域(取決於檔案系統型別)上。在掛載檔案系統時,該超級塊中的內容被讀入磁碟中,從而構建出位於記憶體中的新的超級塊。 inode則反映了檔案系統物件中的一般元資料資訊。dentry則是反映出某個檔案系統物件在全域性檔案系統樹中的位置。 Linux對這四種資料結構進行了相關的關聯。 如下圖: ![結構體關係](https://img-blog.csdnimg.cn/20210117212509549.png) ## 1. 超級塊(super block) 超級塊:一個超級塊對應一個檔案系統(已經安裝的檔案系統型別如ext2,此處是實際的檔案系統,不是VFS)。 之前我們已經說了檔案系統用於管理這些檔案的資料格式和操作之類的,系統檔案有系統檔案自己的檔案系統,同時對於不同的磁碟分割槽也有可以是不同的檔案系統。那麼一個超級塊對於一個獨立的檔案系統。儲存檔案系統的型別、大小、狀態等等。 (“檔案系統”和“檔案系統型別”不一樣!一個檔案系統型別下可以包括很多檔案系統即很多的super_block) 既然我們知道對於不同的檔案系統有不同的super_block,那麼對於不同的super_block的操作肯定也是不同的,所以我們在下面的super_block結構中可以看到上面說的抽象的struct結構(例如下面的:struct super_operations): (linux核心3.14) ```c 1246 struct super_block { 1247 struct list_head s_list; /* Keep this first */ 1248 dev_t s_dev; /* search index; _not_ kdev_t */ 1249 unsigned char s_blocksize_bits; 1250 unsigned long s_blocksize; 1251 loff_t s_maxbytes; /* Max file size */ 1252 struct file_system_type *s_type; 1253 const struct super_operations *s_op; 1254 const struct dquot_operations *dq_op; 1255 const struct quotactl_ops *s_qcop; 1256 const struct export_operations *s_export_op; 1257 unsigned long s_flags; 1258 unsigned long s_magic; 1259 struct dentry *s_root; 1260 struct rw_semaphore s_umount; 1261 int s_count; 1262 atomic_t s_active; 1263 #ifdef CONFIG_SECURITY 1264 void *s_security; 1265 #endif 1266 const struct xattr_handler **s_xattr; 1267 1268 struct list_head s_inodes; /* all inodes */ 1269 struct hlist_bl_head s_anon; /* anonymous dentries for (nfs) exporting */ 1270 struct list_head s_mounts; /* list of mounts; _not_ for fs use */ 1271 struct block_device *s_bdev; 1272 struct backing_dev_info *s_bdi; 1273 struct mtd_info *s_mtd; 1274 struct hlist_node s_instances; 1275 struct quota_info s_dquot; /* Diskquota specific options */ 1276 1277 struct sb_writers s_writers; 1278 1279 char s_id[32]; /* Informational name */ 1280 u8 s_uuid[16]; /* UUID */ 1281 1282 void *s_fs_info; /* Filesystem private info */ 1283 unsigned int s_max_links; 1284 fmode_t s_mode; 1285 1286 /* Granularity of c/m/atime in ns. 1287 Cannot be worse than a second */ 1288 u32 s_time_gran; 1289 1290 /* 1291 * The next field is for VFS *only*. No filesystems have any business 1292 * even looking at it. You had been warned. 1293 */ 1294 struct mutex s_vfs_rename_mutex; /* Kludge */ 1295 1296 /* 1297 * Filesystem subtype. If non-empty the filesystem type field 1298 * in /proc/mounts will be "type.subtype" 1299 */ 1300 char *s_subtype; 1301 1302 /* 1303 * Saved mount options for lazy filesystems using 1304 * generic_show_options() 1305 */ 1306 char __rcu *s_options; 1307 const struct dentry_operations *s_d_op; /* default d_op for dentries */ 1308 1309 /* 1310 * Saved pool identifier for cleancache (-1 means none) 1311 */ 1312 int cleancache_poolid; 1313 1314 struct shrinker s_shrink; /* per-sb shrinker handle */ 1315 1316 /* Number of inodes with nlink == 0 but still referenced */ 1317 atomic_long_t s_remove_count; 1318 1319 /* Being remounted read-only */ 1320 int s_readonly_remount; 1321 1322 /* AIO completions deferred from interrupt context */ 1323 struct workqueue_struct *s_dio_done_wq; 1324 1325 /* 1326 * Keep the lru lists last in the structure so they always sit on their 1327 * own individual cachelines. 1328 */ 1329 struct list_lru s_dentry_lru ____cacheline_aligned_in_smp; 1330 struct list_lru s_inode_lru ____cacheline_aligned_in_smp; 1331 struct rcu_head rcu; 1332 }; ``` 解釋欄位: |**欄位**|**描述** | |--|:--| **s_list**|指向超級塊連結串列的指標,這個struct list_head是很熟悉的結構了,裡面其實就是用於連線關係的prev和next欄位。核心單獨使用一個簡單的結構體將所有的super_block都連結起來。 **s_dev**|包含該具體檔案系統的塊裝置識別符號。例如,對於 /dev/hda1,其裝置識別符號為 0x301 **s_blocksize_bits**|上面的size大小佔用位數,例如512位元組就是9 bits **s_blocksize**|檔案系統中資料塊大小,以位元組單位 **s_maxbytes**|允許的最大的檔案大小(位元組數) **struct file_system_type *s_type**|檔案系統型別(也就是當前這個檔案系統屬於哪個型別?ext2還是fat32),要區分“檔案系統”和“檔案系統型別”不一樣!一個檔案系統型別下可以包括很多檔案系統即很多的super_block,後面會說! **struct super_operations *s_op**|指向某個特定的具體檔案系統的用於超級塊操作的函式集合 **struct dquot_operations *dq_op**|指向某個特定的具體檔案系統用於限額操作的函式集合 **struct quotactl_ops *s_qcop**|用於配置磁碟限額的的方法,處理來自使用者空間的請求 **s_flags**|安裝標識 **s_magic**|區別於其他檔案系統的標識 **s_root**|指向該具體檔案系統安裝目錄的目錄項 **s_umount**|對超級塊讀寫時進行同步 **s_count**|對超級塊的使用計數 **s_active**|引用計數 超級塊方法 ```c struct super_operations { //該函式在給定的超級塊下建立並初始化一個新的索引節點物件 struct inode *(*alloc_inode)(struct super_block *sb); //釋放指定的索引結點 。 void (*destroy_inode)(struct inode *); //VFS在索引節點被修改時會呼叫此函式。 void (*dirty_inode) (struct inode *, int flags); // 將指定的inode寫回磁碟。 int (*write_inode) (struct inode *, struct writeback_control *wbc); //刪除索引節點。 int (*drop_inode) (struct inode *); void (*evict_inode) (struct inode *); //用來釋放超級塊 void (*put_super) (struct super_block *); //使檔案系統的資料元素與磁碟上的檔案系統同步,wait引數指定操作是否同步。 int (*sync_fs)(struct super_block *sb, int wait); int (*freeze_fs) (struct super_block *); int (*unfreeze_fs) (struct super_block *); //獲取檔案系統狀態。把檔案系統相關的統計資訊放在statfs中 int (*statfs) (struct dentry *, struct kstatfs *); int (*remount_fs) (struct super_block *, int *, char *); void (*umount_begin) (struct super_block *); int (*show_options)(struct seq_file *, struct dentry *); int (*show_devname)(struct seq_file *, struct dentry *); int (*show_path)(struct seq_file *, struct dentry *); int (*show_stats)(struct seq_file *, struct dentry *); #ifdef CONFIG_QUOTA ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t); ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t); #endif int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t); long (*nr_cached_objects)(struct super_block *, int); long (*free_cached_objects)(struct super_block *, long, int); }; ``` ## 2. 索引節點(inode) 索引節點inode: 儲存的其實是實際的資料的一些資訊,這些資訊稱為“元資料”(也就是對檔案屬性的描述)。 例如:檔案大小,裝置識別符號,使用者識別符號,使用者組識別符號,檔案模式,擴充套件屬性,檔案讀取或修改的時間戳,連結數量,指向儲存該內容的磁碟區塊的指標,檔案分類等等。 ( 注意資料分成:元資料+資料本身 ) 同時注意:inode有兩種,一種是VFS的inode,一種是具體檔案系統的inode。前者在記憶體中,後者在磁碟中。所以每次其實是將磁碟中的inode調進填充記憶體中的inode,這樣才是算使用了磁碟檔案inode。 ### inode怎樣生成的? 每個inode節點的大小,一般是128位元組或256位元組。inode節點的總數,在格式化時就給定(現代OS可以動態變化),一般每2KB就設定一個inode。 一般檔案系統中很少有檔案小於2KB的,所以預定按照2KB分,一般inode是用不完的。所以inode在檔案系統安裝的時候會有一個預設數量,後期會根據實際的需要發生變化。 注意inode號:inode號是唯一的,表示不同的檔案。其實在Linux內部的時候,訪問檔案都是通過inode號來進行的,所謂檔名僅僅是給使用者容易使用的。 當我們開啟一個檔案的時候,首先,系統找到這個檔名對應的inode號;然後,通過inode號,得到inode資訊,最後,由inode找到檔案資料所在的block,現在可以處理檔案資料了。 ### inode和檔案的關係? 當建立一個檔案的時候,就給檔案分配了一個inode。一個inode只對應一個實際檔案,一個檔案也會只有一個inode。inodes最大數量就是檔案的最大數量。 ```c 527 struct inode { 528 umode_t i_mode; /* 訪問許可權控制 */ 529 unsigned short i_opflags; 530 kuid_t i_uid; /* 使用者的id */ 531 kgid_t i_gid; /* 使用組id */ 532 unsigned int i_flags; /* 檔案系統標誌 */ 533 534 #ifdef CONFIG_FS_POSIX_ACL 535 struct posix_acl *i_acl; 536 struct posix_acl *i_default_acl; 537 #endif 538 539 const struct inode_operations *i_op; /*索引節點操作表*/ 540 struct super_block *i_sb; /* 相關的超級塊 */ 541 struct address_space *i_mapping; /* 相關的地址對映 */ 542 543 #ifdef CONFIG_SECURITY 544 void *i_security; 545 #endif 546 547 /* Stat data, not accessed from path walking */ 548 unsigned long i_ino; /* 索引節點號 */ 549 /* 550 * Filesystems may only read i_nlink directly. They shall use the 551 * following functions for modification: 552 * 553 * (set|clear|inc|drop)_nlink 554 * inode_(inc|dec)_link_count 555 */ 556 union { 557 const unsigned int i_nlink; 558 unsigned int __i_nlink; /* 硬連線數 */ 559 }; 560 dev_t i_rdev; /* 實際裝置識別符號號 */ 561 loff_t i_size; 562 struct timespec i_atime; /* 最後訪問時間 */ 563 struct timespec i_mtime; /* 最後修改時間 */ 564 struct timespec i_ctime; /* 最後改變時間 */ 565 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ 566 unsigned short i_bytes; /* 使用的位元組數 */ 567 unsigned int i_blkbits; 568 blkcnt_t i_blocks; /* 檔案的塊數 */ 569 570 #ifdef __NEED_I_SIZE_ORDERED 571 seqcount_t i_size_seqcount; 572 #endif 573 574 /* Misc */ 575 unsigned long i_state; 576 struct mutex i_mutex; 577 578 unsigned long dirtied_when; /* jiffies of first dirtying 首次修改時間*/ 579 580 struct hlist_node i_hash; /* hash值,提高查詢效率 */ 581 struct list_head i_wb_list; /* backing dev IO list */ 582 struct list_head i_lru; /* inode LRU list 未使用的inode*/ 583 struct list_head i_sb_list; /* 連結一個檔案系統中所有inode的連結串列 */ 584 union { 585 struct hlist_head i_dentry; /* 目錄項鍊表 */ 586 struct rcu_head i_rcu; 587 }; 588 u64 i_version; 589 atomic_t i_count; /* 引用計數 */ 590 atomic_t i_dio_count; 591 atomic_t i_writecount; /* 寫者計數 */ 592 const struct file_operations *i_fop; /* former ->i_op->default_file_ops 檔案操作*/ 593 struct file_lock *i_flock; /* 檔案鎖鏈表 */ 594 struct address_space i_data; /* 表示被inode讀寫的頁面 */ 595 #ifdef CONFIG_QUOTA 596 struct dquot *i_dquot[MAXQUOTAS];/* 節點的磁碟限額 */ 597 #endif 598 struct list_head i_devices; /* 裝置連結串列(共用同一個驅動程式的裝置形成的連結串列。) */ 599 union { 600 struct pipe_inode_info *i_pipe; /* 管道資訊 */ 601 struct block_device *i_bdev; /* 塊裝置驅動節點 */ 602 struct cdev *i_cdev; /* 字元裝置驅動節點 */ 603 }; 604 605 __u32 i_generation; /* 索引節點版本號 */ 606 607 #ifdef CONFIG_FSNOTIFY 608 __u32 i_fsnotify_mask; /* all events this inode cares about */ 609 struct hlist_head i_fsnotify_marks; 610 #endif 611 612 #ifdef CONFIG_IMA 613 atomic_t i_readcount; /* struct files open RO */ 614 #endif 615 void *i_private; /* fs or device private pointer 使用者私有資料*/ 616 }; ``` 注意管理inode的四個連結串列: ```c static struct hlist_head *inode_hashtable __read_mostly; ``` ### 節點方法 ```c struct inode_operations { struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int); void * (*follow_link) (struct dentry *, struct nameidata *); int (*permission) (struct inode *, int); struct posix_acl * (*get_acl)(struct inode *, int); int (*readlink) (struct dentry *, char __user *,int); void (*put_link) (struct dentry *, struct nameidata *, void *); int (*create) (struct inode *,struct dentry *, umode_t, bool); int (*link) (struct dentry *,struct inode *,struct dentry *); int (*unlink) (struct inode *,struct dentry *); int (*symlink) (struct inode *,struct dentry *,const char *); int (*mkdir) (struct inode *,struct dentry *,umode_t); int (*rmdir) (struct inode *,struct dentry *); int (*mknod) (struct inode *,struct dentry *,umode_t,dev_t); int (*rename) (struct inode *, struct dentry *, struct inode *, struct dentry *); int (*rename2) (struct inode *, struct dentry *, struct inode *, struct dentry *, unsigned int); int (*setattr) (struct dentry *, struct iattr *); int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *); int (*setxattr) (struct dentry *, const char *,const void *,size_t,int); ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t); ssize_t (*listxattr) (struct dentry *, char *, size_t); int (*removexattr) (struct dentry *, const char *); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); int (*update_time)(struct inode *, struct timespec *, int); int (*atomic_open)(struct inode *, struct dentry *, struct file *, unsigned open_flag, umode_t create_mode, int *opened); int (*tmpfile) (struct inode *, struct dentry *, umode_t); int (*set_acl)(struct inode *, struct posix_acl *, int); } ____cacheline_aligned; ``` 對其中一些重要的結果進行分析: |方法| 含義 | |--|:--| create() |如果該inode描述一個目錄檔案,那麼當在該目錄下建立或開啟一個檔案時,核心必須為這個檔案建立一個inode。VFS通過呼叫該inode的i_op->create()函式來完成上述新inode的建立。該函式的第一個引數為該目錄的 inode,第二個引數為要開啟新檔案的dentry,第三個引數是對該檔案的訪問許可權。如果該inode描述的是一個普通檔案,那麼該inode永遠都不會呼叫這個create函式; lookup() |查詢指定檔案的dentry; link() |用於在指定目錄下建立一個硬連結。這個link函式最終會被系統呼叫link()呼叫。該函式的第一個引數是原始檔案的dentry,第二個引數即為上述指定目錄的inode,第三個引數是連結檔案的dentry。 unlink ()|在某個目錄下刪除指定的硬連結。這個unlink函式最終會被系統呼叫unlink()呼叫。 第一個引數即為上述硬連結所在目錄的inode,第二個引數為要刪除檔案的dentry。 symlink ()|在某個目錄下新建 mkdir()|在指定的目錄下建立一個子目錄,當前目錄的inode會呼叫i_op->mkdir()。該函式會被系統呼叫mkdir()呼叫。第一個引數即為指定目錄的inode,第二個引數為子目錄的dentry,第三個引數為子目錄許可權; rmdir ()|從inode所描述的目錄中刪除一個指定的子目錄時,該函式會被系統呼叫rmdir()最終呼叫; mknod() |在指定的目錄下建立一個特殊檔案,比如管道、裝置檔案或套接字等。 ## 3)目錄項(dentry) 目錄項是描述檔案的邏輯屬性,只存在於記憶體中,並沒有實際對應的磁碟上的描述,更確切的說是存在於記憶體的目錄項快取,為了提高查詢效能而設計。 注意不管是資料夾還是最終的檔案,都是屬於目錄項,所有的目錄項在一起構成一顆龐大的目錄樹。 例如:open一個檔案/home/xxx/yyy.txt,那麼/、home、xxx、yyy.txt都是一個目錄項,VFS在查詢的時候,根據一層一層的目錄項找到對應的每個目錄項的inode,那麼沿著目錄項進行操作就可以找到最終的檔案。 注意:目錄也是一種檔案(所以也存在對應的inode)。開啟目錄,實際上就是開啟目錄檔案。 ```c 108 struct dentry { 109 /* RCU lookup touched fields */ 110 unsigned int d_flags; /* protected by d_lock */ 111 seqcount_t d_seq; /* per dentry seqlock */ 112 struct hlist_bl_node d_hash; /* lookup hash list */ 113 struct dentry *d_parent; /* parent directory 父目錄*/ 114 struct qstr d_name; 115 struct inode *d_inode; /* Where the name belongs to - NULL is 116 * negative 與該目錄項關聯的inode*/ 117 unsigned char d_iname[DNAME_INLINE_LEN]; /* small names 短檔名*/ 118 119 /* Ref lookup also touches following */ 120 struct lockref d_lockref; /* per-dentry lock and refcount */ 121 const struct dentry_operations *d_op; /* 目錄項操作 */ 122 struct super_block *d_sb; /* The root of the dentry tree 這個目錄項所屬的檔案系統的超級塊(目錄項樹的根)*/ 123 unsigned long d_time; /* used by d_revalidate 重新生效時間*/ 124 void *d_fsdata; /* fs-specific data 具體檔案系統的資料 */ 125 126 struct list_head d_lru; /* LRU list 未使用目錄以LRU 演算法連結的連結串列 */ 127 /* 128 * d_child and d_rcu can share memory 129 */ 130 union { 131 struct list_head d_child; /* child of parent list 目錄項通過這個加入到父目錄的d_subdirs中*/ 132 struct rcu_head d_rcu; 133 } d_u; 134 struct list_head d_subdirs; /* our children 本目錄的所有孩子目錄連結串列頭 */ 135 struct hlist_node d_alias; /* inode alias list 索引節點別名連結串列*/ 136 }; ``` 一個有效的dentry結構必定有一個inode結構,這是因為一個目錄項要麼代表著一個檔案,要麼代表著一個目錄,而目錄實際上也是檔案。所以,只要dentry結構是有效的,則其指標d_inode必定指向一個inode結構。但是inode卻可以對應多個。 整個結構其實就是一棵樹,如果看過我的裝置模型kobject就能知道,目錄其實就是檔案(kobject、inode)再加上一層封裝,這裡所謂的封裝主要就是增加兩個指標,一個是指向父目錄,一個是指向該目錄所包含的所有檔案(普通檔案和目錄)的連結串列頭。 這樣才能有我們的目錄操作(比如回到上次目錄,只需要一個指標步驟【..】,而進入子目錄需要連結串列索引需要多個步驟) dentry相關的操作(inode裡面已經包含了mkdir,rmdir,mknod之類的操作了) ```c struct dentry_operations { /* 該函式判斷目錄物件是否有效。VFS準備從dcache中使用一個目錄項時,會呼叫該函式. */ int (*d_revalidate)(struct dentry *, unsigned int); int (*d_weak_revalidate)(struct dentry *, unsigned int); /* 該目錄生成雜湊值,當目錄項要加入到散列表時,VFS要呼叫此函式。 */ int (*d_hash)(const struct dentry *, struct qstr *); /* 該函式來比較name1和name2這兩個檔名。使用該函式要加dcache_lock鎖。 */ int (*d_compare)(const struct dentry *, const struct dentry *, unsigned int, const char *, const struct qstr *); /* 當d_count=0時,VFS呼叫次函式。使用該函式要叫 dcache_lock鎖。 */ int (*d_delete)(const struct dentry *); /* 當該目錄物件將要被釋放時,VFS呼叫該函式。 */ void (*d_release)(struct dentry *); void (*d_prune)(struct dentry *); /* 當一個目錄項丟失了其索引節點時,VFS就掉用該函式。 */ void (*d_iput)(struct dentry *, struct inode *); char *(*d_dname)(struct dentry *, char *, int); struct vfsmount *(*d_automount)(struct path *); int (*d_manage)(struct dentry *, bool); } ____cacheline_aligned; ``` ## 4)檔案物件(file) 檔案物件描述的是程序已經開啟的檔案。因為一個檔案可以被多個程序開啟,所以一個檔案可以存在多個檔案物件。但是由於檔案是唯一的,那麼inode就是唯一的,目錄項也是定的! 程序其實是通過檔案描述符來操作檔案的,每個檔案都有一個32位的數字來表示下一個讀寫的位元組位置,這個數字叫做檔案位置。 一般情況下開啟檔案後,開啟位置都是從0開始,除非一些特殊情況。Linux用file結構體來儲存開啟的檔案的位置,所以file稱為**開啟的檔案描述**。file結構形成一個雙鏈表,稱為系統開啟檔案表。 ### file ```c 775 struct file { 776 union { 777 struct llist_node fu_llist; /* 每個檔案系統中被開啟的檔案都會形成一個雙鏈表 */ 778 struct rcu_head fu_rcuhead; 779 } f_u; 780 struct path f_path; 781 #define f_dentry f_path.dentry 782 struct inode *f_inode; /* cached value */ 783 const struct file_operations *f_op; /* 指向檔案操作表的指標 */ 784 785 /* 786 * Protects f_ep_links, f_flags. 787 * Must not be taken from IRQ context. 788 */ 789 spinlock_t f_lock; 790 atomic_long_t f_count; /* 檔案物件的使用計數 */ 791 unsigned int f_flags; /* 開啟檔案時所指定的標誌 */ 792 fmode_t f_mode; /* 檔案的訪問模式(許可權等) */ 793 struct mutex f_pos_lock; 794 loff_t f_pos; /* 檔案當前的位移量 */ 795 struct fown_struct f_owner; 796 const struct cred *f_cred; 797 struct file_ra_state f_ra; /* 預讀狀態 */ 798 799 u64 f_version; /* 版本號 */ 800 #ifdef CONFIG_SECURITY 801 void *f_security; /* 安全模組 */ 802 #endif 803 /* needed for tty driver, and maybe others */ 804 void *private_data; /* 私有資料 */ 805 806 #ifdef CONFIG_EPOLL 807 /* Used by fs/eventpoll.c to link all the hooks to this file */ 808 struct list_head f_ep_links; 809 struct list_head f_tfile_llink; 810 #endif /* #ifdef CONFIG_EPOLL */ 811 struct address_space *f_mapping;/* 頁快取對映 */ 812 #ifdef CONFIG_DEBUG_WRITECOUNT 813 unsigned long f_mnt_write_state; 814 #endif 815 } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */ ``` 重點解釋一些重要欄位: 1. 首先,f_flags、f_mode和f_pos代表的是這個程序當前操作這個檔案的控制資訊。這個非常重要,因為對於一個檔案,可以被多個程序同時開啟,那麼對於每個程序來說,操作這個檔案是非同步的,所以這個三個欄位就很重要了。 2. 對於引用計數f_count,當我們關閉一個程序的某一個檔案描述符時候,其實並不是真正的關閉檔案,僅僅是將f_count減一,當f_count=0時候,才會真的去關閉它。對於dup,fork這些操作來說,都會使得f_count增加,具體的細節,以後再說。 3. f_op也是很重要的!是涉及到所有的檔案的操作結構體。例如:使用者使用read,最終都會呼叫file_operations中的讀操作,而file_operations結構體是對於不同的檔案系統不一定相同。裡面一個重要的操作函式是release函式,當用戶執行close時候,其實在核心中是執行release函式,這個函式僅僅將f_count減一,這也就解釋了上面說的,使用者close一個檔案其實是將f_count減一。只有引用計數減到0才關閉檔案。 注意:對於“正在使用”和“未使用”的檔案物件分別使用一個雙向連結串列進行管理。 ### files_struct 上面的file只是對一個檔案而言,對於一個程序(使用者)來說,可以同時處理多個檔案,所以需要另一個結構來管理所有的files! 即:使用者開啟檔案表--->files_struct ```c 172 struct files_struct { 173 atomic_t count; 174 rwlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */ 175 int max_fds; 176 int max_fdset; 177 int next_fd; 178 struct file ** fd; /* current fd array */ 179 fd_set *close_on_exec; 180 fd_set *open_fds; 181 fd_set close_on_exec_init; 182 fd_set open_fds_init; 183 struct file * fd_array[NR_OPEN_DEFAULT]; 184 }; ``` 解釋一些欄位: |**欄位** | **描述** | |--|:--| count| 引用計數 file_lock| 鎖,保護下面的欄位 max_fds| 當前檔案物件的最大的數量 max_fdset| 檔案描述符最大數 next_fd| 已分配的最大的檔案描述符+1 fd| 指向檔案物件指標陣列的指標,一般就是指向最後一個欄位fd_arrray,當檔案數超過NR_OPEN_DEFAULT時候,就會重新分配一個數組,然後指向這個新的陣列指標! close_on_exec| 執行exec()時候需要關閉的檔案描述符 open_fds| 指向開啟的檔案描述符的指標 close_on_exec_init| 執行exec()時候需要關閉的檔案描述符初始化值 open_fds_init| 檔案描述符初值集合 fd_array| 檔案物件指標的初始化陣列 ### fs_struct 上面的file和files_struct記錄的是與程序相關的檔案的資訊,但是對於程序本身來說,自身的一些資訊用什麼表示,這裡就涉及到fs_struct結構體。 ```c 5 struct fs_struct { 6 atomic_t count; 7 rwlock_t lock; 8 int umask; 9 struct dentry * root, * pwd, * altroot; 10 struct vfsmount * rootmnt, * pwdmnt, * altrootmnt; 11 }; ``` 解釋一些欄位: |欄位 | 描述 | |--|:--| count|引用計數 lock|保護鎖 umask|開啟檔案時候預設的檔案訪問許可權 root|程序的根目錄 pwd|程序當前的執行目錄 altroot|使用者設定的替換根目錄 注意:實際執行時,這三個目錄不一定都在同一個檔案系統中。例如,程序的根目錄通常是安裝於“/”節點上的ext檔案系統,而當前工作目錄可能是安裝於/etc的一個檔案系統,替換根目錄也可以不同檔案系統中。 rootmnt,pwdmnt,altrootmnt:對應於上面三個的安裝點。 ### 檔案方法(操作)file_operations ```c struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); int (*iterate) (struct file *, struct dir_context *); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); int (*show_fdinfo)(struct seq_file *m, struct file *f); }; ``` 上面這個對我們驅動開發人員應該是最熟悉的,也是必須掌握的了。 |欄位 | 描述 | |--|:--| owner |用於指定擁有這個檔案操作結構體的模組,通常取THIS_MODULE; llseek |用於設定檔案的偏移量。第一個引數指明要操作的檔案,第二個引數為偏移量,第三個引數為開始偏移的位置(可取SEEK_SET,SEEK_CUR和SEEK_END之一)。 read |從檔案中讀資料。第一個引數為原始檔,第二個引數為目的字串,第三個引數指明欲讀資料的總位元組數,第四個引數指明從原始檔的某個偏移量處開始讀資料。由系統呼叫read()呼叫; write |往檔案裡寫資料。第一個引數為目的檔案,第二個引數源字串,第三個引數指明欲寫資料的總位元組數,第四個引數指明從目的檔案的某個偏移量出開始寫資料。由系統呼叫write()呼叫; mmap |將指定檔案對映到指定的地址空間上。由系統呼叫mmap()呼叫; open |開啟指定檔案,並且將這個檔案和指定的索引結點關聯起來。由系統呼叫open()呼叫; release |釋放以開啟的檔案,當開啟檔案的引用計數(f_count)為0時,該函式被呼叫; fsync() |檔案在緩衝的資料寫回磁碟; # 四、程序與這四者之間的關係 核心中用於管理程序的結構體是task_struct。 程序開啟檔案就涉及到上述4個重要的資料結構: ```c file fs_struct files_struct namespace ``` 每個程序都有自己的namespace。 fs_struct用於表示程序與檔案系統之間的結構關係,比如當前的工作目錄,程序的根目錄等等。 files_struct 用於表示當前程序開啟的檔案。 而對於每一個開啟的檔案,由file物件來表示。 Linux中,常常用檔案描述符(file descriptor)來表示一個開啟的檔案,這個描述符的值往往是一個大於或等於0的整數。 而這個整數,其實就是在files_struct中file陣列fd的下標。 對於所有開啟的檔案, 這些檔案描述符會儲存在open_fds的點陣圖中。 ![程序與超級塊、檔案、索引結點、目錄項的關係](https://img-blog.csdnimg.cn/20210119180835191.png) 從圖中可知: 1. 程序通過task_struct中的一個域files->files_struct 來了解它當前所開啟的檔案物件;而我們通常所說的檔案描述符其實是程序開啟的檔案物件陣列的索引值。 2. 檔案物件通過域f_dentry找到它對應的dentry物件,再由dentry物件的域d_inode找到它對應的索引節點(通過索引節點又可以得到超級塊的資訊,也就可以得到最終操作檔案的方法,在open檔案的時候就是使用這樣一個過程),這樣就建立了檔案物件與實際的物理檔案的關聯。 3. 檔案物件所對應的檔案操作函式列表是通過索引節點的域i_fop得到的,而i_fop最終又是通過struct super_operations *s_op來初始化的。 VFS檔案系統中的inode和dentry與實際檔案系統的inode和dentry有一定的關係,但不能等同。 真實磁碟檔案的inode和dentry是存在於物理外存上的,但VFS中的inode和dentry是存在於記憶體中的,系統讀取外存中的inode和dentry資訊進行一定加工後,生成記憶體中的inode和dentry。 虛擬的檔案系統也具有inode和dentry結構,只是這是系統根據相應的規則生成的,不存在於實際外存中。 # 五、磁碟與檔案系統 假設一塊磁碟被分為好幾個分割槽,每個分割槽都是不同的檔案系統。 ![磁碟與檔案系統](https://img-blog.csdnimg.cn/20210117194918574.p