1、Linux 虚拟文件系统虚拟文件系统,简称 VFS。系统中所有文件系统不但依赖 VFS 共存,而且也依靠 VFS系统协同工作。程序可以利用标准的 Unix 系统调用对不同的文件系统,甚至不同介质上的文件系统进行读写操作。 1.通用文件系统接口 VFS 使得用户可以直接使用 open(),read()和 write()这样的系统调用而无需考虑具体文件系统和实际物理介质,这点 DOS 是不具备的,DOS 中任何对非本地文件系统的访问都要依靠特殊工具才能完成。 VFS 与块 I/O 相结合,提供抽象、接口以及交融,使得用户空间的程序调用统一的系统调用访问各种文件,不管文件系统是什么,也不管文件系统位
2、于何种介质,采用命名策略是统一的。 2.文件系统抽象层 VFS 可以为应用提供通用接口操作各类文件系统,是因为内核在底层文件系统接口上建立了一个抽象层。这个抽象系统模型,包括了任何文件系统的常用功能集和行为。当然,该模型偏重于 Unix 风格的文件系统。 内核通过抽象层能够方便、简单地支持各种类型的文件系统,实际文件系统通过编程提供 VFS 所期望的抽象接口和数据结构。比如用户空间的一个写操作: ret = write(fd, buf, len); write 系统调用将 buf 指针指向的长度为 len 字节的数据写入文件描述符 fd 对应的 文件的当前位置。这个系统调用首先调用 sys_w
3、rite()处理, sys_write()函数要找到 fd 所在的文件系统实际给出的是哪个写操作,然后再 执行该操作。具体文件系统的写方法是文件系统实现的一部分,数据最终写入介质。3.Unix 文件系统 Unix 使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(mount point)。 从本质上讲文件系统是特殊的数据分层存储结构,包含文件、目录和相关控制信息。 文件系统通用操作包含创建、删除和安装等。在 Unix 中,文件系统采用全局命名空间,挂载到系统根目录下的一个子节点。 Unix 文件概念和面向记录的文件系统形(比如 OpenVMS 的 File-11)成鲜明对
4、比,面向记录的文件系统提供更丰富、更结构化的表示,而简单的面向字节流抽象的 Unix 文件则以简单性和相当的灵活性为代价。 Unix 中,目录属于普通文件,它列出包含在其中的所有文件,由于 VFS 把目录当作文件对待,所以可以对目录执行和文件相同的操作。 Unix 系统将文件的信息和文件本身这两个概念加以区分,例如访问控制权限、大小、拥有者、创建时间等。文件相关信息称作文件的元数据,被存储在一个单独的数据结构中,叫做索引节点(inode) 。索引节点存储在超级块中。超级块是一种包含文件系统信息的数据结构。 Linux 的 VFS 设计目标就是要保证能与支持或实现了这些概念的文件系统协同工作。
5、非 Unixf 风格的文件系统(比如 FAT 或 NTFS) ,要在 Linux 上工作,必须经过封装,提供一个符合这些概念的界面。这个封装转换需要在使用现场引入一些特殊处理,这样文件系统仍然能工作,但其带来的开销是很大的。4.VFS 对象及其数据结构 VFS 其实采用的是面向对象的设计,内核是纯 C 开发,用结构体来实现对象,结构体包含数据和操作这些数据的函数指针。 VFS 有四个主要对象类型,分别是: 超级块对象:代表一个具体已安装文件系统 索引节点对象:代表一个具体文件 目录项对象:代表一个目录项,是路径的一个组成部分 文件对象:它代表由进程打开的文件 注意,这里的目录项代表的是路径的一
6、个组成部分,可能包含一个普通文件。这不同于目录。Unix 中,目录仅是一个普通文件。 每个主要对象中,都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法: super_operations 对象,包含针对特定文件系统调用方法,比如 write_inode(),sync_fs等 inode_operations 对象,包含 create(),link() 等; dentry_operations 对象,包含 d_compare(),d_delete()等 file_operations 对象,包含进程针对已打开文件所能调用的方法,如 read()和 write()等 操作对象作
7、为一个结构体指针来实现,此结构体中包含指向操作其父对象的函数指针。通常来说,可以继承使用 VFS 通用函数,如果通用函数提供的基本功能无法满足需要,那就必须使用实际文件系统独有方法填充这些函数指针,使其指向文件系统实例。 VFS 使用了大量结构体对象,还包括 file_system_type 描述文件系统及其性能,vfsmount 包含安装点相关信息等。 5.超级块对象 各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或者文件系统控制块(超级块对象) 。对于非基于磁盘的文件系统(比如基于内存的文件系统 sysfs) ,它们会在使用
8、现场创建超级块并将其保存到内存中。 超级块对象由 super_block 结构体表示,定义在中: struct super_block struct list_head s_list; /* Keep this first */指向所有超级快的链表dev_t s_dev; /* search index; _not_ kdev_t */设备标识符unsigned char s_dirt; /修改( 脏)标志unsigned char s_blocksize_bits; /块大小,单位 bitunsigned long s_blocksize; /块大小,单位字节loff_t s_maxbyte
9、s; /* Max file size */文件大小上限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; /文
10、件系统幻数struct dentry *s_root; /目录挂载点struct rw_semaphore s_umount; /卸载信号量struct mutex s_lock; /超级块信号量int s_count; /超级块引用计数int s_need_sync; /尚未同步标志atomic_t s_active; /活动引用计数#ifdef CONFIG_SECURITYvoid *s_security; /活动模块#endifstruct xattr_handler *s_xattr; /扩展属性操作struct list_head s_inodes; /* all inodes *
11、/inodes 链表struct hlist_head s_anon; /* anonymous dentries for (nfs) exporting */匿名目录项struct list_head s_files; /被分配文件链表/* s_dentry_lru and s_nr_dentry_unused are protected by dcache_lock */struct list_head s_dentry_lru; /* unused dentry lru */未被使用目录项链表int s_nr_dentry_unused; /* # of dentry on lru */
12、链表中目录项的数目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; /* Diskquota specific options */限额相关选项int s_frozen; /frozen 标志位wait_queue_head_t s_wait_unfrozen; /冻结的等待队列char s_id32; /* Informa
13、tional name */文本名字void *s_fs_info; /* Filesystem private info */文件系统特殊信息fmode_t s_mode; /安装权限/* Granularity of c/m/atime in ns.Cannot be worse than a second */u32 s_time_gran; /时间戳粒度/* The next field is for VFS *only*. No filesystems have any business* even looking at it. You had been warned.*/struc
14、t mutex s_vfs_rename_mutex; /* Kludge */重命名信号量/* Filesystem subtype. If non-empty the filesystem type field* in /proc/mounts will be “type.subtype“*/char *s_subtype;/子类型名称/* Saved mount options for lazy filesystems using* generic_show_options()*/char *s_options; /已存安装选项; 创建、管理和撤销超级块对象的代码位于文件 fs/supe
15、r.c 中,超级块对象通过alloc_super()函数创建并初始化。在文件系统安装时,文件系统会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。 6.超级块操作 超级块对象中最重要的一个域是 s_op, 它指向超级块的操作函数表,操作函数表在定义 该结构体每一项都是一个指向超级块操作函数的指针,超级块操作函数执行文件系统和索引节点的底层操作。 当文件系统需要对其超级块执行操作时,首先要在超级块对象中找到合适的方法,比如一个文件系要写自己的超级块,需要调用 sb-s_op-write_super(sb); 由于 C 语言无法直接得到操作函数的父对象,所以必须将父
16、对象以参数显示传给操作函数。 struct super_operations /在给定的超级块下创建和初始化一个新的索引节点对象struct inode *(*alloc_inode)(struct super_block *sb);/释放给定的索引节点void (*destroy_inode)(struct inode *);/VFS 在索引节点脏( 被修改) 时会调用此函数/日志文件系统,(比如 ext3/etx4)执行该函数进行日志更新void (*dirty_inode) (struct inode *);/用于将给定的索引节点写入磁盘,wait 参数指明写操作是否需同步int (*w
17、rite_inode) (struct inode *, struct writeback_control *wbc);/在最后一个指向索引节点的引用被释放后,VFS 会调用该函数,VFS 只需要简单地删除这个索引节点后,Unix 文件系统就不会定义这个函数了void (*drop_inode) (struct inode *);void (*delete_inode) (struct inode *);/从磁盘删除给定的索引节点void (*put_super) (struct super_block *);/卸载文件系统时被 VFS 调用,用来释放超级块,调用者必须持有 s_lock 锁/
18、用给定的超级块更新磁盘上的超级块,VFS 通过该函数对内存中的超级块和磁盘中的超级块进行同步,调用者必须持有 s_lock 锁void (*write_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 *);int (*statfs) (struct dent
19、ry *, struct kstatfs *);/获取文件系统状态/当执行新的安装选项重新安装文件系统时,VFS 会调用,必须持有 s_lock 锁int (*remount_fs) (struct super_block *, int *, char *);/VFS 调用,释放索引节点,并清空包含相关数据所有页面void (*clear_inode) (struct inode *);/VFS 调用中断安装操作,该函数被网络文件系统使用,如 NFS。void (*umount_begin) (struct super_block *);int (*show_options)(struct s
20、eq_file *, struct vfsmount *);int (*show_stats)(struct seq_file *, struct vfsmount *);#ifdef CONFIG_QUOTAssize_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);#endifint (*bdev_try_to_free_page)(struct
21、super_block*, struct page*, gfp_t); 以上函数都是由 VFS 在进程上下文调用,除了 dirty_inode(),其他函数可以阻塞。 其中一些函数是可选的,不需要的函数设为 NULL,如果 VFS 发现操作函数指针是NULL,它调用通用函数指向相应操作,或者什么都不做。 7.索引节点对象 索引节点对象,包含了内核在操作文件或目录时需要的全部信息。 对于 Unix 风格的文件系统来说,这些信息可以从磁盘索引节点直接读入。有的文件系统没有索引节点,没有将数据和控制信息分开存放。不管哪种情况、采用哪种方式,索引节点对象必须在内存中创建,以便于文件系统调用。 索引节点
22、对象 inode 结构,在 中定义 struct inode struct hlist_node i_hash; /散列表/* backing dev IO list */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; /使用者 idgid_t i_gid; /使用组 id
23、dev_t i_rdev; /实际设备标志符unsigned int i_blkbits; /以位单位文件大小u64 i_version; /版本号loff_t i_size; /以字节单位文件大小#ifdef _NEED_I_SIZE_ORDEREDseqcount_t i_size_seqcount; /以 i_size 进行串行计数#endifstruct timespec i_atime; /最后访问时间struct timespec i_mtime; /最后修改时间struct timespec i_ctime; /最后改变时间blkcnt_t i_blocks; /文件的块数uns
24、igned short i_bytes; /使用的字节数umode_t i_mode; /访问权限/* i_blocks, i_bytes, maybe i_size */spinlock_t i_lock; /自旋锁struct mutex i_mutex; /互斥锁struct rw_semaphore i_alloc_sem; /嵌入 i_sem 内部const struct inode_operations *i_op;/索引节点操作表/* former -i_op-default_file_ops */const struct file_operations *i_fop;/缺省的索
25、引节点操作表struct super_block *i_sb; /相关的超级块struct file_lock *i_flock;struct address_space *i_mapping;/相关的地址映射struct address_space i_data; /设备地址映射#ifdef CONFIG_QUOTAstruct dquot *i_dquotMAXQUOTAS;#endifstruct list_head i_devices;union struct pipe_inode_info *i_pipe; /管道信息struct block_device *i_bdev; /块设备
26、驱动struct cdev *i_cdev; /字符设备驱动;_u32 i_generation;#ifdef CONFIG_FSNOTIFY_u32 i_fsnotify_mask; /* all events this inode cares about */struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */#endif#ifdef CONFIG_INOTIFYstruct list_head inotify_watches; /* watches on this inode */struct mut
27、ex inotify_mutex; /* protects the watches list */#endifunsigned long i_state;unsigned long dirtied_when; /* jiffies of first dirtying */unsigned int i_flags; /文件系统标志atomic_t i_writecount; /写者技数#ifdef CONFIG_SECURITYvoid *i_security; /安全模块#endif#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl *i_acl;struct
28、 posix_acl *i_default_acl;#endif/* fs or device private pointer */void *i_private;/fs 私有指针; 一个索引节点代表文件系统中的一个文件(索引节点仅当文件被访问时,才在内存中创建) ,也可以是设备文件或管道文件这样的特殊文件。 8.索引节点操作 索引节点对象的 inode_operations 项非常重要,因为它描述了操作索引节点的所有方法,这些方法由文件系统实现。在定义 struct inode_operations /VFS 通过系统调用 create()和 open()来调用该函数,从而为 dentry
29、对象创建/一个新的索引节点int (*create) (struct inode *,struct dentry *,int, struct nameidata *);/在特定的目录中寻找索引节点,该索引节点要对应于 denrty 中给定的文件名struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *);/被系统调用 link()调用,用来创建硬链接/硬链接名称由 dentry 参数指定,链接对象是 dir 目录中 old_dentry 目录项所代表的文件int (*link) (struct d
30、entry *,struct inode *,struct dentry *);/被系统调用 ulink()调用,从目录 dir 中删除由目录项 dentry 指定的索引节点对象int (*unlink) (struct inode *,struct dentry *);/被 symlik()调用,创建符号链接int (*symlink) (struct inode *,struct dentry *,const char *);/被 mknod()调用,创建新目录int (*mkdir) (struct inode *,struct dentry *,int);/被 rmdir()调用,删除
31、 dir 目录中的 dentry 目录项代表的文件int (*rmdir) (struct inode *,struct dentry *);/被 mknod()调用,船舰特殊文件 (设备文件,管道,套接字等 )int (*mknod) (struct inode *,struct dentry *,int,dev_t);/移动文件int (*rename) (struct inode *, struct dentry *,struct inode *, struct dentry *);/readlink()调用,拷贝数据到特定的缓冲 buffer 中?/拷贝的数据来自 dentry 指定的
32、符号链接,拷贝大小最大可达 buflen 字节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_ac
33、l)(struct inode *, int);/被 notify_change()调用,在修改索引节点后,通知发送了“ 改变事件“int (*setattr) (struct dentry *, struct iattr *);/在通知索引节点需要从磁盘中更新时,VFS 会调用int (*getattr) (struct vfsmount *mnt, struct dentry *, struct kstat *);/VFS 调用,给 dentry 指定的文件设置扩展属性int (*setxattr) (struct dentry *, const char *,const void *,s
34、ize_t,int);/VFS 调用,向 value 中拷贝给定文件的扩展属性 name 对应的数值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 *, lo
35、ff_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); 9.目录项对象 VFS 把目录当作文件对待,一个完整路径中的每个组成部分都由一个索引节点对象标志。虽然可以用索引节点表示,但为了实际操作方便,引入目录项对象。完整路径的每一部分都是一个目录项对象,比如/bin/vi 中/,bin,vi 是三个目录项对象。 VFS
36、在执行目录操作时(需要的话 )会现场创建目录项对象。 目录项对象 dentry 结构体在 定义 struct dentry atomic_t d_count; /使用计数/* protected by d_lock */ unsigned int d_flags; /目录项标识/* per dentry lock */spinlock_t d_lock; /但目录项锁 int d_mounted;/* Where the name belongs to - NULL is* negative */struct inode *d_inode; /相关联的索引节点/* The next three
37、 fields are touched by _d_lookup. Place them here* so they all fit in a cache line.*/struct hlist_node d_hash; /* lookup hash list */struct dentry *d_parent; /* parent directory */struct qstr d_name; /目录项名字struct list_head d_lru; /* LRU list */未使用的链表/* d_child and d_rcu can share memory*/union /* ch
38、ild of parent list */struct list_head d_child;/目录项内部形成的链表struct rcu_head d_rcu; /RCU 加锁 d_u;struct list_head d_subdirs; /* our children */子目录链表struct list_head d_alias; /* inode alias list */索引节点别名链表unsigned long d_time; /* used by d_revalidate */重置时间const struct dentry_operations *d_op;/目录项操作时间stru
39、ct super_block *d_sb; /* The root of the dentry tree */文件的超级块void *d_fsdata; /* fs-specific data */文件系统特有数据unsigned char d_inameDNAME_INLINE_LEN_MIN; /* small names */; 与超级块和索引节点不同,目录项对象没有对应的磁盘数据结构,VFS 根据字符串形式的路径名现场创建它,而且由于目录项对象并非真正保存在磁盘上,所以目录项结构体没有是否被修改的标志。 9.1 目录项状态 目录项对象有三种有效状态:被使用、未被使用和负状态。 (1)一
40、个被使用的目录项对应一个有效的索引节点(d_inode 指针),并且表明该对象存在一个或多个使用者(d_count 为正值),一个目录项处于被使用状态,意味着它正被 VFS 使用并且指向有效数据,因此不能丢弃。 (2)一个未被使用的目录项对应一个有效的索引节点(d_inode 指向索引节点),但 VFS 当前并未使用(d_count 为 0),该目录项对象仍然指向一个有效对象,而且被保留在缓存中以便需要时使用。这样当需要它时,不必重新创建,这样路径查找更快。 (3)负状态的目录项没有对应的有效索引节点,节点以及被删除或路径已经不正确了。但目录项仍然保留,以便快速解析以后的路径查询。 目录项对象
41、释放后也可以保存到 slab 对象缓存中。 9.2 目录项缓存如果 VFS 层遍历路径名中的所有元素并将它们逐个解析成目录项对象,还要到达最深层目录,是一件非常费力的工作,会浪费大量时间。所以内核将目录项对象缓存在目录项缓存中 dcache 主要包括三个部分: (1)“被使用的”目录项链表, i_dentry 项链接相关索引节点。因为一个索引节点可能有多个链接,所以就可能有多个目录项对象,因此用链表链接他们。 (2)“最近被使用的”双向链表。该链表含有未被使用的或负状态的目录项对象。 由于该链表总是在头部插入目录项,所以头节点目录项是最新的,内核通过删除节点回收内存时,会从链尾删除。 (3)散
42、列表和相应的散列函数,用来快速地将给定路径解析为相关目录项对象。 散列表由数组 dentry_hashtable 表示,其中每一个元素都是一个指向相同键值的目录项对象链表的指针。 实际的散列值由 d_hash()函数计算,它是内核提供给文件系统的唯一的散列函数。 dcache 在一定意义上也提供了对索引节点的缓存,即 icache。和目录项对象相关的索引节点不会被释放。因为目录项让相关索引节点的使用计数为正,这样确保相应索引节点保留在内存中。所以,只要路径名在缓存中找到,那么相应的索引节点肯定也在内存中缓存着。 因为文件访问呈现空间和时间的局部性,所以对目录项和索引节点进程缓存非常有益。时间的
43、局部性,是指程序可能会一次又一次地访问相同的文件,当同一个文件被访问时,所缓存的相关目录项和索引节点命中率高。 空间局部性,是指程序可能在同一个目录下访问多个文件,因此一个文件对应的目录项缓存后命中率高,因为相关的文件可能在下次又被使用。 10.目录项操作 dentry_operation 结构体指向 VFS 操作目录项的所有方法,在 定义 struct dentry_operations int (*d_revalidate)(struct dentry *, struct nameidata *);int (*d_hash) (struct dentry *, struct qstr *)
44、;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); int d_ revalidate(struct dentry *dentry, struct nameidata*name); 判断目录对
45、象是否有效,VFS 准备从 dcache 中使用一个目录项时,会调用该函数,大部分时候设为 NULL,因为 dcache 中的目录项总是有效的。 int (*d_hash) (struct dentry * dentry, struct qstr * name); 该函数为目录生成散列值,当目录项需要加入到散列表中时,VFS 调用该函数。 int (*d_compare) (struct dentry * dentry, struct qstr *name1, struct qstr *name2); 多数文件系统使用 VFS 默认的操作,仅比较字符串。但有些系统,比如 FAT,不区分大小写的
46、,所以需要实现不区分大小写的字符串比较函数。 int (*d_delete)(struct dentry *dentry); 当目录项对象的 d_count 计数值等于 0,VFS 调用该函数 void (*d_release)(struct dentry *); 当目录项对象将要被释放时,VFS 调用该函数 void (*d_iput)(struct dentry *, struct inode *); 当目录项丢失了索引节点时(索引节点被删除了) ,VFS 调用,默认调用 input()函数释放所有节点。 11.文件对象 文件对象,表示进程已打开的文件,进程直接处理的是文件,而不是超级块,
47、索引节点或目录项。文件对象与我们熟悉的系统调用 read()和 write()对应。 多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅是进程观点上已打开的实际文件,只有索引节点和目录项对象才是唯一的。 文件对象在 定义 struct file /* fu_list becomes invalid after file_free is called and queued via* fu_rcuhead for RCU freeing*/union struct list_head fu_list; /文件对象链表struct rcu_head fu_
48、rcuhead; /释放之后的 RCU 链表 f_u;struct path f_path; /包含目录项#define f_dentry f_path.dentry#define f_vfsmnt f_path.mntconst struct file_operations *f_op;/文件操作表spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */单个文件结构锁atomic_long_t f_count; /文件对象的使用计数unsigned int f_flags; /打开文件时所指定的标志fmode_t f_mode; /文件的访问模式loff_t f_pos; /文件当前的位移量( 文件指针)struct fown_struct f_owner; /拥有者通过信号进行异步 I/O 数据传送const struct cred *f_cred; /