虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。
VFS把各种不同的文件系统抽象 后采用统一的方式进行操作。为了支持多文件系统,VFS提供了一个通用文件系统模型,该模型囊括了任何文件系统的常用功能集和行为。其定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。
Unix 文件系统 Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装节点(mount point) 。
从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。在Unix中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间(Linux 将层次化概念引入到单个进程中,每个进程都指定一个唯一的命名空间。因为每个进程都会继承父进程的命名空间,所以所有进程往往都只有一个命名空间),所有已安装文件系统都作为根文件系统树的枝叶出现在系统中。
文件其实可以做一个有序字节串,第一个字节是文件的头,最后一个字节是文件的尾。每一个文件为了便于系统和用户识别,都被分配了一个便于理解的名字。
文件通过目录组织起来 。文件目录好比一个文件夹,用来容纳相关文件。目录可以嵌套(包含其他目录),形成文件路径。路径中每一部分都被称作目录条目,统称为目录项(“/tmp/log/system”,根目录/,目录tmp、log和文件system都是目录条目)。在Unix中,目录属于普通文件 。由于VFS把目录当做文件对待,所以可以对目录执行和文件相同的操作。
Unix系统将文件的相关信息 和文件本身 这两个概念加以区分,例如控制权限、大小、创建时间等信息。文件相关信息,也被称作文件的元数据,被存储在一个单独的数据结构——索引节点 (inode,index node的缩写)。
这些信息和文件系统的控制信息密切相关,文件系统的控制信息存储在超级块 ——一种包含文件系统信息的数据结构。有时,把这些收集起来的信息称为文件系统数据元 ,它集单独文件信息和文件系统的信息于一身。
VFS对象及其数据结构 VFS其实采用的是面向对象的设计思路,使用一组数据结构来代表通用文件对象。因为内核纯粹使用C代码实现,没有直接利用面向对象的语言,所以内核中的数据结构都使用C语言的结构体实现,而这些结构体包含数据的同时也包含操作这些数据的函数指针,其中的操作函数由具体文件系统实现。
VFS中有四个主要的对象类型,它们分别是:
超级块对象 ,它代表一个具体的已安装文件系统。索引节点对象 ,它代表一个具体文件。目录项对象 ,它代表一个目录项,是路径的一个组成部分。文件对象 ,它代表由进程打开的文件。注意,因为VFS将目录作为一个文件来处理,所以不存在目录对象。换句话说,目录项不同于目录,但目录却是另一种形式的文件。
每个主要对象中都包含一个操作对象 ,这些操作对象描述了内核针对主要对象可以使用的方法:
super_operations 对象,其中包括内核针对特定文件系统所能调用的方法,比如write_inode()和sysc_fs()等方法。inode_operations 对象,其中包括内核针对特定文件所能调用的方法,比如create()和link()等方法。dentry_operations 对象,其中包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()等方法。file_operations 对象,其中包括进程针对己打开文件所能调用的方法,比如read()和write()等方法。操作对象作为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。对于其中许多方法来说,可以继承使用VFS提供的通用函数,如果通用函数提供的基本功能无法满足需要,那么就必须使用实际文件系统的独有方法填充这些函数指针,使其指向文件系统实例。
再次提醒,我们这里所说的对象就是指结构体。
VFS使用了大量结构体对象,它所包括的对象远远多于上面提到的这几种主要对象。比如每个注册的文件系统都由file_system_type结构体来表示,它描述了文件系统及其性能;另外,每一个安装点也都用vfsmount结构体表示,它包含的是安装点的相关信息,如位置和安装标志等。
之后介绍两个与进程相关的结构体,它们描述了文件系统以及和进程相关的文件,分别是fs_struct结构体和file结构体。
超级块对象 各种文件系统都必须实现超级块对象,该对象用于存储文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。对于并非基于磁盘的文件系统(如基于内存的文件系统,比如sysfs),它们会在使用时创建超级块并将其保存到内存中。
超级块对象由super_block结构体表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 struct super_block { struct list_head s_list ; dev_t s_dev; unsigned char s_dirt; unsigned char s_blocksize_bits; unsigned long s_blocksize; loff_t s_maxbytes; struct file_system_type *s_type ; const struct super_operations *s_op ; const struct dquot_operations *dq_op ; const struct quotactl_ops *s_qcop ; const struct export_operations *s_export_op ; unsigned long s_flags; unsigned long s_magic; struct dentry *s_root ; struct rw_semaphore s_umount ; struct mutex s_lock ; int s_count; atomic_t s_active; #ifdef CONFIG_SECURITY void *s_security; #endif const struct xattr_handler **s_xattr ; struct list_head s_inodes ; struct hlist_head s_anon ; struct list_head s_files ; struct list_head s_dentry_lru ; int s_nr_dentry_unused; struct block_device *s_bdev ; struct backing_dev_info *s_bdi ; struct mtd_info *s_mtd ; struct list_head s_instances ; struct quota_info s_dquot ; int s_frozen; wait_queue_head_t s_wait_unfrozen; char s_id[32 ]; void *s_fs_info; fmode_t s_mode; u32 s_time_gran; struct mutex s_vfs_rename_mutex ; char *s_subtype; char *s_options; };
创建、管理和撤销超级块对象的代码位于文件<fs/super.c>中。超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并将其信息填充到内存中的超级块对象中。
超级块操作 超级块对象中最重要的一个域是s_op,它指向超级块的操作函数表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 struct super_operations { struct inode *(*alloc_inode )(struct super_block *sb ); void (*destroy_inode)(struct inode *); void (*dirty_inode) (struct inode *); int (*write_inode) (struct inode *, struct writeback_control *wbc); 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); int (*freeze_fs) (struct super_block *); int (*unfreeze_fs) (struct super_block *); int (*statfs) (struct dentry *, 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 *); int (*show_stats)(struct seq_file *, struct vfsmount *); #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 ); };
该结构体中的每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的底层操作 。其中的函数都是由VFS在进程上下文调用。除了dirty_inode()其他函数在必要时都可以阻塞。
索引节点对象 索引节点对象包含了内核在操作文件或目录时需要的全部信息 。对于Unix风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。没有索引节点的文件系统通常将文件的描述信息作为文件的一部分来存放。这些文件系统与Unix风格的文件系统不同,没有将数据与控制信息分开存放。有些现代文件系统使用数据库来存储文件的数据。不管哪种情况、采用哪种方式,索引节点对象必须在内存中创建,以便于文件系统使用。
索引节点对象由inode结构体表示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 struct inode { struct hlist_node i_hash ; struct list_head i_list ; struct list_head i_sb_list ; struct list_head i_dentry ; unsigned long i_ino; atomic_t i_count; unsigned int i_nlink; uid_t i_uid; gid_t i_gid; dev_t i_rdev; unsigned int i_blkbits; u64 i_version; loff_t i_size; #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif struct timespec i_atime ; struct timespec i_mtime ; struct timespec i_ctime ; blkcnt_t i_blocks; unsigned short i_bytes; umode_t i_mode; spinlock_t i_lock; struct mutex i_mutex ; struct rw_semaphore i_alloc_sem ; const struct inode_operations *i_op ; const struct file_operations *i_fop ; struct super_block *i_sb ; struct file_lock *i_flock ; struct address_space *i_mapping ; struct address_space i_data ; #ifdef CONFIG_QUOTA struct dquot *i_dquot [MAXQUOTAS ]; #endif struct list_head i_devices ; union { struct pipe_inode_info *i_pipe ; struct block_device *i_bdev ; struct cdev *i_cdev ; }; __u32 i_generation; #ifdef CONFIG_FSNOTIFY __u32 i_fsnotify_mask; struct hlist_head i_fsnotify_mark_entries ; #endif #ifdef CONFIG_INOTIFY struct list_head inotify_watches ; struct mutex inotify_mutex ; #endif unsigned long i_state; unsigned long dirtied_when; unsigned int i_flags; atomic_t i_writecount; #ifdef CONFIG_SECURITY void *i_security; #endif #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *i_acl ; struct posix_acl *i_default_acl ; #endif void *i_private; };
一个索引节点代表文件系统中(索引节点仅当文件被访问时,才在内存中创建)的一个文件,它也可以是设备或管道这样的特殊文件。因此索引节点结构体中有一些和特殊文件相关的项,比如i_pipe项就指向一个代表有名管道的数据结构,i_bdev指向块设备结构体,i_cdev指向字符设备结构体。这三个指针被存放在一个公用体中,因为一个给定的索引节点每次只能表示三者之一(或三者均不)。
有时,某些文件系统可能并不能完整地包含索引节点结构体要求的所有信息。举个例子,有的文件系统可能并不记录文件的访问时间,这时,该文件系统就可以在实现中选择任意合适的办法来解决这个问题。它可以在i_atime中存储0,或者让i_atime等于i_mtime,或者只在内存中更新i_atime而不将其写回磁盘,或者由文件系统的实现者来决定。
索引节点操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 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 ); void * (*follow_link) (struct dentry *, struct nameidata *); void (*put_link) (struct dentry *, struct nameidata *, void *); void (*truncate) (struct inode *); int (*permission) (struct inode *, int ); int (*check_acl)(struct inode *, 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 *); void (*truncate_range)(struct inode *, loff_t , loff_t ); long (*fallocate)(struct inode *inode, int mode, loff_t offset, loff_t len); int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start, u64 len); };
目录项对象 VFS把目录当作文件对待,所以在路径/bin/vi中,bin和vi都属于文件——bin是特殊的目录文件而vi是一个普通文件,路径中的每个组成部分都由一个索引节点对象表示 。虽然它们可以统一由索引节点表示,但是VFS经常需要执行目录相关的操作,比如路径名查找等。路径名查找需要解析路径中的每一个组成部分,不但要确保它有效,而且还需要再进一步寻找路径中的下一个部分。
为了方便查找操作,VFS引入了目录项的概念 。每个dentry代表路径中的一个特定部分。对前一个例子来说,/、bin和vi,都属于目录项对象。前两个是目录,最后一个是普通文件。必须明确一点:在路径中(包括普通文件在内),每一个部分都是目录项对象。解析一个路径并遍历其分量绝非简单的演练,它是耗时的、常规的字符串比较过程,执行耗时、代码繁琐。目录项对象的引入使得这个过程更加简单。
目录项也可包括安装点。在路径/mnt/cdorm/foo中,构成元素/、mnt、cdorm和foo都属于目录项对象。VFS在执行目录操作时(如果需要的话)会现场创建目录项对象。
目录项对象由dentry结构体表示,定义在文件<linux/dcache.h>中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 struct dentry { atomic_t d_count; unsigned int d_flags; spinlock_t d_lock; int d_mounted; struct inode *d_inode ; struct hlist_node d_hash ; struct dentry *d_parent ; struct qstr d_name ; struct list_head d_lru ; union { struct list_head d_child ; struct rcu_head d_rcu ; } d_u; struct list_head d_subdirs ; struct list_head d_alias ; unsigned long d_time; const struct dentry_operations *d_op ; struct super_block *d_sb ; void *d_fsdata; unsigned char d_iname[DNAME_INLINE_LEN_MIN]; };
与前面的两个对象不同,目录项对象没有对应的磁盘数据结构,VFS根据字符串形式的路径名现场创建它。而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是非被修改的标志。
目录项状态 目录项对象有三种有效状态:被使用、未被使用和负状态 。
一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点)并且表明该对象存在一个或多个使用者(即d_count为正值)。一个目录项处于被使用状态,意味着它正被VFS使用并且指向有效的数据,因此不能被丢弃。
一个未被使用的目录项对应一个有效的索引节点,但是应指明VFS当前并未使用它(d_count为0)。该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时再使用它。由于该目录项不会过早地被撤销,所以以后再需要它时,不必重新创建,与未缓存的目录项相比,这样使路径查找更迅速。但如果要回收内存的话,可以撤销未使用的目录项。
一个负状态的目录项(无效目录项)。没有对应的有效索引节点(d_inode为NULL),因为索引节点已被删除了,或路径不再正确了,但是目录项仍然保留,以便快速解析以后的路径查询。
目录项对象释放后也可以保存到slab对象缓存中去。此时,任何VFS或文件系统代码都没有指向该目录项对象的有效引用。
目录项缓存 如果VFS层遍历路径名中所有的元素并将它们逐个地解析成目录项对象,还要到达最深层目录,将是一件非常费力的工作,会浪费大量的时间。所以内核将目录项对象缓存在目录项缓存(简称dcache )中。
目录项缓存包括两个主要部分:
“被使用的”目录项链表。该链表通过索引节点对象中的i_dentry项连接相关的索引节点,因为一个给定的索引节点可能有多个链接,所以就可能有多个目录项对象,因此用一个链表来连接它们。 “最近被使用的”双向链表。该链表含有未被使用的和负状态的日录项对象。由于该链总是在头部插入目录项,所以链头节点的数据总比链尾的数据要新。当内核必须通过删除节点项回收内存时,会从链尾删除节点项,因为尾部的节点最旧,所以它们在近期内再次被使用的可能性最小。 散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。
散列表由数组dentry_hashtable表示,其中每一个元素都是一个指向具有相同键值的目录项对象链表的指针。数组的大小取决于系统中物理内存的大小。
实际的散列值由d_hash()函数计算,它是内核提供给文件系统的唯一的一个散列函数。
查找散列表要通过d_lookup()函数,如果该函数在dcache中发现了与其相匹配的目录项对象,则匹配的对象被返回;否则,返回NULL。
而dcache在一定意义上也提供对索引节点的缓存 ,也就是icache 。和目录项对象相关的索引节点对象不会被释放,因为目录项会让相关索引节点的使用计数为正,这样就可以确保索引节点留在内存中。只要目录项被缓存,其相应的索引节点也就被缓存了。
因为文件访问呈现空间和时间的局部性,所以对目录项和索引节点进行缓存非常有益。文件访问有时间上的局部性,是因为程序可能会一次又一次地访问相同的文件。因此,当一个文件被访问时,所缓存的相关目录项和索引节点不久被命中的概率较高。文件访问具有空间的局部性是因为程序可能在同一个目录下访问多个文件,因此一个文件对应的目录项缓存后极有可能被命中,因为相关的文件可能在下次又被使用。
目录项操作 dentry_operations结构体指明了VFS操作目录项的所有方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 *); char *(*d_dname)(struct dentry *, char *, int ); };
文件对象 VFS的最后一个主要对象是文件对象。文件对象表示进程已打开的文件 。如果站在用户角度来看待VFS,文件对象会首先进人我们的视野。进程直接处理的是文件,而不是超级块、索引节点或目录项。
文件对象是已打开的文件在内存中的表示。该对象(不是物理文件)由相应的open()系统调用创建,由close()系统调用撤销,所有这些文件相关的调用实际上都是文件操作表中定义的方法。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已打开文件,它反过来指向目录项对象(反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。
文件对象由fi1e结构体表示,定义在文件<linux/fs.h中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 struct file { union { struct list_head fu_list ; struct rcu_head fu_rcuhead ; } f_u; struct path f_path ; #define f_dentry f_path.dentry #define f_vfsmnt f_path.mnt const struct file_operations *f_op ; spinlock_t f_lock; atomic_long_t f_count; unsigned int f_flags; fmode_t f_mode; loff_t f_pos; struct fown_struct f_owner ; const struct cred *f_cred ; struct file_ra_state f_ra ; u64 f_version; #ifdef CONFIG_SECURITY void *f_security; #endif void *private_data; #ifdef CONFIG_EPOLL struct list_head f_ep_links ; #endif struct address_space *f_mapping ; #ifdef CONFIG_DEBUG_WRITECOUNT unsigned long f_mnt_write_state; #endif };
文件对象也没有对应的磁盘数据,所以在结构体中没有代表其对象是否被修改、是否需要写会磁盘的标志。文件对象通过f_dentry指向相关的目录项对象,目录项对象会指向相关的索引节点,索引节点会记录文件是否被修改。
文件操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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 ); 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 ); 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 *, 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 **); };
和文件系统相关的数据结构 内核还使用了另外一些标准数据结构来管理文件系统的其他相关数据。第一个对象是file_system_type,用来描述各种特定文件系统类型,比如ext3、ext4或UDF。第二个结构体是vfsmount,用来描述一个安装文件系统的实例。
因为Linux支持众多不同的文件系统,所以内核必须由一个特殊的结构来描述每种文件系统的功能和行为。file_system_type结构体被定义在<linux/fs.h>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct file_system_type { const char *name; int fs_flags; int (*get_sb) (struct file_system_type *, int , const char *, void *, struct vfsmount *); void (*kill_sb) (struct super_block *); struct module *owner ; struct file_system_type * next ; struct list_head fs_supers ; struct lock_class_key s_lock_key ; struct lock_class_key s_umount_key ; struct lock_class_key s_vfs_rename_key ; struct lock_class_key i_lock_key ; struct lock_class_key i_mutex_key ; struct lock_class_key i_mutex_dir_key ; struct lock_class_key i_alloc_sem_key ; };
当文件系统被实际安装时,将有有个vfsmount结构体在安装点被创建。该结构体用来代表文件系统的的实例——代表一个安装点。
vfsmount结构被定义在<linux/mount.h>中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 struct vfsmount { struct list_head mnt_hash ; struct vfsmount *mnt_parent ; struct dentry *mnt_mountpoint ; struct dentry *mnt_root ; struct super_block *mnt_sb ; struct list_head mnt_mounts ; struct list_head mnt_child ; int mnt_flags; const char *mnt_devname; struct list_head mnt_list ; struct list_head mnt_expire ; struct list_head mnt_share ; struct list_head mnt_slave_list ; struct list_head mnt_slave ; struct vfsmount *mnt_master ; struct mnt_namespace *mnt_ns ; int mnt_id; int mnt_group_id; atomic_t mnt_count; int mnt_expiry_mark; int mnt_pinned; int mnt_ghosts; #ifdef CONFIG_SMP int __percpu *mnt_writers; #else int mnt_writers; #endif };
理清文件系统和所有其他安装点间的关系,是维护所有安装点链表中最复杂的工作。所以,vfsmount结构体中维护的各种链表就是为了能够跟踪这些关联信息。
vfsmount结构还保存了在安装时指定的标志信息,该信息存储在mnt_flages域中。
标志 描述 MNT_NOSUID 禁止该文件系统的可执行文件设置setuid和setgid标志 MNT_MODEV 禁止访问该文件系统上的设备文件 MNT_NOEXEC 禁止执行该文件系统上的可执行文件
和进程相关的数据结构 系统中的每一个进程都有自己的一组打开的文件。有三个数据结构将VFS层和系统的进程紧密联系在一起,它们分别是:file_struct 、fs_struct 和namespace结构体。
file_struct结构体定义在<linux/fdtable.h>中。该结构体由进程描述符中的files目录项指向。所有与单个进程(pre-process)相关的信息 (如打开的文件及文件描述符)都包含在其中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct files_struct { atomic_t count; struct fdtable *fdt ; struct fdtable fdtab ; spinlock_t file_lock ____cacheline_aligned_in_smp; int next_fd; struct embedded_fd_set close_on_exec_init ; struct embedded_fd_set open_fds_init ; struct file * fd_array [NR_OPEN_DEFAULT ]; };
fd_array数组指针指向已打开的文件对象 。因为NR_OPEN_DEFAULT等于BITS_PER_LONG,在64位机器体系结构中这个宏的值为64,所以该数组可以容纳64个文件对象。如果一个进程所打开的文件对象超过64个,内核将分配一个新数组,井且将fdt指针指向它。所以对适当数量的文件对象的访问会执行得很快,因为它是对静态数组进行的操作:如果一个进程打开的文件数量过多,那么内核就需要建立新数组。所以如果系统中有大量的进程都要打开超过64个文件,为了优化性能,管理员可以适当增大NR_OPEN_DEFAULT的预定义值。
和进程相关的第二个结构体是fs_struct。结构由进程描述符的fs域指向。它包含文件系统和进程相关的信息 ,定义在文件<linux/fs_struct.h>中
1 2 3 4 5 6 7 struct fs_struct { int users; rwlock_t lock; int umask; int in_exec; struct path root , pwd ; };
最后一个相关结构体是namespace结构体,它定义在文件<linux/mnt_namespace>中,由进程描述符中的mnt_namespace域指向(现在应该是nsproxy,待确定)。
1 2 3 4 5 6 7 struct mnt_namespace { atomic_t count; struct vfsmount * root ; struct list_head list ; wait_queue_head_t poll; int event; };
list域是连接已安装文件系统的双向链表,它包含的元素组成了全体命名空间。
上述这些数据结构都是通过进程描述符连接起来的。对多数进程来说,它们的描述符都指向唯一的file_struct和fs_struct结构体。但是,对于那些使用克隆标志CLONE_FILLS或CLOINE_FS创建的进程,会共享这两个结构体。
namespace结构体的使用方法却和前两种结构体完全不同,默认情况下,所有的进程共享同样的命名空间(也就是,它们都从相同的挂载表中看到同一个文件系统层次结构)。只有在进行clone()操作时使用CLONE_NEWS标志,才会给进程一个唯一的命名空间结构体的拷贝。因为大多数进程不提供这个标志,所有进程都继承其父进程的命名空间。因此,在大多数系统上只有一个命名空间,不过,CLONE_NEWS标志可以使这一功能失效。
文件系统数据结构体总结 超级块是对一个文件系统的描述;索引节点是对一个文件物理属性 的描述;而目录项是对一个文件逻辑属性 的描述。除此之外,文件与进程之间的关系是由另外的数据结构来描述的。一个进程所处的位置是由fs_struct来描述的,而一个进程(或用户)打开的文件是由files_struct来描述的,而整个系统所打开的文件是由file结构来描述。
每个文件除了有一个索引节点inode数据结构外,还有一个目录项dentry数据结构。dentry 结构中有个d_inode指针指向相应的inode结构。
dentry结构代表的是逻辑意义上的文件,所描述的是文件逻辑上的属性,因此,目录项对象在磁盘上并没有对应的映像;而inode结构代表的是物理意义上的文件,记录的是物理上的属性,一个索引节点对象可能对应多个目录项对象。
各个结构关系图如下:
参考:
超级块对象、索引节点对象、文件对象及目录项对象的数据结构
文件系统中的对象总结及对目录项对象的重点理解