對於一個作業系統來說,最主要的兩個模組就是檔案系統和進程管理。在Linux中,檔案具有廣泛的含義,凡是能夠產生或者消耗資訊的都是檔案。Linux除了支援自身的Ext2檔案系統外,還支援其他各種不同的檔案系統。那麼這一目的是如何?的呢?很容易想到的思路是,在各種不同的檔案系統之上增加一個中介層,用於隔離各個具體檔案系統的差異,為上層使用者提供一個統一的介面。實際上,Linux正是這麼做的,它在具體的檔案系統上引入了一個統一的、抽象的、虛擬檔案系統介面,即所謂的VFS(Virtual Filesystem
System,虛擬檔案系統),來隱藏具體檔案系統的實現細節。這個介面主要由一些抽象的、標準的檔案操作組成,並以系統調用的形式提供給使用者,如read(),wirte(),lseek()。VFS與具體的檔案系統之間的介面是明確定義的,其核心是一個叫做file_operations的資料結構,其成員幾乎全為函數指標,每種檔案系統都有自己的file_operations資料結構,其中儲存了該檔案系統對VFS中的各個介面的實現代碼,如果具體的檔案系統不支援某種操作,其file_operations結構中的相應函數指標就為NULL。file_operations的定義如下:
/** NOTE:* read, write, poll, fsync, readv, writev can be called* without the big kernel lock held in all filesystems.*/struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char *, size_t, loff_t *);ssize_t (*write) (struct file *, const char *, 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 (*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 *);};Linux中VFS和具體檔案系統的關係可以用來說明:當進程通過open()系統調用開啟檔案時,該進程就與具體的檔案建立起了聯絡,一個“已開啟的檔案”在系統中表現為一個file資料結構,其定義如下:
struct file {struct list_head f_list;struct dentry *f_dentry;struct vfsmount *f_vfsmnt;struct file_operations *f_op; //這裡是關鍵,指向該檔案所屬的檔案系統的file_operations結構atomic_t f_count;unsigned int f_flags;mode_t f_mode;loff_t f_pos;unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;struct fown_struct f_owner;unsigned int f_uid, f_gid;int f_error;unsigned long f_version;/* needed for tty driver, and maybe others */void *private_data;};當對檔案進行讀寫等操作時,通過f_op指標調用該檔案所屬的檔案系統(比如Ext2,FAT32,NTFS等)提供的檔案操作函數即可。同一個檔案可以被多個進程開啟,一個進程也可以開啟多個檔案,進程與該進程開啟的檔案(file結構)之間的這種關係在系統是如何來描述的呢?答案在task_struct資料結構中,Linux將進程開啟的檔案作為進程的一項資源記錄在進程的task_struct資料結構中:
struct task_struct {//省略部分/* filesystem information */struct fs_struct *fs;/* open file information */struct files_struct *files;//省略部分};其中fs指向一個fs_struct結構,描述了該與該進程有關的檔案系統的資訊,比如進程的當前工作目錄、進程的根目錄、進程替換目錄等;而files指向一個files_struct結構,描述了該進程開啟的檔案的資訊。我們來看一下files_struct的定義:
/** Open file table structure*/struct files_struct {atomic_t count;rwlock_t file_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]; //注意這裡};前面說過,已開啟的檔案在系統中表現為一個file結構,files_struct結構中的fd_array指標數組記錄了該進程開啟的檔案的file結構。當開啟一個檔案後,我們就可以通過read、write等系統調用來對該檔案進行操作了,在調用read等系統調用時,會傳遞一個所謂的“檔案描述符”的參數,以read為例,read()系統調用的原型如下:
ssize_t read(int fd, void *buf, size_t count);其中的參數fd即為檔案描述符,那麼這個檔案描述符到底是什麼東東呢,為啥根據這個fd參數就可以確定要操作的檔案呢?關鍵就在於fd_array這個數組,系統通過fd_array[fd]指標即可確定要操作的檔案,例如fd_arrar[0]指向的file結構代表就是標準輸入裝置。通過file結構中的f_op,我們就可以得到操作該檔案的代碼,但是如何得到該檔案的內容呢?要找到答案,還是要回到file結構的定義,在file結構中,有一個f_dentry成員,指向一個叫做dentry的結構。dentry中記錄著檔案的各種屬性,比如檔案名稱、存取權限等,其定義如下:
struct dentry {atomic_t d_count;unsigned int d_flags;struct inode * d_inode; /* Where the name belongs to - NULL is negative */ //注意這裡struct dentry * d_parent; /* parent directory */struct list_head d_vfsmnt;struct list_head d_hash; /* lookup hash list */struct list_head d_lru; /* d_count = 0 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 */struct qstr d_name;unsigned long d_time; /* used by d_revalidate */struct dentry_operations *d_op; //注意這裡struct super_block * d_sb; /* The root of the dentry tree */unsigned long d_reftime; /* last time referenced */void * d_fsdata; /* fs-specific data */unsigned char d_iname[DNAME_INLINE_LEN]; /* small names */};其中d_name記錄著檔案名稱,d_flags記錄著檔案的存取權限等資訊。這裡我們需要注意的是d_inode這個成員,該成員是一個指標,指向inode結構。inode結構中提供了檔案的資料在儲存介質上的位置和分布資訊,從這裡可以看出,dentry和inode反應了檔案的兩個方面,即dentry和inode從兩個不同的角度來描述檔案,其中dentry所代表的是邏輯上的檔案,只關注檔案的屬性,而不關注其儲存,而inode多代表的是物理意義上的檔案,記錄的是其物理上的屬性,它們之間是多對一的關係(即同一個物理檔案可以有多個檔案名稱)。dentry中還有一個d_op成員,指向dentry_operations結構,與file_operations相似,該結構也是為了隔離具體檔案系統對目錄的操作差別而引入VFS的,每個具體的檔案系統都會提供自己的dentry_operations。定義如下:
struct dentry_operations {int (*d_revalidate)(struct dentry *, int);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 *);};其中d_delete用於“刪除檔案”,d_release用於“關閉檔案”,d_compare用於檔案名稱的比較。其實,,VFS和具體檔案系統的介面除了file_operations和dentry_operations外,還包括一些其他的資料結構,其中最主要的是inode_operations,該結構定義了操作inode的介面。至此,我們對Linux檔案系統有了一個基本的瞭解,很好地說明了反應了Linux檔案系統的內部結構: