Linux 虛擬文件系統四大對象:超級塊、inode、dentry、file之間關係
更多嵌入式原創文章,請關注公眾號:一口Linux
一:文件系統
1. 什麼是文件系統?
操作系統中負責管理和存儲文件信息的軟件機構稱為文件管理系統,簡稱文件系統。
通常文件系統是用於存儲和組織文件的一種機制,便於對文件進行方便的查找與訪問。
文件系統是對文件存儲設備的空間進行組織和分配,負責文件存儲並對存入的文件進行保護和檢索的系統。
它負責為用戶建立文件,存入、讀出、修改、轉儲文件,控制文件的存取,當用戶不再使用時撤銷文件等。
隨着文件種類的增多,擴增了更多的文件系統,為了對各種文件系統進行統一的管理與組織。
2. Linux文件系統
Linux將文件系統分為了兩層: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系統中的架構視圖如下:
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 */
重點解釋一些重要字段:
- 首先,f_flags、f_mode和f_pos代表的是這個進程當前操作這個文件的控制信息。這個非常重要,因為對於一個文件,可以被多個進程同時打開,那麼對於每個進程來說,操作這個文件是異步的,所以這個三個字段就很重要了。
- 對於引用計數f_count,當我們關閉一個進程的某一個文件描述符時候,其實並不是真正的關閉文件,僅僅是將f_count減一,當f_count=0時候,才會真的去關閉它。對於dup,fork這些操作來說,都會使得f_count增加,具體的細節,以後再說。
- 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的位圖中。
從圖中可知:
- 進程通過task_struct中的一個域files->files_struct 來了解它當前所打開的文件對象;而我們通常所說的文件描述符其實是進程打開的文件對象數組的索引值。
- 文件對象通過域f_dentry找到它對應的dentry對象,再由dentry對象的域d_inode找到它對應的索引節點(通過索引節點又可以得到超級塊的信息,也就可以得到最終操作文件的方法,在open文件的時候就是使用這樣一個過程),這樣就建立了文件對象與實際的物理文件的關聯。
- 文件對象所對應的文件操作函數列表是通過索引節點的域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結構,只是這是系統根據相應的規則生成的,不存在於實際外存中。
五、磁盤與文件系統
假設一塊磁盤被分為好幾個分區,每個分區都是不同的文件系統。