Linux 虛擬文件系統四大對象:超級塊、inode、dentry、file之間關係

更多嵌入式原創文章,請關注公眾號:一口Linux

一:文件系統

1. 什麼是文件系統?

作業系統中負責管理和存儲文件資訊的軟體機構稱為文件管理系統,簡稱文件系統。

通常文件系統是用於存儲和組織文件的一種機制,便於對文件進行方便的查找與訪問。

文件系統是對文件存儲設備的空間進行組織和分配,負責文件存儲並對存入的文件進行保護和檢索的系統。

它負責為用戶建立文件,存入、讀出、修改、轉儲文件,控制文件的存取,當用戶不再使用時撤銷文件等。

隨著文件種類的增多,擴增了更多的文件系統,為了對各種文件系統進行統一的管理與組織。

2. Linux文件系統

Linux將文件系統分為了兩層:VFS(虛擬文件系統)、具體文件系統,如下圖所示:
VFS
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

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創建了一個公共根目錄和全局文件系統樹。要訪問一個文件系統中的文件,必須先將這個文件系統掛載在全局文件系統樹的某個根目錄下,這一掛載過程被稱作文件系統的掛載,所掛載的目錄稱為掛載點。

傳統的文件系統在磁碟上的布局如下:

由上圖可知,文件系統的開頭通常是由一個磁碟扇區所組成的引導塊,該部分的主要目的是用於對作業系統的引導。一般只在啟動作業系統時使用。

隨後是超級塊,超級塊主要存放了該物理磁碟中文件系統結構的相關資訊,並且對各個部分的大小進行說明。

最後由i節點點陣圖,邏輯塊點陣圖、i節點、邏輯塊這幾部分分布在物理磁碟上。

Linux為了對超級塊,i節點,邏輯塊這三部分進行高效的管理,Linux創建了幾種不同的數據結構,分別是文件系統類型、inode、dentry等幾種。

其中,文件系統類型規定了某種文件系統的行為,利用該數據結構可以構造某種文件系統類型的實例,另外,該實例也被稱為超級塊實例。

超級塊則是反映了文件系統整體的控制資訊。超級塊能夠以多種的方式存在,對於基於磁碟的文件系統,它以特定的格式存在於磁碟的固定區域(取決於文件系統類型)上。在掛載文件系統時,該超級塊中的內容被讀入磁碟中,從而構建出位於記憶體中的新的超級塊。

inode則反映了文件系統對象中的一般元數據資訊。dentry則是反映出某個文件系統對象在全局文件系統樹中的位置。

Linux對這四種數據結構進行了相關的關聯。
如下圖:

結構體關係

1. 超級塊(super block)

超級塊:一個超級塊對應一個文件系統(已經安裝的文件系統類型如ext2,此處是實際的文件系統,不是VFS)。

之前我們已經說了文件系統用於管理這些文件的數據格式和操作之類的,系統文件有系統文件自己的文件系統,同時對於不同的磁碟分區也有可以是不同的文件系統。那麼一個超級塊對於一個獨立的文件系統。保存文件系統的類型、大小、狀態等等。

(「文件系統」和「文件系統類型」不一樣!一個文件系統類型下可以包括很多文件系統即很多的super_block)

既然我們知道對於不同的文件系統有不同的super_block,那麼對於不同的super_block的操作肯定也是不同的,所以我們在下面的super_block結構中可以看到上面說的抽象的struct結構(例如下面的:struct super_operations):

(linux內核3.14)

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 引用計數

超級塊方法

 
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最大數量就是文件的最大數量。

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的四個鏈表:

static struct hlist_head *inode_hashtable __read_mostly;

節點方法

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)。打開目錄,實際上就是打開目錄文件。

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之類的操作了)

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

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

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結構體。

  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

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個重要的數據結構:

file 
fs_struct 
files_struct 
namespace

每個進程都有自己的namespace。

fs_struct用於表示進程與文件系統之間的結構關係,比如當前的工作目錄,進程的根目錄等等。

files_struct 用於表示當前進程打開的文件。

而對於每一個打開的文件,由file對象來表示。

Linux中,常常用文件描述符(file descriptor)來表示一個打開的文件,這個描述符的值往往是一個大於或等於0的整數。
而這個整數,其實就是在files_struct中file數組fd的下標。
對於所有打開的文件, 這些文件描述符會存儲在open_fds的點陣圖中。

進程與超級塊、文件、索引結點、目錄項的關係

從圖中可知:

  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結構,只是這是系統根據相應的規則生成的,不存在於實際外存中。

五、磁碟與文件系統

假設一塊磁碟被分為好幾個分區,每個分區都是不同的文件系統。
磁碟與文件系統