虚拟文件系统
虚拟文件系统(VFS)作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。系统中所有的文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统,程序可以利用标准的Unix系统调用不同的文件系统,甚至在不同介质上的文件系统进行读写操作。
1.文件系统抽象层
Linux可以使用通用接口对所有类型的文件系统进行操作,是因为内核在它的文件系统接口上建立了一个抽象层,使得Linux能够支持各种文件系统,即便他们在功能和行为上存在很大差别。
VFS抽象层之所以能够衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念是的接口和数据结构。例如用户进程进行下面写操作:
ret = write(fd, buf, len);
该系统调用将buf
指针指向长为len
字节的数据写入文件描述符fd
对应的文件的当前位置。这个系统调用首先被一个通用调用sys_write()
处理,sys_write()
函数找到fd
所在的文件系统实际给出的是哪个写操作,然后再执行该操作。
2.Unix文件系统
从本质上文件系统是特殊的数据分层存储结构,包括文件、目录和相关的控制信息。文件系统的通用操作包含创建、删除和安装等。
Unix使用了四种和文件系统相关的传统抽象概念:文件、目录项、索引节点和安装点(挂载点,mount point):
- 文件,一个有序字节串,字节串的第一个字节是文件的头,最后一个字节是文件的尾。与Unix文件形成对比的是面向记录的文件系统(如OpenVMS的File-11),面向记录的文件系统提供更丰富、更结构化的表示,而简单的面向字节流的Unix则以简单性和灵活性为优点
-
目录,文件通过目录组织。文件目录好比一个文件夹,用来容纳相关文件,同时目录也可以层层嵌套。例如
/home/file
是文件路径的例子,根目录为/
,home
和file
为目录。Unix中,目录也是普通文件,可以执行文件的操作 - 索引节点,Unix系统将文件的相关信息和文件本身分开,例如控制权限、大小、拥有者、创建时间等。文件相关信息又称文件的元数据,存储在一个单独的数据结构中,该结构称为索引节点(inode)
- 安装点,文件系统被安装在一个特定的安装点,文件系统的安装位置
Unix文件系统在物理磁盘上,也是按照上述方式存储。Unix中文件的概念从物理上被映射到存储介质。Linux的VFS设计目标就是保证能与支持和实现这些概念的文件系统协同工作。例如像FAT或NTFS这样的非Unix风格文件系统,经过封装提供符合这些概念的界面(目的是用于支持inode
)。
3.VFS对象及数据结构
VFS采用了面向对象的设计思路,因为使用的C语言,因此内核的数据结构都是用C语言的结构体,这些结构体包含数据和操作数据的函数指针,其中操作函数由具体的文件系统实现。
VFS有四个主要的对象类型,分别是:
- 超级块对象,代表一个具体已安装的文件系统
- 索引节点对象,代表一个具体文件
- 目录项对象,代表一个目录项,是路径的组成部分
- 文件对象,代表由进程打开的文件
每个主要对象都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法:
-
super_operations
对象,其中包括内核对文件系统所能调用的方法,例如write_inode()
和sync_fs
等方法 -
inode_operations
对象,其中包括内核对特定文件所能调用的方法,比如create()
和link()
等方法 -
dentry_operations
对象,其中包括内核对特定目录所能调用的方法,比如d_compare()
和d_delete()
等方法 -
file_operations
对象,其中包括进程针对已打开文件所能调用的方法,比如read()
和write()
等方法
注意,上述的操作对象均是指的结构体,上面4个对象名称就是对应结构体名称。
4.超级块对象
各种文件系统都必须实现超级块对象,该对象用于存储特定文件系统的信息,通常包括对应于存放在磁盘特定扇区的文件系统超级块或文件系统控制块(所以称为超级块对象)。对于非基于磁盘的文件系统(如基于内存的sysfs),它们会在使用现场创建超级块并保存到内存中。
超级块对象由super_block
结构体表示,定义在文件<linux/fs.h>
,下面给出它的结构和各个域描述:
struct super_block {
struct list_head s_list; /* list of all superblocks */
dev_t s_dev; /* identifier */
unsigned long s_blocksize; /* block size in bytes */
unsigned long s_old_blocksize; /* old block size in bytes */
unsigned char s_blocksize_bits; /* block size in bits */
unsigned char s_dirt; /* dirty flag */
unsigned long long s_maxbytes; /* max file size */
struct file_system_type s_type; /* filesystem type */
struct super_operations s_op; /* superblock methods */
struct dquot_operations *dq_op; /* quota methods */
struct quotactl_ops *s_qcop; /* quota control methods */
struct export_operations *s_export_op; /* export methods */
unsigned long s_flags; /* mount flags */
unsigned long s_magic; /* filesystem's magic number */
struct dentry *s_root; /* directory mount point */
struct rw_semaphore s_umount; /* unmount semaphore */
struct semaphore s_lock; /* superblock semaphore */
int s_count; /* superblock ref count */
int s_syncing; /* filesystem syncing flag */
int s_need_sync_fs; /* not-yet-synced flag */
atomic_t s_active; /* active reference count */
void *s_security; /* security module */
struct list_head s_dirty; /* list of dirty inodes */
struct list_head s_io; /* list of writebacks */
struct hlist_head s_anon; /* anonymous dentries */
struct list_head s_files; /* list of assigned files */
struct block_device *s_bdev; /* associated block device */
struct list_head s_instances; /* instances of this fs */
struct quota_info s_dquot; /* quota-specific options */
char s_id[32]; /* text name */
void *s_fs_info; /* filesystem-specific info */
struct semaphore s_vfs_rename_sem; /* rename semaphore */
};
创建,管理和销毁超级块对象的代码位于文件<fs/super.c>
中,超级块对象通过alloc_super()
函数创建并初始化。在文件系统安装时,内核会调用该函数以便从磁盘读取文件系统超级块,并且将其信息填充到内存中的超级块对象中。其中最重要的一个是s_op
,指向超级块的操作函数表,由super_operations
结构体表示,定义在<linux/fs.h>
中,如下:
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 *);
void (*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 *, int);
void (*write_super_lockfs) (struct super_block *);
void (*unlockfs) (struct super_block *);
int (*statfs) (struct super_block *, struct statfs *);
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 *);
};
一个文件系统要写自己的超级块,需要调用:sb->s_op->write_super(sb)
这里的sb
是指向文件系统超级块的指针,沿着该指针进入超级块操作函数表,并从表中取得希望得到的write_super()
函数,该函数执行写入超级块的实际操作。
5.索引节点对象
索引节点对象包含了内核在操作文件或目录(目录在Unix里也是文件啊)时需要的全部信息。对于Unix风格的文件系统来说,这些信息可以从磁盘的索引节点直接读取。另外无论什么文件系统,索引节点对象必须在内存中创建,以便于文件系统使用。
索引节点对象由inode
结构体表示,定义在<linux/fs.h>
中:
struct inode {
struct hlist_node i_hash; /* hash list */
struct list_head i_list; /* list of inodes */
struct list_head i_dentry; /* list of dentries */
unsigned long i_ino; /* inode number */
atomic_t i_count; /* reference counter */
umode_t i_mode; /* access permissions */
unsigned int i_nlink; /* number of hard links */
uid_t i_uid; /* user id of owner */
gid_t i_gid; /* group id of owner */
kdev_t i_rdev; /* real device node */
loff_t i_size; /* file size in bytes */
struct timespec i_atime; /* last access time */
struct timespec i_mtime; /* last modify time */
struct timespec i_ctime; /* last change time */
unsigned int i_blkbits; /* block size in bits */
unsigned long i_blksize; /* block size in bytes */
unsigned long i_version; /* version number */
unsigned long i_blocks; /* file size in blocks */
unsigned short i_bytes; /* bytes consumed */
spinlock_t i_lock; /* spinlock */
struct rw_semaphore i_alloc_sem; /* nests inside of i_sem */
struct semaphore i_sem; /* inode semaphore */
struct inode_operations *i_op; /* inode ops table */
struct file_operations *i_fop; /* default inode ops */
struct super_block *i_sb; /* associated superblock */
struct file_lock *i_flock; /* file lock list */
struct address_space *i_mapping; /* associated mapping */
struct address_space i_data; /* mapping for device */
struct dquot *i_dquot[MAXQUOTAS]; /* disk quotas for inode */
struct list_head i_devices; /* list of block devices */
struct pipe_inode_info *i_pipe; /* pipe information */
struct block_device *i_bdev; /* block device driver */
unsigned long i_dnotify_mask; /* directory notify mask */
struct dnotify_struct *i_dnotify; /* dnotify */
unsigned long i_state; /* state flags */
unsigned long dirtied_when; /* first dirtying time */
unsigned int i_flags; /* filesystem flags */
unsigned char i_sock; /* is this a socket? */
atomic_t i_writecount; /* count of writers */
void *i_security; /* security module */
__u32 i_generation; /* inode version number */
union {
void *generic_ip; /* filesystem-specific info */
} u;
};
一个索引节点代表一个文件,它也可以是设备或管道这种特殊文件。因此在索引节点对象中有一些特殊文件相关项,比如i_pipe
代表有名管道数据结构,i_bdev
指向块设备结构,在最新内核版本里面它们都是在一个union
里表示这个索引节点只能是某一种文件。
和超级块类似,对索引节点的操作调用方式如下:
i->i_op->truncate(i)
i
指向给定的索引节点。索引节点的操作结构体inode_operations()
结构体定义在文件<linux/fs.h>
中:
struct inode_operations {
int (*create) (struct inode *, struct dentry *,int);
struct dentry * (*lookup) (struct inode *, struct dentry *);
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 *, int);
int (*follow_link) (struct dentry *, struct nameidata *);
int (*put_link) (struct dentry *, struct nameidata *);
void (*truncate) (struct inode *);
int (*permission) (struct inode *, int);
int (*setattr) (struct dentry *, struct iattr *);
int (*getattr) (struct vfsmount *, 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 *);
};
从上面还有可以看到很多跟linux指令相关的函数,例如mkdir
。
6.目录项对象
虽然VFS把目录当作文件对待,但是为了方便查找,VFS引入了目录项概念。例如/bin/vi
中/
、bin
和vi
都是目录项对象,前两个是文件,后一个是普通文件。解析一个文件路径是耗时的常规字符串比较过程,因此VFS引入目录项简化这个过程。
目录项对象由dentry
结构体表示,定义在文件<linux/dcache.h>
中:
struct dentry {
atomic_t d_count; /* usage count */
unsigned long d_vfs_flags; /* dentry cache flags */
spinlock_t d_lock; /* per-dentry lock */
struct inode *d_inode; /* associated inode */
struct list_head d_lru; /* unused list */
struct list_head d_child; /* list of dentries within */
struct list_head d_subdirs; /* subdirectories */
struct list_head d_alias; /* list of alias inodes */
unsigned long d_time; /* revalidate time */
struct dentry_operations *d_op; /* dentry operations table */
struct super_block *d_sb; /* superblock of file */
unsigned int d_flags; /* dentry flags */
int d_mounted; /* is this a mount point? */
void *d_fsdata; /* filesystem-specific data */
struct rcu_head d_rcu; /* RCU locking */
struct dcookie_struct *d_cookie; /* cookie */
struct dentry *d_parent; /* dentry object of parent */
struct qstr d_name; /* dentry name */
struct hlist_node d_hash; /* list of hash table entries */
struct hlist_head *d_bucket; /* hash bucket */
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* short name */
};
目录项对象有三个有效状态:被使用,未被使用和负状态。
- 被使用,目录项分配了一个有效索引节点,并且该对象存在使用者,不能被丢弃
- 未被使用,目录项分配了一个有效索引节点,但是VFS中没有使用它,该目录项指向一个有效对象,被保存在缓存中以便需要时再使用它,如果要回收内存,可以撤销未使用的目录项
- 负状态,或者说无效目录项,没有对应有效索引节点,因为索引节点被删除了,或者路径不正确,但是目录项仍然保留,以便快速解析路径查询,如果有需要,可以撤销它,实际上负状态的目录项也很少被使用
如果VFS层遍历路径名中所有的元素并将它们逐个地解析成目录项对象,还要达到最深层目录,将是一件费力的工作,因此内核将目录项对象缓存在目录项缓存中(dcache),目录项缓存包括三个主要部分:
- 被使用的目录项链表。该链表通过索引节点对象中的
i_dentry
项链接相关的索引节点,因为一个给定索引节点可能有多个链接,存在多个目录项对象 - 最近未被使用双向链表。保存未使用和负状态的目录项对象,头部是最新加入的,删除从尾部删
- 散列表和散列函数用于快速地将给定路径解析为相关目录项对象
散列表由数组dentry_hashtable
表示,其中每个元素都是一个指向具有相同键值的目录项对象链表的指针,数组大小取决于系统中物理内存大小。
实际的散列值由d_hash()
函数计算,它是内核提供给文件系统的唯一一个散列函数。通过d_lookup()
函数查找散列表。
VFS会优先从目录项缓存中搜索路径名,这样加快了检索速度。
目录项操作dentry_operation
结构体指明了VFS操作目录项的所有方法,定义在<linux/dcache.h>
:
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 *);
};
7.文件对象
文件对象是已经打开的文件在内存中的表示,包含我们非常熟悉的信息(如访问模式,当前偏移量等)。文件对象仅仅在进程观点上表示已打开文件,它反过来指向目录项对象(再反过来指向索引节点),其实只有目录项对象才表示已打开的实际文件。虽然一个文件对应的文件对象不是唯一的,但对应的索引节点和目录项对象无疑是唯一的。
文件对象由file
结构体表示,定义在<linux/fs.h>
中:
struct file {
struct list_head f_list; /* list of file objects */
struct dentry *f_dentry; /* associated dentry object */
struct vfsmount *f_vfsmnt; /* associated mounted fs */
struct file_operations *f_op; /* file operations table */
atomic_t f_count; /* file object's usage count */
unsigned int f_flags; /* flags specified on open */
mode_t f_mode; /* file access mode */
loff_t f_pos; /* file offset (file pointer) */
struct fown_struct f_owner; /* owner data for signals */
unsigned int f_uid; /* user's UID */
unsigned int f_gid; /* user's GID */
int f_error; /* error code */
struct file_ra_state f_ra; /* read-ahead state */
unsigned long f_version; /* version number */
void *f_security; /* security module */
void *private_data; /* tty driver hook */
struct list_head f_ep_links; /* list of eventpoll links */
spinlock_t f_ep_lock; /* eventpoll lock */
struct address_space *f_mapping; /* page cache 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 *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, char *, size_t, loff_t);
ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
ssize_t (*aio_write) (struct kiocb *, 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);
int (*aio_fsync) (struct kiocb *, int);
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 flags);
int (*dir_notify) (struct file *filp, unsigned long arg);
int (*flock) (struct file *filp, int cmd, struct file_lock *fl);
};
8.文件系统相关的数据结构
除了上述几种VFS基础对象,内核还使用了一些标准数据结构用于管理文件系统的其他数据:file_system_type和vfsmount。
linux支持不同文件系统,内核需要一个特殊结构用于描述文件系统:file_system_type。file_system_type结构体定义在<linux/fs.h>
中:
struct file_system_type {
const char *name; /* filesystem's name */
struct subsystem subsys; /* sysfs subsystem object */
int fs_flags; /* filesystem type flags */
/* the following is used to read the superblock off the disk */
struct super_block *(*get_sb) (struct file_system_type *, int,char *, void *);
/* the following is used to terminate access to the superblock */
void (*kill_sb) (struct super_block *);
struct module *owner; /* module owning the filesystem */
struct file_system_type *next; /* next file_system_type in list */
struct list_head fs_supers; /* list of superblock objects */
};
get_sb()
函数从磁盘上读取超级块,并且在文件系统被安装时,在内存中组装超级块对象。每种文件系统,不管有多少实例安装到系统中,还是根本没安装,都只有一个file_system_type结构。
更有趣的是,当文件系统被实际安装时,将有一个vfsmount结构体在安装点被创建,该结构体用来代表文件系统的实例——换句话说,代表一个安装点(挂载点)。
vfsmount结构被定义在<linux/mount.h>
中:
struct vfsmount {
struct list_head mnt_hash; /* hash table list */
struct vfsmount *mnt_parent; /* parent filesystem */
struct dentry *mnt_mountpoint; /* dentry of this mount point */
struct dentry *mnt_root; /* dentry of root of this fs */
struct super_block *mnt_sb; /* superblock of this filesystem */
struct list_head mnt_mounts; /* list of children */
struct list_head mnt_child; /* list of children */
atomic_t mnt_count; /* usage count */
int mnt_flags; /* mount flags */
char *mnt_devname; /* device file name */
struct list_head mnt_list; /* list of descriptors */
struct list_head mnt_fslink; /* fs-specific expiry list */
struct namespace *mnt_namespace /* associated namespace */
};
9.与进程相关的数据结构
系统中每一个进程都有一组自己打开的文件,像根文件系统、当前工作目录、安装点等。有三个数据结构将VFS层和系统进程紧密联系在一起,它们分别是:file_struct、fs_struct和namespace结构体。
file_struct结构体定义在文件<linux/fdtabl.h>
中,该结构体由进程描述符的files
目录项指向。所有与单个进程相关的信息(如打开的文件及文件描述符)都包含其中,其结构和描述如下:
struct files_struct {
atomic_t count; /* structure's usage count */
spinlock_t file_lock; /* lock protecting this structure */
int max_fds; /* maximum number of file objects */
int max_fdset; /* maximum number of file descriptors */
int next_fd; /* next file descriptor number */
struct file **fd; /* array of all file objects */
fd_set *close_on_exec; /* file descriptors to close on exec() */
fd_set *open_fds; /* pointer to open file descriptors */
fd_set close_on_exec_init; /* initial files to close on exec() */
fd_set open_fds_init; /* initial set of file descriptors */
struct file *fd_array[NR_OPEN_DEFAULT]; /* default array of file objects */
};
fd_array
数组指针指向已经打开的文件对象,因为NR_OPEN_DEFAULT等于BITS_PER_LONG,所以64位机,该值就为64,表示该数组最多容纳64个文件对象,如果超出,内核就会分配一个新数组,并用fd指针指向它。为了优化性能,管理员可以调整NR_OPEN_DEFAULT的预定义值。
第二个结构体是fs_struct。该结构由检查描述符的fs
域指向,包含文件系统和进程相关的信息,定义在<linux/fs_struct.h>
中:
struct fs_struct {
int users;
spinlock_t lock;
seqcount_t seq;
int umask;
int in_exec;
struct path root, pwd;
};
该结构包含当前进程的当前工作目录(pwd)和根目录。
最后一个是namespace结构体,由进程描述符的mnt_namespace
域指向,它使得每个进程在系统中都能看到唯一的安装文件系统。定义在文件<linux/mnt_namespace.h>
(实际上我没找到啊!)中:
struct namespace {
atomic_t count; /* structure usage count */
struct vfsmount *root; /* mount object of root directory */
struct list_head list; /* list of mount points */
struct rw_semaphore sem; /* semaphore protecting the namespace */
};
默认情况下,所有的进程共享同样的命名空间。