當用戶層程序員在編寫文件函數時,常常會用到open(),read()和write()這類系統調用,而且用的也很爽,因爲我們只需要知道這些函數如何調用就OK了,而不用深究具體文件系統和實際物理介質是如何實現的。而我們內核編程人員就要了解這些底層實現,給上層人員提供更多的方便。因此我們的任務就更復雜,佛家有一句名言:“我不入地獄,誰如地獄”因此我們就要有奉獻精神編寫出具有統一簡單效率高的接口爲用戶層程序員提供方便。
如果你有這種奉獻精神和喜愛Linux內核以及有着很高的好奇心的話,那我們就來深入學習內核看看Linux內核到底是如何實現這些功能的。
一.VFS的概念
Linux 之所以能支持除了Ext2文件系統之外的各種文件系統,是因爲Linux提供了一種統一的框架,就是所謂的虛擬文件系統轉換(Virtual FilesystemSwitch),簡稱虛擬文件系統(VFS)。這樣, 用戶程序可以通過同一個文件系統界面,也就是同一組系統調用,能夠對各種不同的文件系統以及文件進行操作。
1.在上面提到Linux中支持幾十種文件系統,它是怎麼管理的呢,它都管理什麼呢?
首先,VFS 只對掛載到文件系統種的文件系統進行管理,即它時按需管理的。
其次,因爲它繼承了Unix的設計思想,所以它也是對文件,目錄項,索引節點和超級塊進行管理。
2.VFS中四個主要對象:
1>超級塊對象:描述已安裝文件系統。
每個文件系統 都對應一個超級對象。文件系統的控制信息存儲在超級塊中。
2>索引節點對象:描述一個文件。
每個文件 都有一個索引節點對象。每個索引節點對象都有一索引節點號---->正是用這個號來唯一的標識某個文件系統中的指定文件。
3>目錄項對象:描述一個目錄項,是路徑的組成部分。
VFS把每個目錄看作一個由若干子目錄和文件組成的常規文件。
例如:我們在查找路徑名:/tmp/test時,內核爲根目錄“/”創建第一個目錄項對象,爲根目錄下tmp項創建第二級目錄項對象,爲/tmp目錄下的test項創建第三級目錄項對象。
4>文件對象:描述由進程打開的文件。
Tiger-John說明:
1.因爲 VFS 將目錄作爲一個文件來處理,所以不存在目錄對象。雖然目錄項不同於目錄,但目錄卻和文件相同。
2.Linux中將文件的相關信息和文件本身區分開了。
在Linux 中文件的相關信息,被存儲在一個單獨的數據結構中,該結構被稱爲索引節點。文件(目錄)信息按照索引節點形式存儲在單獨的塊中;控制信息被集中存儲在磁盤的超級塊中
說了這麼多,VFS到底是如何實現的呢?----現在我們就來深入內核代碼來看看吧
二.VFS四個主要對象的實現
VFS採用的是面向對象的設計思想,使用一簇數據結構來代表通用文件對象。所以內核中的數據結構都使用C結構體實現。
1.superblock(超級塊)對象:
1>超級塊用來描述特定文件系統的信息。它存放在磁盤特定的扇區中 ,它在使用的時候將信息存在於內存中。
2> 當內核對一個文件系統進行初始化和註冊時在內存爲其分配一個超級塊,這就是VFS超級塊。
即,VFS超級塊是各種具體文件系統在安裝時建立的,並在這些文件系統卸載時被自動刪除 。
3>超級塊對象由結構體 super_block來體現。
VFS超級塊的數據結構爲 super_block在include/linux/fs.h中可以查看
1318struct super_block {
1319 struct list_head s_list; // 超級快鏈表指針
1320 dev_t s_dev; // 設備表示符
1321 unsigned char s_dirt; //髒標誌
1322 unsigned char s_blocksize_bits; //以位爲單位的塊的大小
1323 unsigned long s_blocksize; //以字節爲單位的塊大小
1324 loff_t s_maxbytes; //文件大小上限
1325 struct file_system_type *s_type; //指向文件系統的file_system_type 數據結構的指針
1326 const struct super_operations *s_op; //超級塊方法
1327 const struct dquot_operations *dq_op; //磁盤限額方法
1328 const struct quotactl_ops *s_qcop; //限額控制方法
1329 const struct export_operations *s_export_op; //導出方法
1330 unsigned long s_flags; //登錄標誌
1331 unsigned long s_magic; //文件系統的魔數
1332 struct dentry *s_root; //目錄登錄點
1333 struct rw_semaphore s_umount; //卸載信號量
1334 struct mutex s_lock; //超級塊信號量
1335 int s_count; //超級塊引用計數
1336 atomic_t s_active; //活動引用記數
1337#ifdef CONFIG_SECURITY
1338 void *s_security; //安全模塊
1339#endif
1340 const struct xattr_handler **s_xattr;
1341
1342 struct list_head s_inodes; //把所有索引對象鏈接在一起,存放的是頭結點
1343 struct hlist_head s_anon; //匿名目錄項
1344#ifdef CONFIG_SMP
1345 struct list_head __percpu *s_files;
1346#else
1347 struct list_head s_files; //鏈接所有打開的文件。
1348#endif
1349 /* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */
1350 struct list_head s_dentry_lru; /* unused dentry lru */
1351 int s_nr_dentry_unused; /* # of dentry on lru */
1352
1353 struct block_device *s_bdev; //相關的塊設備
1354 struct backing_dev_info *s_bdi;
1355 struct mtd_info *s_mtd;
1356 struct list_head s_instances; //該類型文件系統
1357 struct quota_info s_dquot; //限額相關選項
1358
1359 int s_frozen;
1360 wait_queue_head_t s_wait_unfrozen;
1361
1362 char s_id[32]; //文本名字
1363
1364 void *s_fs_info; //文件系統特設信息
1365 fmode_t s_mode;
1366
1367 /* Granularity of c/m/atime in ns.
1368 Cannot be worse than a second */
1369 u32 s_time_gran;
1370
1371 /*
1372 * The next field is for VFS *only*. No filesystems have any business
1373 * even looking at it. You had been warned.
1374 */
1375 struct mutex s_vfs_rename_mutex; /* Kludge */
1376
1377 /*
1378 * Filesystem subtype. If non-empty the filesystem type field
1379 * in /proc/mounts will be "type.subtype"
1380 */
1381 char *s_subtype;
1382
1383 /*
1384 * Saved mount options for lazy filesystems using
1385 * generic_show_options()
1386 */
1387 char *s_options;
1388};
現在我們來對其中主要的數據結構進行分析
比較重要的數據結構以在上面有註釋。
我們先來看一個圖,再來具體解釋:
1.s_list :所有的超級塊形成一個雙聯表,s_list.prev和s_list.next分別指向與當前超級塊相鄰的前一個元素和後一個元素。通常我們通過list_entry宏來獲取s_list所在超級塊結構體的地址(超級塊對像是以雙向鏈表的形式鏈接在一起的)。
2. s_lock :保護鏈表免受多處理器系統上的同時訪問。
3.s_fs_info: 字段指向具體文件系統的超級塊。
例如:超級塊對象指的是Ext2文件系統,該字段就指向ext2_sb_info數據結構。
4.s_dirt :來表示該超級塊是否是髒的,也就是說,磁盤上的數據是否必須要更新。
5.超級塊對象是通過函數alloc_super()創建並初始化的。在文件系統安裝時,內核會調用該函數以便從磁盤讀取文件系統超級塊,並且將其信息填充到內存中的超級塊對象中 。
6. 超級對象中最重要的就是s_op,每一種文件系統都應該有自己的super_operations操作實例。它指向超級塊的操作函數表, 它由struct super_operations結構體來表示。
現在來看一下它的定義:它的定義在 include/linux/fs.h頭文件中可以看到
1560struct super_operations {
1561 struct inode *(*alloc_inode)(struct super_block *sb);
1562 void (*destroy_inode)(struct inode *);
1563
1564 void (*dirty_inode) (struct inode *);
1565 int (*write_inode) (struct inode *, struct writeback_control *wbc);
1566 int (*drop_inode) (struct inode *);
1567 void (*evict_inode) (struct inode *);
1568 void (*put_super) (struct super_block *);
1569 void (*write_super) (struct super_block *);
1570 int (*sync_fs)(struct super_block *sb, int wait);
1571 int (*freeze_fs) (struct super_block *);
1572 int (*unfreeze_fs) (struct super_block *);
1573 int (*statfs) (struct dentry *, struct kstatfs *);
1574 int (*remount_fs) (struct super_block *, int *, char *);
1575 void (*umount_begin) (struct super_block *);
1576
1577 int (*show_options)(struct seq_file *, struct vfsmount *);
1578 int (*show_stats)(struct seq_file *, struct vfsmount *);
1579#ifdef CONFIG_QUOTA
1580 ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
1581 ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
1582#endif
1583 int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
1584};
1>可以看到該結構體中的每一項都是一個指向超級塊操作函數的指針,超級塊操作函數執行文件系統和索引節點的低層操作。
2>當文件系統需要對超級塊執行操作時,要在超級塊對象中尋找需要的操作方法
例如:一個文件系統要寫自己的超級塊,需要調用:
sturct super_block * sb;
sb->s_op->write_super)(sb);
sb是指向文件系統超級塊的指針,沿着該指針進入超級塊操作函數表,並從表中取得writ_super()函數,該函數執行寫入超級塊的實際操作。
Tiger-John說明:
儘管writ_super()方法來自超級塊,但是在調用時,還是要把超級塊作爲參數傳遞給它。
3>.分析其中比較重要的一些數據結構。
a.struct inode * alloc_inode(struct super_block * sb) :創建和初始化一個新的索引結點。
b.void destroy_inode(struct super_block *sb) :釋放指定的索引結點 。
c.void dirty_inode(struct inode *inode) :VFS在索引節點被修改時會調用此函數。
d.void write_inode(struct inode *inode, struct writeback_control *wbc) 將指定的inode寫回磁盤。
e.void drop_inode( struct inode * inode):刪除索引節點。
f.void put_super(struct super_block *sb) :用來釋放超級塊。
g.void write_super(struct super_block *sb):更新磁盤上的超級塊。
h.void sync_fs(struct super_block *sb,in wait):使文件系統的數據元素與磁盤上的文件系統同步,wait參數指定操作是否同步。
i.int statfs(struct super_block *sb,struct statfs *statfs):獲取文件系統狀態。把文件系統相關的統計信息放在statfs中。
2.VFS的索引節點
1>文件系統處理文件或目錄時的所有信息都存放在稱爲索引節點的數據結構中。
文件名可以隨時該,但是索引節點對文件是唯一的(它是隨文件的存在而存在)。
2>具體文件系統的索引節點是存放在磁盤上的,是一種靜態結構,要使用它,必須將其調入內存,填寫 VFS的索引節點。VFS索引節點也稱爲動態節點。(即索引節點僅當文件被方位時纔在內存中創建)
3>我們來深入下來看一下它的內核
它的定義在 /include/linux/fs.h中有這個結構體的定義
725struct inode {
726 struct hlist_node i_hash; //散列表
727 struct list_head i_list; //索引節點鏈表
728 struct list_head i_sb_list; //鏈接一個文件系統中所有inode的鏈表
729 struct list_head i_dentry; //目錄項鍊表
730 unsigned long i_ino; //索引節點號
731 atomic_t i_count; //引用計數
732 unsigned int i_nlink; //硬連接數
733 uid_t i_uid; //使用者的id
734 gid_t i_gid; //使用組id
735 dev_t i_rdev; //實際設備標識符號
736 unsigned int i_blkbits;
737 u64 i_version; //版本號
738 loff_t i_size; //以字節爲單位
739#ifdef __NEED_I_SIZE_ORDERED
740 seqcount_t i_size_seqcount;
741#endif
742 struct timespec i_atime; //最後訪問時間
743 struct timespec i_mtime; //最後修改時間
744 struct timespec i_ctime; //最後改變時間
745 blkcnt_t i_blocks; //文件的塊數
746 unsigned short i_bytes; //使用的字節數
747 umode_t i_mode; //訪問權限控制
748 spinlock_t i_lock; //自旋鎖
749 struct mutex i_mutex;
750 struct rw_semaphore i_alloc_sem;
751 const struct inode_operations *i_op; //索引節點操作表
752 const struct file_operations *i_fop; //默認的索引節點鏈表
753 struct super_block *i_sb; //相關的超級塊
754 struct file_lock *i_flock; //文件鎖鏈表
755 struct address_space *i_mapping; //相關的地址映射
756 struct address_space i_data; //設備地址映射
757#ifdef CONFIG_QUOTA
758 struct dquot *i_dquot[MAXQUOTAS]; //節點的磁盤限額
759#endif
760 struct list_head i_devices; //塊設備鏈表
761 union {
762 struct pipe_inode_info *i_pipe; //管道信息
763 struct block_device *i_bdev; //塊設備驅動
764 struct cdev *i_cdev;
765 };
766
767 __u32 i_generation; //索引節點版本號
768
769#ifdef CONFIG_FSNOTIFY
770 __u32 i_fsnotify_mask; /* all events this inode cares about */
771 struct hlist_head i_fsnotify_marks;
772#endif
773
774 unsigned long i_state; //狀態標誌
775 unsigned long dirtied_when; //首次修改時間
776
777 unsigned int i_flags; //文件系統標誌
778
779 atomic_t i_writecount; //寫者計數
780#ifdef CONFIG_SECURITY
781 void *i_security; //安全模塊
782#endif
783#ifdef CONFIG_FS_POSIX_ACL
784 struct posix_acl *i_acl;
785 struct posix_acl *i_default_acl;
786#endif
787 void *i_private; /* fs or device private pointer */
788};
在我們看完這段代碼後,是不是發現在上面被紫色標記的一些信息我們都很熟悉,還記的我們在終端下輸入命令:ls 命令後可以看到文件的信息,這些信息就是記錄在這裏的。
這是爲什麼呢?
在原理課中我們知道,文件是由FCB(文件控制塊控制的),而具體到Linux下,文件是有索引節點結構控制的。所以在struct inode 裏存放了文件的基本信息。
大家有沒有發現在怎麼在索引節點裏面會包含超級塊的對象呢(上面被紅色標記的),有些人可能不明白了,先看看下面的圖,再來解釋把。
從上面對的圖我們可以看出索引節點 對象靠i_sb指回到了超級塊對象。
i_hash :爲了提高查找inode的效率,每一個inode都會有一個hash值。該字段指向hash值相同的inode所形成的雙鏈表該字段包含prev和next兩個指針,分別指向上述鏈表的前一個元素和後一個元素;
i_list :所有索引結點形成的雙聯表,(從圖上可以看出,索引節點對象是靠它來鏈接的)
i_dentry :所有引用該inode的目錄項將形成一個雙聯表,該字段即爲這個雙聯表的頭結點
i_ino :索引結點號。通過ls -i命令可以查看文件的索引節點號;
i_count :引用計數;
i_nlink :硬鏈接數。當該inode描述一個目錄時,這個值至少爲2,代表.和..的數目;
i_uid :inode所屬文件的擁有者的id,通過ls -n可查看擁有者id;
i_gid :inode所屬文件所在組的id,通過ls -n可查看組id;
i_rdev :如果該inode描述的是一個設備文件,此值爲設備號;
i_blkbits :以位爲單位的塊大小;
i_atime :文件最近一次被訪問的時間。通過ls -lu可查看該時間;
i_mtime :文件最近一次被修改的時間,這裏的修改只文件內容被修改。通過ls -l可查看該時間;
i_ctime :文件最近一次被修改的時間,這裏的修改除了指文件內容被修改外,更強調的是文件的屬性被修改。通過ls -lc可查看該時間;
i_blocks :文件使用塊的個數,通過ls -s可以查看該某個文件的塊使用數目;
i_mode :文件的訪問權限;
i_op : 指向索引結點操作結構體的指針;
i_fop : 指向文件操作街頭體的指針;
i_sb : 指向inode所屬文件系統的超級塊的指針;
i_pipe :如果inode所代表的文件是一個管道,則使用該字段;
i_bdev :如果inode所代表的文件是一個塊設備,則使用該字段;
i_cdev :如果inode所代表的文件是一個字符設備,則使用該字段;
i_state : 索引節點的狀態信息。
Tiger-John說明:
1.在同一個文件系統中,每個索引節點號都是唯一的,內核可以根據索引節點號的散列值來查找其inode結構。
2.inode中有兩個設備號i_dev和i_rdev。
a.特設文件外,每個節點都存儲在某個設備上,這就是i_dev。
b. 如果索引節點所代表的並不是常規文件,而是某個設備,則需要另一個設備號,這就是i_rdev。
3.對i_state的說明:
每個VFS索引節點都會複製磁盤索引節點包含的一些數據,比如文件佔有的磁盤數。如果i_state 的值等於I_DIR,該索引節點就是“髒“的。也就是說,對應的磁盤索引節點必須被更新。
4.三個重要的雙向鏈表:
a.未用索引節點鏈表,正在使用索引節點鏈表和髒索引節點鏈表。每個索引節點對象總是出現在上面三種的一個。
b.這3個鏈表都是通過索引節點的i_list 域鏈接在一起的。
c.屬於“正在使用“或“髒“鏈表的索引節點對象也同時存放在一個散列表中。
<散列表加快了對索引節點對象的搜索>.
5.一個索引節點代表文件系統中的一個文件,它也可 以是設備或管道這樣的特殊文件。所以在索引節點結構體中有一些和特殊文件相關的項。
6.有時候某些文件系統並不能完整地包含索引節點結構體要求的所有信息。那麼此時剛怎麼辦呢?
此時,可以給它賦一些其它的值。
例如:一個文件系統可能並不記錄文件的訪問時間,這時就可以在i_atime中存儲0。
7.i_list和i_sb_list的區別
a:i_list:VFS中使用四個鏈表來管理不同狀態的inode結點。inode_unused將當前未使用的inode鏈接起來,inode_in_use將當前正在被使用的inode鏈接起來,超級塊中的s_dirty將所有髒inode鏈接起來,i_hash將所有hash值相同的inode鏈接起來。i_list中包含prev和next兩個指針,分別指向與當前inode處於同一個狀態鏈表的前後兩個元素
b.i_sb_list:每個文件系統中的inode都會形成一個雙聯表,這個雙鏈表的頭結點存放在超級塊的s_inodes中。而該字段中的prev和next指針分別指向在雙鏈表中與其相鄰的前後兩個元素
c..索引結點中i_sb_list鏈表是鏈接一個文件系統中所有inode的鏈表,因此相鄰的inode之間均會由此鏈表鏈接;而i_list鏈接的是處於同一個狀態的所有inode。所以,相鄰inode之間並不一定鏈接在一起。
4>與索引節點關聯的方法叫索引節點操作表,它是在 struct inode_operations這個結構體中具體描述的。它的定義在 include/linux/fs.h頭文件中定義。
1516struct inode_operations {
1517 int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
1518 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);
1519 int (*link) (struct dentry *,struct inode *,struct dentry *);
1520 int (*unlink) (struct inode *,struct dentry *);
1521 int (*symlink) (struct inode *,struct dentry *,const char *);
1522 int (*mkdir) (struct inode *,struct dentry *,int);
1523 int (*rmdir) (struct inode *,struct dentry *);
1524 int (*mknod) (struct inode *,struct dentry *,int,dev_t);
1525 int (*rename) (struct inode *, struct dentry *,
1526 struct inode *, struct dentry *);
1527 int (*readlink) (struct dentry *, char __user *,int);
1528 void * (*follow_link) (struct dentry *, struct nameidata *);
1529 void (*put_link) (struct dentry *, struct nameidata *, void *);
1530 void (*truncate) (struct inode *);
1531 int (*permission) (struct inode *, int);
1532 int (*check_acl)(struct inode *, int);
1533 int (*setattr) (struct dentry *, struct iattr *);
1534 int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);
1535 int (*setxattr) (struct dentry *, const char *,const void *,size_t,int);
1536 ssize_t (*getxattr) (struct dentry *, const char *, void *, size_t);
1537 ssize_t (*listxattr) (struct dentry *, char *, size_t);
1538 int (*removexattr) (struct dentry *, const char *);
1539 void (*truncate_range)(struct inode *, loff_t, loff_t);
1540 long (*fallocate)(struct inode *inode, int mode, loff_t offset,
1541 loff_t len);
1542 int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
1543 u64 len);
1544};
inode_operations對象包括了內核針對特定文件所能調用的方法。
現在我們對其中一些重要的結果進行分析:
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() :在指定的目錄下創建一個特殊文件,比如管道、設備文件或套接字等。
以上對索引節點中數據結構的解釋來自edsionte
-------------------------------------------------------------------------------------
Tiger-John總結:
1.對於不同的文件系統,上面的每個函數的具體實現是不同的,也不是每個函數都必須實現,沒有實現的函數對應的域應當設置爲NULL 。
2.上面我們說了兩個主要的操作對像:superblock和inode。它們兩個對象中都包含一個操作對象。super_operations和inode_opetations它們有什麼區別呢
a.super_operations對象:其中包括內核針對特定文件系統 所有調用的方法。
b.inode_operations對象: 其中包括內核對特定文件 的所有調用的方法。
所以它們一個是針對文件系統,一個是針對文件 。
3.本來inode 中應該包括“目錄節點”的名稱,但由於符號鏈接的存在,導致一個物理文件可能有多個文件名,因此把和“目錄節點”名稱相關的部分從 inode 中分開,放在一個專門的 dentry 結構中。
本篇文章來源於 Linux公社網站(www.linuxidc.com) 原文鏈接:http://www.linuxidc.com/Linux/2011-02/32127.htm