文件系統之文件系統架構

文件系統層次分析

由上而下主要分爲用戶層、VFS 層、文件系統層、緩存層、塊設備層、磁盤驅動層、磁盤物理層

  1. 用戶層:最上面用戶層就是我們日常使用的各種程序,需要的接口主要是文件的創建、刪除、打開、關閉、寫、讀等。

  2. VFS 層:我們知道 Linux 分爲用戶態和內核態,用戶態請求硬件資源需要調用 System Call 通過內核態去實現。用戶的這些文件相關操作都有對應的 System Call 函數接口,接口調用 VFS 對應的函數。

  3. 文件系統層:不同的文件系統實現了 VFS 的這些函數,通過指針註冊到 VFS 裏面。所以,用戶的操作通過 VFS 轉到各種文件系統。文件系統把文件讀寫命令轉化爲對磁盤 LBA 的操作,起了一個翻譯和磁盤管理的作用。

  4. 緩存層:文件系統底下有緩存,Page Cache,加速性能。對磁盤 LBA 的讀寫數據緩存到這裏。

  5. 塊設備層:塊設備接口 Block Device 是用來訪問磁盤 LBA 的層級,讀寫命令組合之後插入到命令隊列,磁盤的驅動從隊列讀命令執行。Linux 設計了電梯算法等對很多 LBA 的讀寫進行優化排序,儘量把連續地址放在一起。

  6. 磁盤驅動層:磁盤的驅動程序把對 LBA 的讀寫命令轉化爲各自的協議,比如變成 ATA 命令,SCSI 命令,或者是自己硬件可以識別的自定義命令,發送給磁盤控制器。Host Based SSD 甚至在塊設備層和磁盤驅動層實現了 FTL,變成對 Flash 芯片的操作。

  7. 磁盤物理層:讀寫物理數據到磁盤介質。

虛擬文件系統 VFS

VFS 的作用就是採用標準的系統調用讀寫位於不同物理介質上的不同文件系統。VFS 是一個可以讓 open()、read()、write() 等系統調用不用關心底層的存儲介質和文件系統類型就可以工作的粘合層。在古老的 DOS 操作系統中,要訪問本地文件系統之外的文件系統需要使用特殊的工具才能進行。而在 Linux 下,通過 VFS,一個抽象的通用訪問接口屏蔽了底層文件系統和物理介質的差異性。每一種類型的文件系統代碼都隱藏了實現的細節。因此,對於 VFS 層和內核的其它部分而言,每一種類型的文件系統看起來都是一樣的。

現在先了解一下 VFS 的數據結構:雖然不同文件系統類型的物理結構不同,但是虛擬文件系統定義了一套統一的數據結構:超級快對象、索引節點對象、 目錄項對象、文件對象。

1. 超級塊對象

超級塊對象代表一個具體的已安裝文件系統,一般是一個分區有一個超級塊。各種文件系統都必須實現超級塊對象,該對象存儲文件系統的信息,使用 struct super_block 結構體表示,在 include/linux/fs.h 文件中

struct super_block {
 struct list_head s_list; //超級快鏈表頭,指向所有超級塊
 dev_t   s_dev;  //設備號
 unsigned char  s_blocksize_bits;//塊大小,單位爲位
 unsigned long  s_blocksize;//塊大小,單位爲字節
 loff_t   s_maxbytes;//文件大小上限
 struct file_system_type *s_type;//文件系統類型
 const struct super_operations *s_op;//超級塊操作方法函數
 const struct dquot_operations *dq_op;//磁盤限額方法函數
 const struct quotactl_ops *s_qcop;//限額控制方法函數
 const struct export_operations *s_export_op;//導出方法函數
 unsigned long  s_flags;//掛載標誌位
 unsigned long  s_iflags;//
 unsigned long  s_magic;//文件系統的魔數,每種文件系統類型被分配一個唯一的魔幻數
 struct dentry  *s_root;//目錄掛載點
 struct rw_semaphore s_umount;//卸載信號量
 int   s_count;//超級塊引用計數
 atomic_t  s_active;//活動引用計數
#ifdef CONFIG_SECURITY
 void                    *s_security;//指向安全模塊
#endif
 const struct xattr_handler **s_xattr;//擴展的屬性
#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
 const struct fscrypt_operations *s_cop;//文件加密方法函數
#endif
 struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
 struct list_head s_mounts; /* list of mounts; _not_ for fs use */
 struct block_device *s_bdev;//相關的塊設備
 struct backing_dev_info *s_bdi;
 struct mtd_info  *s_mtd;//存儲磁盤信息

 //把同一個文件系統類型的所有超級塊實例鏈接在一起,鏈表的頭節點是結構體file_system_type的成員fs_supers
 struct hlist_node s_instances;
 unsigned int  s_quota_types; /* Bitmask of supported quota types */
 struct quota_info s_dquot;//磁盤配額相關選項

 struct sb_writers s_writers;//超級塊使用信息

 char   s_id[32];//超級塊名字,也是分區名字
 uuid_t   s_uuid;  /* UUID */

 void    *s_fs_info;//文件系統私有信息
 unsigned int  s_max_links;//文件鏈接上限
 fmode_t   s_mode;//安裝權限

 /* Granularity of c/m/atime in ns.
    Cannot be worse than a second */
 u32     s_time_gran;//文件訪問修改時間的最小單位

 /*
  * The next field is for VFS *only*. No filesystems have any business
  * even looking at it. You had been warned.
  */
 struct mutex s_vfs_rename_mutex; /* Kludge */

 /*
  * Filesystem subtype.  If non-empty the filesystem type field
  * in /proc/mounts will be "type.subtype"
  */
 char *s_subtype;//文件系統子類型

 const struct dentry_operations *s_d_op; //默認的超級塊中目錄操作方法函數

 /*
  * Saved pool identifier for cleancache (-1 means none)
  */
 int cleancache_poolid;//保存池標識符
  ......
 /*
  * Owning user namespace and default context in which to
  * interpret filesystem uids, gids, quotas, device nodes,
  * xattrs and security labels.
  */
 struct user_namespace *s_user_ns;//擁有者

 /*
  * Keep the lru lists last in the structure so they always sit on their
  * own individual cachelines.
  */
 struct list_lru  s_dentry_lru ____cacheline_aligned_in_smp;//最近最少使用的目錄鏈表
 struct list_lru  s_inode_lru ____cacheline_aligned_in_smp;//最近最少使用的文件鏈表
 struct rcu_head  rcu;
 struct work_struct destroy_work;

 struct mutex  s_sync_lock; /* sync serialisation lock */

 /*
  * Indicates how deep in a filesystem stack this SB is
  */
 int s_stack_depth;//指示此超級塊在文件系統堆棧中的深度

 /* s_inode_list_lock protects s_inodes */
 spinlock_t  s_inode_list_lock ____cacheline_aligned_in_smp;//超級塊鎖
 struct list_head s_inodes;//超級塊中所有文件的鏈表

 spinlock_t  s_inode_wblist_lock;//回寫需要的鎖
 struct list_head s_inodes_wb;//需要回寫的文件鏈表
} __randomize_layout;

超級塊對象中最重要的是成員是 const struct super_operations *s_op; 這是超級塊操作方法函數:

struct super_operations {
    struct inode *(*alloc_inode)(struct super_block *sb);//創建和初始化一個新的索引節點
 void (*destroy_inode)(struct inode *);//釋放一個索引節點

    void (*dirty_inode) (struct inode *, int flags);//處理髒索引節點的函數,日誌文件系統通過此函數進行日誌更新
 int (*write_inode) (struct inode *, struct writeback_control *wbc);//將制定的索引節點寫入磁盤
 int (*drop_inode) (struct inode *);//索引節點引用計數爲0時執行此函數
 void (*evict_inode) (struct inode *);//刪除磁盤的索引節點,硬鏈接計數爲0時會執行此函數
 void (*put_super) (struct super_block *);//釋放超級塊,卸載文件系統時調用
 int (*sync_fs)(struct super_block *sb, int wait);//使文件系統和磁盤數據同步
 int (*freeze_super) (struct super_block *);//鎖定超級塊
 int (*freeze_fs) (struct super_block *);//做快照以前調用,阻塞更新,文件系統處於一個一致的狀態
 int (*thaw_super) (struct super_block *);//超級塊解鎖
 int (*unfreeze_fs) (struct super_block *);//做完快照調用,可以繼續更新文件系統
 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 *);//查看文件系統裝載的選項,用於proc文件系統
 int (*show_devname)(struct seq_file *, struct dentry *);//查看硬件設備名稱
 int (*show_path)(struct seq_file *, struct dentry *);//查看掛載路徑
 int (*show_stats)(struct seq_file *, struct dentry *);//查看文件系統的統計信息,用於proc文件系統
#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);
 struct dquot **(*get_dquots)(struct inode *);
#endif
 int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
 long (*nr_cached_objects)(struct super_block *,
      struct shrink_control *);
 long (*free_cached_objects)(struct super_block *,
        struct shrink_control *);
};

2. 掛載描述符

一個文件系統,只有掛載到內存中目錄樹的一個目錄下,進程才能訪問這個文件系統。每次掛載文件系統,虛擬文件系統就會創建一個掛載描述符。掛載描述符用來描述文件系統的一個掛載實例,同一個存儲設備上的文件系統可以多次掛載,每次掛載到不同的目錄下。結構體爲 struc mount,在 fs/mount.h 文件中:

struct mount {
 struct hlist_node mnt_hash;//散列表
 struct mount *mnt_parent;//父親文件系統
 struct dentry *mnt_mountpoint;//掛載點的目錄
 struct vfsmount mnt;//文件系統的掛載信息,結構體包含根目錄和超級塊
 union {
  struct rcu_head mnt_rcu;
  struct llist_node mnt_llist;
 };
#ifdef CONFIG_SMP
 struct mnt_pcp __percpu *mnt_pcp;
#else
 int mnt_count;
 int mnt_writers;
#endif
 struct list_head mnt_mounts;//子文件系統鏈表頭
 struct list_head mnt_child;//父文件系統的mnt_mounts
 struct list_head mnt_instance;//把掛載描述符添加到超級塊的掛載實例鏈表中
 const char *mnt_devname;//指向存儲設備的名稱,比如/dev/hda1 
 struct list_head mnt_list;
 struct list_head mnt_expire; /* link in fs-specific expiry list */
 struct list_head mnt_share;//共享掛載的循環鏈表
 struct list_head mnt_slave_list;//從屬掛載的鏈表
 struct list_head mnt_slave;//用於從屬掛載的鏈表
 struct mount *mnt_master;//指向主掛載文件系統
 struct mnt_namespace *mnt_ns;//指向掛載命名空間
 struct mountpoint *mnt_mp;//指向掛載點
 struct hlist_node mnt_mp_list;//把掛載描述符加入同一個掛載點的掛載描述符鏈表
 struct list_head mnt_umounting; /* list entry for umount propagation */
#ifdef CONFIG_FSNOTIFY
 struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
 __u32 mnt_fsnotify_mask;
#endif
 int mnt_id;//掛載id
 int mnt_group_id;掛載組id
 int mnt_expiry_mark;  /* true if marked for expiry */
 struct hlist_head mnt_pins;
 struct fs_pin mnt_umount;
 struct dentry *mnt_ex_mountpoint;
} __randomize_layout;

3. 文件系統類型

因爲每種文件系統的超級塊的格式不同,所以每種文件系統需要向虛擬文件系統註冊文件系統類型 file_system_type,並且實現 mount 方法用來讀取和解析超級塊。結構體爲 struct file_system_type ,在 include/linux/fs.h 文件中:

struct file_system_type {
 const char *name;//文件系統類型
 int fs_flags;//使用標誌
#define FS_REQUIRES_DEV  1  /* 文件系統在物理設備上 */
#define FS_BINARY_MOUNTDATA 2 /*二進制的數據結構數據,比如nfs */
#define FS_HAS_SUBTYPE  4 /* 文件系統含有子類型,比如fuse */
#define FS_USERNS_MOUNT  8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */

 //掛載文件系統的回調函數,用來讀取並且解析超級塊
 struct dentry *(*mount) (struct file_system_type *, int,
         const char *, void *);
 //卸載文件系統的回調函數
 void (*kill_sb) (struct super_block *);
 struct module *owner;//指向實現這個文件系統的模塊,一般位THIS_MODULE
 struct file_system_type * next;//指向文件系統鏈表下一種文件系統類型
 struct hlist_head fs_supers;//該種類文件系統的超級塊鏈表

 struct lock_class_key s_lock_key;
 struct lock_class_key s_umount_key;
 struct lock_class_key s_vfs_rename_key;
 struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

 struct lock_class_key i_lock_key;
 struct lock_class_key i_mutex_key;
 struct lock_class_key i_mutex_dir_key;
};

4. 索引節點

在文件系統中,每個文件對應一個索引節點,而且一個索引節點只有文件被訪問纔會在內存中創建。索引節點描述了兩類數據信息,1. 文件的屬性,也稱爲元數據;2. 文件數據的存儲位置。當內核訪問存儲設備上一個文件的時候,會在內核中創建和初始化一個節點,結構體爲 struct inode,在 include/linux/fs.h 文件中:

struct inode {
 umode_t   i_mode;//文件的訪問權限
 unsigned short  i_opflags;//
 kuid_t   i_uid;//使用者id
 kgid_t   i_gid;//使用組id
 unsigned int  i_flags;//文件系統標誌

#ifdef CONFIG_FS_POSIX_ACL
 //訪問控制相關屬性
 struct posix_acl *i_acl;
 struct posix_acl *i_default_acl;
#endif

 const struct inode_operations *i_op;//索引節點操作方法函數
 struct super_block *i_sb;//索引節點所屬超級塊
 struct address_space *i_mapping;//相關地址映射(文件內容映射)

#ifdef CONFIG_SECURITY
 void   *i_security;//安全模塊使用
#endif

 /* Stat data, not accessed from path walking */
 unsigned long  i_ino;//索引節點號
 /*
  * Filesystems may only read i_nlink directly.  They shall use the
  * following functions for modification:
  *
  *    (set|clear|inc|drop)_nlink
  *    inode_(inc|dec)_link_count
  */
 union {
  const unsigned int i_nlink;//硬鏈接計數
  unsigned int __i_nlink;
 };
 dev_t   i_rdev;//實際設備標識符
 loff_t   i_size;//文件長度
 struct timespec64 i_atime;//最後訪問時間
 struct timespec64 i_mtime;//最後修改時間
 struct timespec64 i_ctime;//最後改變時間
 spinlock_t  i_lock;//自旋鎖
 unsigned short          i_bytes;//使用的字節數
 u8   i_blkbits;//以位爲單位的塊大小
 u8   i_write_hint;//
 blkcnt_t  i_blocks;//文件的塊數

#ifdef __NEED_I_SIZE_ORDERED
 seqcount_t  i_size_seqcount;//對i_size進行串行計數
#endif

 /* Misc */
 unsigned long  i_state;//索引狀態標誌
 struct rw_semaphore i_rwsem;//讀寫信號量

 unsigned long  dirtied_when; /* jiffies of first dirtying */
 unsigned long  dirtied_time_when;//第一次弄髒數據時間

 struct hlist_node i_hash;//散列表,用於快速查找
 struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
 struct bdi_writeback *i_wb;  /* the associated cgroup wb */

 /* foreign inode detection, see wbc_detach_inode() */
 int   i_wb_frn_winner;
 u16   i_wb_frn_avg_time;
 u16   i_wb_frn_history;
#endif
 struct list_head i_lru;//最近最少使用鏈表
 struct list_head i_sb_list;
 struct list_head i_wb_list;//等待回寫索引鏈表
 union {
  struct hlist_head i_dentry;//目錄項鍊表
  struct rcu_head  i_rcu;//讀拷貝鏈表
 };
 atomic64_t  i_version;//版本號
 atomic_t  i_count;//引用計數
 atomic_t  i_dio_count;//直接io操作計數
 atomic_t  i_writecount;//寫者計數
#ifdef CONFIG_IMA
 atomic_t  i_readcount;//讀者計數
#endif
 const struct file_operations *i_fop;//索引操作方法函數
 struct file_lock_context *i_flctx;//
 struct address_space i_data;//設備地址映射
 struct list_head i_devices;//塊設備鏈表
 union {
  struct pipe_inode_info *i_pipe;//管道信息
  struct block_device *i_bdev;//塊設備驅動
  struct cdev  *i_cdev;//字符設備驅動
  char   *i_link;//硬鏈接
  unsigned  i_dir_seq;//目錄信號量
 };

 __u32   i_generation;

#ifdef CONFIG_FSNOTIFY
 __u32   i_fsnotify_mask; /* all events this inode cares about */
 struct fsnotify_mark_connector __rcu *i_fsnotify_marks;//文件系統通知掩碼
#endif

#if IS_ENABLED(CONFIG_FS_ENCRYPTION)
 struct fscrypt_info *i_crypt_info;//加密信息
#endif

 void   *i_private; /* fs or device private pointer */
} __randomize_layout;

索引文件分爲以下幾種類型,在 i_flags 參數中區分:

內核支持兩種鏈接:

索引節點的操作函數也很重要:

struct inode_operations {
 //在特定目錄中尋找索引節點
 struct dentry * (*lookup) (struct inode *,struct dentry *, unsigned int);
 const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);//獲取指定inode的連接
 int (*permission) (struct inode *, int);//檢查指定的inode是否允許訪問
 
 //獲取訪問控制方法函數
 struct posix_acl * (*get_acl)(struct inode *, int);

 int (*readlink) (struct dentry *, char __user *,int);

 //創建一個索引節點,文件open時會用到
 int (*create) (struct inode *,struct dentry *, umode_t, bool);
 
 //創建硬鏈接,系統調用link會調用它
 int (*link) (struct dentry *,struct inode *,struct dentry *);
 
 //刪除目錄項指向的索引節點,系統調用unlink會調用它
 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 *, unsigned int);
   
 int (*setattr) (struct dentry *, struct iattr *);//設置文件屬性
 int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);//獲取文件屬性
 ssize_t (*listxattr) (struct dentry *, char *, size_t);
 
 //
 int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
        u64 len);
 int (*update_time)(struct inode *, struct timespec64 *, int);//更新訪問時間
 int (*atomic_open)(struct inode *, struct dentry *,
      struct file *, unsigned open_flag,
      umode_t create_mode);
 int (*tmpfile) (struct inode *, struct dentry *, umode_t);
 int (*set_acl)(struct inode *, struct posix_acl *, int);//設置訪問控制參數
} ____cacheline_aligned;

5. 目錄項

目錄項對象代表一個目錄項,明明 linux 有個說法是一切皆文件,那爲什麼還要有目錄項對象呢?因爲雖然全部可以統一有索引節點表示,但是 VFS 需要經常執行目錄相關操作,比如路徑查找等,需要解析路徑的每一個組成部分,不但要確保它有效,還需要進一步查找下一部分。解析一個路徑並且遍歷是一個耗時的,目錄對象的引入會使得這個過程非常簡單。目錄項對象有 struct dentry 表示,在 include/linux/dcache.h 文件中:

struct dentry {
 /* RCU lookup touched fields */
 unsigned int d_flags;//目錄項緩存標識符,由d_lock保護
 seqcount_t d_seq;//目錄項對象的順序鎖
 struct hlist_bl_node d_hash;//散列表,方便查找
 struct dentry *d_parent;//父目錄的目錄項對象
 struct qstr d_name;//目錄項名稱
 struct inode *d_inode;//該目錄項下的索引節點
     
 unsigned char d_iname[DNAME_INLINE_LEN];//存放短的文件名稱

 /* Ref lookup also touches following */
 struct lockref d_lockref;//每個目錄項的鎖
 const struct dentry_operations *d_op;//目錄項操作方法函數
 struct super_block *d_sb;//目錄項所在的超級塊
 unsigned long d_time;//重置時間
 void *d_fsdata;//文件系統特有數據

 union {
  struct list_head d_lru; //未使用鏈表的
  wait_queue_head_t *d_wait;//等待隊列
 };
 struct list_head d_child;//目錄項下的子目錄項鍊表
 struct list_head d_subdirs;//目錄項下所有目錄項鍊表
 /*
  * d_alias and d_rcu can share memory
  */
 union {
  struct hlist_node d_alias;//索引節點別名鏈表
  struct hlist_bl_node d_in_lookup_hash; /* only for in-lookup ones */
   struct rcu_head d_rcu;//rcu加鎖
 } d_u;
} __randomize_layout;

和前面兩個對象不同,目錄項對象沒有對應的磁盤結構,VFS 根據字符串形式的路徑現場創建它,由於目錄項對象沒有真正的保存在磁盤中,目錄項沒有修改標誌、回寫等。目錄項有三種狀態:

如果 VFS 遍歷路徑名中的所有元素並解析成目錄項對象,還要達到最深層次,這是非常費力的事情,會浪費大量時間,所以 VFS 會對目錄項對象遍歷解析,解析完畢後會緩存在 dcache 中,緩存主要包括三部分:

現在看看目錄項的操作函數:

struct dentry_operations {
 //判斷目錄項對象是否有效
 int (*d_revalidate)(struct dentry *, unsigned int);
 int (*d_weak_revalidate)(struct dentry *, unsigned int);
 
 //目錄項對象生產散列值,加入散列表是需要此函數
 int (*d_hash)(const struct dentry *, struct qstr *);
 
 //文件名對比,根據文件系統類型定製,因爲有些文件系統不區分大小寫
 int (*d_compare)(const struct dentry *,
   unsigned int, const char *, const struct qstr *);
   
 //刪除目錄項對象,當d_count爲0時調用
 int (*d_delete)(const struct dentry *);
 
 //創建和初始化目錄項對象
 int (*d_init)(struct dentry *);
 
 //釋放目錄項對象,默認情況下什麼都不做
 void (*d_release)(struct dentry *);
 void (*d_prune)(struct dentry *);
 
 //釋放索引節點,當磁盤索引被刪除時調用
 void (*d_iput)(struct dentry *, struct inode *);
 char *(*d_dname)(struct dentry *, char *, int);
 struct vfsmount *(*d_automount)(struct path *);
 int (*d_manage)(const struct path *, bool);
 struct dentry *(*d_real)(struct dentry *, const struct inode *);
} ____cacheline_aligned;

6. 文件對象

文件對象代表進程打開的文件,包括普通文件和目錄文件,是用戶直接操作的對象,也是用戶最熟悉的對象。他是由 open 創建,close 撤銷使用的。通過文件對象,VFS 可以找到其對應的目錄項和索引節點,從而找到所在的超級塊。文件對象實際上沒有對應的磁盤結構,他的作用是連接用戶和 VFS,給與用戶操作文件的方法,從而實現間接操作磁盤文件。文件對象由 struct file 結構體表示,在在 include/linux/fs.h 文件中:

struct file {
 union {
  struct llist_node fu_llist;//文件對象鏈表
  struct rcu_head  fu_rcuhead;//釋放後的rcu鏈表
 } f_u;
 struct path  f_path;//包含文件目錄項
 struct inode  *f_inode;//文件對應的索引節點
 const struct file_operations *f_op;//文件的操作方法函數

 /*
  * Protects f_ep_links, f_flags.
  * Must not be taken from IRQ context.
  */
 spinlock_t  f_lock;//單個文件鎖
 enum rw_hint  f_write_hint;//文件的寫生命時間
 atomic_long_t  f_count;//文件對象使用計數
 unsigned int   f_flags;//當打開文件時指定的標誌
 fmode_t   f_mode;//文件的訪問模式,比如讀、寫、創建
 struct mutex  f_pos_lock;//f_pos的互斥鎖
 loff_t   f_pos;//文件的當前位移量
 struct fown_struct f_owner;//擁有者通過信號進行異步IO數據傳送
 const struct cred *f_cred;//文件的信任狀
 struct file_ra_state f_ra;//預讀狀態信息

 u64   f_version;//版本號
#ifdef CONFIG_SECURITY
 void   *f_security;//安全模塊
#endif
 /* needed for tty driver, and maybe others */
 void   *private_data;//私有數據,一般用於指向tty驅動

#ifdef CONFIG_EPOLL
 /* Used by fs/eventpoll.c to link all the hooks to this file */
 struct list_head f_ep_links;//事件池鏈表
 struct list_head f_tfile_llink;//所有文件的事件池鏈表
#endif /* #ifdef CONFIG_EPOLL */
 struct address_space *f_mapping;//文件頁緩存映射
 errseq_t  f_wb_err;//文件回寫錯誤狀態標誌位
} __randomize_layout

文件對象的操作方法函數很重要,由 struct file_operations 表示:

struct file_operations {
 struct module *owner;
 //更新文件的位置偏移量,由系統調用llseek調用它
 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 (*read_iter) (struct kiocb *, struct iov_iter *);//同步讀
 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);//同步寫
 int (*iterate) (struct file *, struct dir_context *);
 int (*iterate_shared) (struct file *, struct dir_context *);
 
 //函數睡眠等待給定的文件活動,由系統調用poll調用
 __poll_t (*poll) (struct file *, struct poll_table_struct *);
 
 //給設備文件發送命令參數,有系統調用ioctl調用
 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 *);
 unsigned long mmap_supported_flags;
 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 (*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);//檢查flags的有效性,一般用於nfs,它不允許O_APPEND和O_DIRECT結合
 
 //忠告鎖,由系統調用flock調用它
 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 **, void **);
 long (*fallocate)(struct file *file, int mode, loff_t offset,
     loff_t len);
 void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
 unsigned (*mmap_capabilities)(struct file *);
#endif
 ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
   loff_t, size_t, unsigned int);
 int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,
   u64);
 int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,
   u64);
 int (*fadvise)(struct file *, loff_t, loff_t, int);
} __randomize_layout;

總結:

我們在進程中掛載了一個文件系統,也就是說找到了這個超級塊 super_block 結構體,可以通過 super_block 結構體中的 s_inodes(索引)找到對應的文件,同時,在遍歷 inodes 的時候,會自動的解析 inode 路徑的每一個組成部分,組成 struct dentry(目錄項),方便系統使用樹的形式表示 inode(索引)之間的關係。這樣子我們打開了這個磁盤掛載的目錄就可以看到磁盤的目錄和文件了,我們打開了一個索引,系統會創建一個 struct file(文件)結構體,這就是我們平時操作一個文件的方式了。

相反,我們在進程中操作一個文件(struct file),可以通過 struct file 中的 struct inode 參數找到其索引,進而找到超級塊(struct super_block),這樣子,VFS 就知道要操作的文件系統和索引了。

上面的是 VFS 對上層的通道,對底層又會是怎麼樣子呢?其實,在內核初始化的時候,會註冊了一種文件系統類型,VFS 掛載這種文件系統會根據 super_block 結構體中 struct file_system_type *s_type,找到這個文件系統類型,內核纔可以根據文件系統類型來調用對應超級塊的操作函數,因爲每一種文件系統類型的超級塊操作函數具體是實現是不同的。然後在掛載這種文件系統的時候,會創建一個 struct mount 實例,當然,比如有兩個 u 盤,要掛載兩個,就要有兩個 struct mount 實例。

補充一下,file_system_type、 super block、 mount 的關係圖:

file 、dentry 、inode 關係圖:

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Z2jcUzpPHeBcobRrUl8wfw