虚拟文件系统(VFS)为用户空间提供了文件系统相关的接口,用户程序可以通过标准的Unix文件系统调用对不同介质上的不同文件系统进行读写操作。
通用文件系统接口
VFS使得用户可以直接使用open()、read()和write()而无需考虑具体的文件系统和实际物理介质。标准系统调用也可以在不同的介质和文件系统之间执行,VFS负责这种不同介质和不同文件系统之间的协调,并对上提供一种通用的访问方法。
之所以这种通用接口对所有类型的文件系统都可以操作,是因为内核在它的底层文件系统之上建立了一个抽象层。这个抽象层提供了一个通用文件系统模型,支持各种文件系统。VFS定义了所有文件系统都支持的基本数据结构和接口,而实际文件系统都实现了这些基本接口。由于实际文件系统的代码在统一的接口和数据结构下隐藏了实现的细节,所以在VFS层和内核的其他部分来看,所有的文件系统都是相同的。
VFS中有四个主要的对象模型,分别是:
- 超级块对象:一个已安装的文件系统;
- 索引节点对象:代表一个文件;
- 目录项对象:代表路径的一个组成部分;
- 文件对象:代表文件,注意目录也是文件。
每种对象模型内核都定义了对应的操作对象,描述了内核针对该对象可以使用的方法。
超级块对象
每种文件系统都必须实现超级块,用于存储特定文件系统的信息,通常对应于存放在磁盘特定扇区中的文件系统超级块或文件系统控制块。对于非基于磁盘的文件系统,会在使用现场创建超级块并保存在内存中。
超级块用struct super_block结构体表示:
super_block { /* Keep this first 指向超级块链表的指针 */ /* search index; _not_ kdev_t 设备标志符 */ s_dirt; /* 修改(脏)标志 */ s_blocksize_bits; /* 块大小 单位bits */ s_blocksize; /* 块大小 单位Bytes*/ /* Max file size */ s_type; s_op; /× 超级块方法 ×/ dq_op; /× 磁盘限额方法 ×/ s_qcop; /× 限额控制方法 ×/ s_export_op; /× 导出方法 ×/ s_flags; s_magic; s_root; s_umount; s_lock; s_count; s_active; CONFIG_SECURITY s_security; 1421#endif s_xattr; 1423 /* all inodes */ /* anonymous dentries for (nfs) exporting */ CONFIG_SMP s_files; 1428#else s_files; 1430#endif /* s_dentry_lru, s_nr_dentry_unused protected by dcache.c lru locks */ /* unused dentry lru */ /* # of dentry on lru */ 1434 /* s_inode_lru_lock protects s_inode_lru and s_nr_inodes_unused */ ____cacheline_aligned_in_smp; /* unused inode lru */ /* # of inodes on lru */ 1439 s_bdev; s_bdi; s_mtd; s_instances; /* Diskquota specific options */ 1445 s_frozen; s_wait_unfrozen; 1448 /* Informational name */ /* UUID */ 1451 /* Filesystem private info */ s_mode; 1454 /* Granularity of c/m/atime in ns. Cannot be worse than a second */ s_time_gran; 1458 /* * The next field is for VFS *only*. No filesystems have any business * even looking at it. You had been warned. */ /* Kludge */ 1464 /* * Filesystem subtype. If non-empty the filesystem type field */ s_subtype; 1470 /* * Saved mount options for lazy filesystems using * generic_show_options() */ s_options; /* default d_op for dentries */ 1477 /* * Saved pool identifier for cleancache (-1 means none) */ cleancache_poolid; 1482 /* per-sb shrinker handle */ 1484};
超级块对象中的s_op定义了超级块的操作函数表,用super_operations结构体表示,其中的每一项都定义了一种操作的函数指针:
super_operations { sb); inode *); 1661 flags); wbc); inode *); inode *); super_block *); super_block *); wait); super_block *); super_block *); kstatfs *); super_block *, int *, char *); super_block *); 1674 vfsmount *); vfsmount *); vfsmount *); vfsmount *); CONFIG_QUOTA loff_t); loff_t); 1682#endif gfp_t); super_block *); super_block *, int); 索引节点对象
索引节点对象包含了内核在操作文件和目录时需要的全部信息,这些信息可以从磁盘索引节点直接读入。
索引节点使用inode结构体表示,一个索引节点代表了文件系统中的一个文件,它也可以是设备或者管道这样的特殊文件。
/* * Keep mostly read-only and often accessed (especially for * the RCU path lookup and 'stat' data) fields at the beginning * of the 'struct inode' */ inode { i_mode; i_opflags; i_uid; i_gid; i_flags; 755 CONFIG_FS_POSIX_ACL i_acl; i_default_acl; 759#endif 760 i_op; i_sb; i_mapping; 764 CONFIG_SECURITY i_security; 767#endif 768 /* Stat data, not accessed from path walking */ i_ino; /* * Filesystems may only read i_nlink directly. They shall use the * following functions for modification: * * (set|clear|inc|drop)_nlink * inode_(inc|dec)_link_count */ 778 union { i_nlink; __i_nlink; 781 }; i_rdev; i_atime; i_mtime; i_ctime; /* i_blocks, i_bytes, maybe i_size */ i_bytes; i_blocks; i_size; 790 __NEED_I_SIZE_ORDERED i_size_seqcount; 793#endif 794 /* Misc */ i_state; i_mutex; 798 /* jiffies of first dirtying */ 800 i_hash; /* backing dev IO list */ /* inode LRU list */ i_sb_list; 805 union { i_dentry; i_rcu; 808 }; i_count; i_blkbits; i_version; i_dio_count; i_writecount; /* former ->i_op->default_file_ops */ i_flock; i_data; CONFIG_QUOTA MAXQUOTAS]; 819#endif i_devices; 821 union { i_pipe; i_bdev; i_cdev; 825 }; 826 i_generation; 828 CONFIG_FSNOTIFY /* all events this inode cares about */ i_fsnotify_marks; 832#endif 833 CONFIG_IMA /* struct files open RO */ 836#endif /* fs or device private pointer */ 838}; 839
其中,i_op定义了索引节点对象的所有操作方法:
inode_operations { nameidata *); nameidata *); inode *, int); inode *, int); 1618 __user *,int); nameidata *, void *); 1621 nameidata *); dentry *); dentry *); dentry *,const char *); dentry *,int); dentry *); dev_t); dentry *, dentry *); inode *); iattr *); kstat *); size_t,int); size_t); size_t); dentry *, const char *); loff_t); start, len); 目录项对象
VFS把目录当作文件看待,而路径的每个部分则用目录项来表示。在路径中,包括普通文件在内,每个部分都是目录项对象。目录项对象的主要目的是为了方便路径名的查找等操作。
目录项用struct dentry 表示,它没有对应的磁盘数据结构,由于它并不会保存到磁盘上,所以也没有脏标志。
dentry { /* RCU lookup touched fields */ /* protected by d_lock */ /* per dentry seqlock */ /* lookup hash list */ /* parent directory */ d_name; /* Where the name belongs to - NULL is * negative */ /* small names */ 126 /* Ref lookup also touches following */ /* protected by d_lock */ /* per dentry lock */ d_op; /* The root of the dentry tree */ /* used by d_revalidate */ /* fs-specific data */ 134 /* LRU list */ /* * d_child and d_rcu can share memory */ 139 union { /* child of parent list */ d_rcu; d_u; /* our children */ /* inode alias list */ 145};
如果VFS遍历路径名中的所有元素并将它们逐个解析成目录项对象,这将是一件非常耗时的工作。因此,内核将目录项对象缓存在目录项缓存中。目录项缓存包括三部分:
- 被使用的目录项链表:通过索引节点的i_dentry项连接相关的索引节点;
- 最近被使用的目录项双向链表
- 散列表,用来快速将指定路径解析为相关目录项对象
内核通过dentry_operations定义了目录项操作函数列表:
dentry_operations { nameidata *); inode *, qstr *); inode *, inode *, qstr *); dentry *); dentry *); dentry *); inode *); dentry *, char *, int); path *); bool); 文件对象
文件对象代表已打开的文件,同一个文件可能对应多个文件对象(多个进程打开该文件),而一个文件对应的索引节点和目录项对象是唯一的。
文件对象通过结构体struct file表示,文件对象没有对应的磁盘数据,它在文件打开的时候创建,文件关闭的时候销毁。文件对象中也不记录脏标志(由inode记录)。
file { /* * fu_list becomes invalid after file_free is called and queued via * fu_rcuhead for RCU freeing */ 969 union { fu_list; fu_rcuhead; f_u; f_path; dentry mnt f_op; 977 /* * Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR. * Must not be taken from IRQ context. */ f_lock; CONFIG_SMP f_sb_list_cpu; 985#endif f_count; f_flags; f_mode; f_pos; f_owner; f_cred; f_ra; 993 f_version; CONFIG_SECURITY f_security; 997#endif /* needed for tty driver, and maybe others */ private_data; 1000
CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ f_ep_links; /* #ifdef CONFIG_EPOLL */ f_mapping; CONFIG_DEBUG_WRITECOUNT f_mnt_write_state; 1008#endif 1009};
下面的结构定义了文件对象的操作函数列表:
file_operations { owner; loff_t, int); loff_t *); loff_t *); loff_t); loff_t); filldir_t); poll_table_struct *); file *, unsigned int, unsigned long); file *, unsigned int, unsigned long); vm_area_struct *); file *); id); file *); datasync); datasync); file *, int); file_lock *); loff_t *, int); file *, unsigned long, unsigned long, unsigned long, unsigned long); check_flags)(int); file_lock *); size_t, unsigned int); size_t, unsigned int); file_lock **); offset, len); 1611};
参考:
《Linux内核设计与实现》
http://lxr.linux.no