虛擬文件系統
- 1 通用文件系統
- 2 文件系統抽象層
- 3 Unix文件系統
- 4 VFS對象及其數據結構
- 其他VFS對象
- 5 超級快對象
- 超級塊操作
- 6 索引節點對象
- 索引節點操作
- 7 目錄項對象
- 目錄項狀態
- 目錄項緩存
- 目錄項操作
- 8 文件對象
- 9 和文件系統相關的數據結構
- 10 和進程相關的數據結構
- 11 Linux中的文件系統
虛擬文件系統,簡稱VFS,是內核的子系統,為用戶空間程序提供了文件系統相關的接口。系統中所有文件系統不但依賴VFS共存,而且也依靠VFS系統協同工作。通過虛擬文件系統,程序可以 利用標準的UNIX文件系統調用對 不同介質上的 不同文件系統進行讀寫操作。
如下圖:使用cp命令從ext3文件系統格式的硬盤拷貝數據到ext2文件系統格式的可移動磁盤上。兩種不同的文件系統,兩種不同的介質,連接到同一個VFS上。
1 通用文件系統
VFS使得用戶可以直接使用open()、write()和read()這樣的系統調用而無需考慮具體文件系統實際物理介質。系統調用還可以在這些不同的文件系統和介質之間執行。正是由于包括Linux在內的現代操作系統引入了抽象層,通過虛擬接口訪問文件系統,才使得這種協作性和通用性成為可能,新的文件系統和新種類的存儲介質都能找到進入Linux之路,程序無需重寫,甚至無需重新編譯。
2 文件系統抽象層
之所以可以使用這種通用接口對所有類型的文件系統進行操作,是因為內核在它的底層文件系統接口上建立了一個抽象層,該抽象層使Linux能夠支持各種文件系統,即便是它們在功能和行為上存在很大差別。為了支持多文件系統,VFS提供了一個通用文件系統模型,該模型囊括了我們所能想到的文件系統的常用功能和行為。
VFS抽象層之所以能鏈接各種各樣的文件系統,是因為它定義了所有文件系統都支持的基本的、概念上的接口和數據結構。同時實際文件系統也將自身的諸如如何打開文件、目錄是什么等概念在形式上與VFS定義保持一致。因為實際文件系統的代碼在同一的接口和數據結構下隱藏了具體的實現細節,所以在VFS層和內核的其他部分看來,所有的文件系統都是相同的。
內核通過抽象層能夠方便、簡單地支持各種類型的文件系統,實際文件系統通過編程提供VFS所期望的抽象接口和數據結構,這樣,內核就可以毫不費力地和任何文件系統系統工作。并且這樣提供給用戶空間的接口,也可以和任何文件系統無縫連接在一起,完成實際工作。
我們在用戶空間調用write方法,會首先在VFS找到一個通用系統調用sys_write()處理,sys_wirte()會找到所在的文件系統給出的寫操作,然后通過該寫操作,往物理介質上寫數據。
3 Unix文件系統
Unix使用了四種和文件系統相關的傳統抽象概念:文件、目錄項、索引節點和安裝點。
從本質上說文件系統是特殊的數據分層存儲結構,它包含文件、目錄和相關的控制信息。在Unix中,文件系統被安裝在一個特定的安裝點上,所有的已安裝文件系統都作為根文件系統數的枝葉出現在系統中。
文件其實可以看做是一個有序字節串,字節串中第一個字節是文件的頭,最后一個字節時文件的尾。文件通過目錄組織起來,文件目錄好比一個文件夾,用來容納相關文件。因為目錄也可以包含子目錄,所以目錄可以層層嵌套,形成文件路徑。路徑中的每一部分都被稱作目錄條目。/home/wolfman/butter的根目錄是/,目錄home、wolfman和文件butter都是目錄條目,它們被統稱為目錄項。在Unix中,目錄屬于普通文件,它列出包含在其中的所有文件。由于VFS把目錄當做文件對待,所以可以對目錄和文件執行相同的操作。
Unix系統將文件的相關信息和文件本身這兩個概念加以區分,文件的相關信息,有時被稱作文件的元數據,被存儲在一個單獨的數據結構中,該結構被稱為索引節點(inode)。
4 VFS對象及其數據結構
VFS其實采用的是面向對象的設計思路,使用一族數據結構來代表通用文件對象。VFS有四個主要的對象類型,它們分別是:
- 超級塊對象,它代表一個已安裝的文件系統
- 索引節點對象,它代表一個文件
- 目錄項對象,它代表一個目錄項,是路徑的一個組成部分。
- 文件對象,它代表由進程打開的文件
注意,因為VFS將目錄作為一個文件來處理,所以不存在目錄對象。目錄項代表的是路徑中的一個組成部分,它可能包括一個普通文件,目錄項不同于目錄,但目錄卻和文件相同。
其他VFS對象
VFS使用了大量結構體對象,它所包括的對象遠遠多于上面提到的這幾種主要對象。比如每個注冊的文件系統都是由file_system_type結構體來表示,它描述了文件系統及其能力,另外,每一個安裝點也都有vfsmount結構體表示,它包含安裝點的相關信息。如位置和安裝標志等。
后面還要介紹三個與進程相關的結構體,它們描述了文件系統以及和進程相關的文件,這三個結構體分別是file_struct、fs_struct和namespace。
5 超級快對象
各種文件系統都必須實現超級塊,該對象用于存儲特定文件系統的信息,通常對應于存放在磁盤特定扇區中的文件系統超級塊或文件系統控制塊。對于并非基于磁盤的文件系統(如基于內存的文件系統,比如sysfs),它們會在使用現場創建超級塊并將其保存到內存中。
超級塊對象由spuer_block結構體表示,定義在文件linux/fs.h中。創建、管理和銷毀超級塊對象的代碼位于文件fs/super.c中。超級塊對象通過alloc_super()函數創建并初始化,在文件系統安裝時,內核回調用該函數以便從磁盤讀取文件系統超級塊,并且將其信息填充到內存中的超級塊對象中。
超級塊操作
超級塊對象中最重要的一個域是s_op,它指向超級塊的操作函數表。超級塊操作函數表如下
struct super_operations {struct inode *(*alloc_inode)(struct super_block *sb);void (*destroy_inode)(struct inode *);void (*read_inode) (struct inode *);void (*dirty_inode) (struct inode *);int (*write_inode) (struct inode *, int);void (*put_inode) (struct inode *);void (*drop_inode) (struct inode *);void (*delete_inode) (struct inode *);void (*put_super) (struct super_block *);void (*write_super) (struct super_block *);int (*sync_fs)(struct super_block *sb, int wait);void (*write_super_lockfs) (struct super_block *);void (*unlockfs) (struct super_block *);int (*statfs) (struct super_block *, struct kstatfs *);int (*remount_fs) (struct super_block *, int *, char *);void (*clear_inode) (struct inode *);void (*umount_begin) (struct super_block *);int (*show_options)(struct seq_file *, struct vfsmount *);
};
該結構體中的每一項都是指向超級快操作函數的指針,超級塊操作函數執行文件系統和索引節點的低層操作。
上面所有函數都是由VFS在進程上下文中調用,必要時,它們都可以阻塞,這其中的一些函數是可選的:在超級塊操作表中,文件系統可以將不需要的函數指針設置成NULL,如果VFS發現操作函數指針是NULL,那它要么就會調用通用函數執行相應操作,要門什么也不做,如何選擇取決于函數。
6 索引節點對象
索引節點對象包含了內核在操作文件或目錄時需要的全部信息。對于Unix風格的文件系統來說,這些信息可以從磁盤索引節點直接讀入。如果一個文件系統沒有索引節點,那么,不管這些相關信息在磁盤上是怎么存放的,文件系統都必須從中提取這些消息。
索引節點對象由inode結構體表示,定義在文件linux/fs.h中。
索引節點操作
索引節點對象中的inode_operations項描述了VFS用以操作索引節點對象的所有方法,這些方法由文件系統實現。inode_operatiions結構體定義在文件linux/fs.h中
struct inode_operations {int (*create) (struct inode *,struct dentry *,int, struct nameidata *);struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);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 *,int);int (*rmdir) (struct inode *,struct dentry *);int (*mknod) (struct inode *,struct dentry *,int,dev_t);int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *);int (*readlink) (struct dentry *, char __user *,int);int (*follow_link) (struct dentry *, struct nameidata *);void (*put_link) (struct dentry *, struct nameidata *);void (*truncate) (struct inode *);int (*permission) (struct inode *, int, struct nameidata *);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 *);
};
7 目錄項對象
VFS把目錄當作文件看待,所以在路徑/bin/vi中,bin和vi都是文件,bin是特殊的目錄文件,而vi是一個普通文件,路徑中的每個組成部分都由一個索引節點對象表示。
為了方便查找操作,VFS引入了目錄項的概念。每個目錄項代表路徑一個特定部分,對前一個例子來說,/、bin和vi都屬于目錄項對象。前兩個是目錄,最后一個是普通文件。
目錄項也可包括安裝點。在路徑/mnt/cdrom/foo中,/、mnt、cdrom和foo都屬于目錄項對象。VFS在執行目錄操作時,如果需要的話,會現場創建目錄項對象。
目錄項對象由dentry結構體表示,定義在文件linux/dcache.h中。
struct dentry {atomic_t d_count;unsigned int d_flags; /* protected by d_lock */spinlock_t d_lock; /* per dentry lock */struct inode *d_inode; /* Where the name belongs to - NULL is* negative *//** The next three fields are touched by __d_lookup. Place them here* so they all fit in a 16-byte range, with 16-byte alignment.*/struct dentry *d_parent; /* parent directory */struct qstr d_name;struct list_head d_lru; /* LRU list */struct list_head d_child; /* child of parent list */struct list_head d_subdirs; /* our children */struct list_head d_alias; /* inode alias list */unsigned long d_time; /* used by d_revalidate */struct dentry_operations *d_op;struct super_block *d_sb; /* The root of the dentry tree */void *d_fsdata; /* fs-specific data */struct rcu_head d_rcu;struct dcookie_struct *d_cookie; /* cookie, if any */struct hlist_node d_hash; /* lookup hash list */ int d_mounted;unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
不同于前面兩個對象,目錄項對象沒有對應的磁盤數據結構,VFS根據字符串性式的路徑名現場創建它,而且由于目錄項對象并非真正保存在磁盤上,所以目錄結構體沒有是否被修改的標志。
目錄項狀態
目錄項對象有三種有效狀態:被使用、未被使用和負狀態。
一個被使用的目錄項對應一個有效的索引節點(d_inode指向相應的索引節點)并且表明該對象存在一個或多個使用者(d_count為正值)。一個目錄項處于被使用狀態,意味著它正被VFS使用并且指向有效的索引節點,因此不能被丟棄。
一個未被使用的目錄項對應一個有效的索引節點,但是VFS當前并未使用它(d_count為0)。該目錄項對象指向一個有效的對象,而且被保留在緩存中以便需要時在使用它。
一個負狀態的目錄項沒有對應的有效索引節點(d_node為NULL),因為索引節點已經被刪除了,或路徑不再正確了,但是目錄項仍然保留,以便快速解析以后的路徑查詢。
目錄項緩存
如果VFS層遍歷路徑名中所有的元素并將它們逐個解析成目錄項對象,這將是一件非常費力的工作,會浪費大量的時間,所以內核將目錄項對象緩存在目錄項緩存(簡稱dcache)中。
目錄項緩存包括三個主要部分:
- "被使用的"目錄項鏈表
- "最近被使用的"雙向鏈表。該鏈表含有未被使用的和負狀態的目錄項對象。由于該鏈表以時間順序插入,所以鏈頭的結點是最新數據,當內核必須通過刪除結點項回收內存時,會從鏈尾刪除結點項,因為尾部的節點最舊,在近期內再次被使用的可能性最小
- 散列表和相應的散列函數用來快速地將給定路徑解析為相關目錄項對象。
舉個例子,假設你需要在自己目錄中編譯一個源文件,/home/dracula/src/foo.c,每一次對foo.c文件進行訪問,VFS必須沿著嵌套的目錄依次解析全部路徑:/、home、dracula、src和最終的foo.c。為了避免每次訪問該路徑名都進行這種耗時的操作,VFS會先在目錄項緩存中搜索路徑名,如果找到了,直接訪問。相反,如果該目錄項在目錄項緩存中并不存在,VFS就必須通過查文件系統為每個路徑分量解析路徑,解析完畢后,再將目錄項對象加入dcache中,以便以后可以快速訪問它。
目錄項操作
dentry_operation結構體指明了VFS操作目錄項的所有方法。
該結構體定義在文件<linux/dcache.h>中
struct dentry_operations {int (*d_revalidate)(struct dentry *, struct nameidata *);int (*d_hash) (struct dentry *, struct qstr *);int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);int (*d_delete)(struct dentry *);void (*d_release)(struct dentry *);void (*d_iput)(struct dentry *, struct inode *);
};
8 文件對象
文件對象表示進程已打開的文件。文件對象是已打開的文件在內存中的表示,該對象由相應的open()系統調用創建,由close()系統調用銷毀。因為多個進程可以同時打開和操作同一個文件,所以同一個文件也可能存在多個對應的文件對象(一個進程打開了一個文件就會有一個文件對象)。文件對象僅僅在進程觀點上代表已打開文件,它反過來指向目錄項對象(索引節點),其實只有目錄項對象才表示已打開的實際文件,雖然一個文件對應的文件對象不是唯一的,但對應的索引節點和目錄項對象是唯一的。
文件對象由file結構體表示,定義在文件linux/fs中
struct file {struct list_head f_list;struct dentry *f_dentry;struct vfsmount *f_vfsmnt;struct file_operations *f_op;atomic_t f_count;unsigned int f_flags;mode_t f_mode;int f_error;loff_t f_pos;struct fown_struct f_owner;unsigned int f_uid, f_gid;struct file_ra_state f_ra;unsigned long f_version;void *f_security;/* needed for tty driver, and maybe others */void *private_data;#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_head f_ep_links;spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */struct address_space *f_mapping;
};
類似于目錄項對象,文件對象實際上沒有對應的磁盤數據,所以在結構體中沒有代表其對象是否為臟,是否需要寫回磁盤的標志。文件對象通過f_dentry指針指向相關的目錄項對象。目錄項會指向相關的索引節點,索引節點會記錄文件是否是臟的。
文件對象的操作由file_operations結構體表示,定義在linux/fs.h中
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 (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, 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 (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);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 (*dir_notify)(struct file *filp, unsigned long arg);int (*flock) (struct file *, int, struct file_lock *);
};
9 和文件系統相關的數據結構
除了以上幾種VFS基礎對象外,內核還使用了另外一些標準數據結構來管理文件系統的其他相關數據。第一個結構體是file_system_type,用來描述各種特定文件系統類型,比如ext3。第二個結構體是vfsmount,用來描述一個安裝文件系統的實例。
因為Linux支持眾多不同的文件系統,所以內核必須由一個特殊的結構體來描述每種文件系統的功能和行為。file_system_type定義在linux/fs.h中
struct file_system_type {const char *name;int fs_flags;struct super_block *(*get_sb) (struct file_system_type *, int,const char *, void *);void (*kill_sb) (struct super_block *);struct module *owner;struct file_system_type * next;struct list_head fs_supers;
};
每種文件系統,不管有多少個實例安裝到系統中,還是根本就沒有安裝到奧系統中,都只有一個file_system_type結構。
當文件系統被實際安裝時,將有一個vfsmount結構體在安裝時被創建。該結構體用來代表文件系統的實例。
vfsmount結構被定義在linux/mount.h中
struct vfsmount
{struct list_head mnt_hash;struct vfsmount *mnt_parent; /* fs we are mounted on */struct dentry *mnt_mountpoint; /* dentry of mountpoint */struct dentry *mnt_root; /* root of the mounted tree */struct super_block *mnt_sb; /* pointer to superblock */struct list_head mnt_mounts; /* list of children, anchored here */struct list_head mnt_child; /* and going through their mnt_child */atomic_t mnt_count;int mnt_flags;int mnt_expiry_mark; /* true if marked for expiry */char *mnt_devname; /* Name of device e.g. /dev/dsk/hda1 */struct list_head mnt_list;struct list_head mnt_fslink; /* link in fs-specific expiry list */struct namespace *mnt_namespace; /* containing namespace */
};
10 和進程相關的數據結構
系統中的每一個進程都有自己的一組打開的文件,像根文件系統、當前工作目錄、安裝點等。有三個數據結構將VFS層和系統的進程緊緊聯系在一起,它們分別是:files_struct、fs_struct和namespace結構體。
files_struct結構體定義在文件linux/file.h中。該結構體由進程描述符中的files域指向。所有與每個進程相關的信息如打開的文件及文件描述符都包含在其中,其結構體描述如下:
struct files_struct {atomic_t count;spinlock_t file_lock; /* Protects all the below members. Nests inside tsk->alloc_lock */int max_fds;int max_fdset;int next_fd;struct file ** fd; /* current fd array */fd_set *close_on_exec;fd_set *open_fds;fd_set close_on_exec_init;fd_set open_fds_init;struct file * fd_array[NR_OPEN_DEFAULT];
};
fd數組指針指向已打開的文件對象鏈表,默認情況下,指向fd_array數組,因為NR_OPEN_DEFALUT等于32,所以該數組可以容納32個文件對象,如果一個進程鎖打開的文件對象超過32個,內核將分配一個新數組,并且將fd指針指向它。
fs_struct由進程描述符的fs域指向,它包含文件系統和進程相關的信息,定義在linux/fs_struct.h中。
struct fs_struct {atomic_t count;rwlock_t lock;int umask;struct dentry * root, * pwd, * altroot;struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};
該結構包含了當前進程的當前工作目錄(pwd)和根目錄(root)。
namespace定義在文件linux/namespace.h中,由進程描述符中的namespace域指向。2.4內核以后,單進程命名空間被加入到內核中,它使得每一個進程都在系統中都看到唯一的安裝系統文件
struct namespace {atomic_t count;struct vfsmount * root;struct list_head list;struct rw_semaphore sem;
};
list域是連接已安裝文件系統的雙向鏈表,它包含的元素組成了全體命名空間。默認情況下,所有的進程共享同樣的命名空間,只有在進行clone()操作時使用CLONE_NEWNS標志,才會給進程一個另外的命名空間結構體的拷貝。
11 Linux中的文件系統
Linux支持相當多種類的文件系統,從本地文件系統。如ext2和ext3,到網絡文件系統,如NFS和Coda、VFS層提供了給這些文件系統一個統一的實現框架,而且還提供了能和標準系統調用交換工作的同一接口。由于VFS層的存在,使得Linux實現新文件系統的工作變得簡單起來,它可以輕松地使這些文件系統通過Unix系統調用而協同工作。