概念
VFS(Virtual Filesystem Switch)称为虚拟文件系统或虚拟文件系统转换,是一个内核软件层,在具体的文件系统之上抽象的一层,用来处理与Posix文件系统相关的所有调用,表现为能够给各种文件系统提供一个通用的接口,使上层的应用程序能够使用通用的接口访问不同文件系统,同时也为不同文件系统的通信提供了媒介。
架构
VFS在整个Linux系统中的架构视图如下:
Linux系统的User使用GLIBC(POSIX标准、GUN C运行时库)作为应用程序的运行时库,然后通过操作系统,将其转换为系统调用SCI(system-call interface),SCI是操作系统内核定义的系统调用接口,这层抽象允许用户程序的I/O操作转换为内核的接口调用。VFS提供了一个抽象层,将POSIX API接口与不同存储设备的具体接口实现进行了分离,使得底层的文件系统类型、设备类型对上层应用程序透明。
接口适配示例
用户写入一个文件,使用POSIX标准的write接口,会被操作系统接管,转调sys_write这个系统调用(属于SCI层)。然后VFS层接受到这个调用,通过自身抽象的模型,转换为对给定文件系统、给定设备的操作,这一关键性的步骤是VFS的核心,需要有统一的模型,使得对任意支持的文件系统都能实现系统的功能。这就是VFS提供的统一的文件模型(common file model),底层具体的文件系统负责具体实现这种文件模型,负责完成POSIX API的功能,并最终实现对物理存储设备的操作。
VFS这一层建模和抽象是有必要的,如果放在SCI层会导致操作系统的系统调用的功能过于复杂,易出bug。那么就只能让底层文件系统都遵循统一实现,这对于已经出现的各种存储设备来说天然就有不同的特性,也是无法实现的。因此VFS这样一层抽象是有其必要性的。
跨设备/文件系统示例
VFS为不同设备或文件系统间的访问提供了媒介,下面的示意图和代码中,用户通过cp命令进行文件的拷贝,对用户来说是不用关心底层是否跨越文件系统和设备的,具体都通过VFS抽象层实现对不同文件系统的读写操作。
VFS的抽象接口
上述示例中提到VFS也有自己的文件模型,用来支持操作系统的系统调用。下面是VFS抽象模型支持的所有Linux系统调用:
- 文件系统相关:mount, umount, umount2, sysfs, statfs, fstatfs, fstatfs64, ustat
- 目录相关:chroot,pivot_root,chdir,fchdir,getcwd,mkdir,rmdir,getdents,getdents64,readdir,link,unlink,rename,lookup_dcookie
- 链接相关:readlink,symlink
- 文件相关:chown, fchown,lchown,chown16,fchown16,lchown16,hmod,fchmod,utime,stat,fstat,lstat,acess,oldstat,oldfstat,oldlstat,stat64,lstat64,lstat64,open,close,creat,umask,dup,dup2,fcntl, fcntl64,select,poll,truncate,ftruncate,truncate64,ftruncate64,lseek,llseek,read,write,readv,writev,sendfile,sendfile64,readahead
Linux系统VFS支持的文件系统
- Disk-based 文件系统:Ext2, ext3, ReiserFS,Sysv, UFS, MINIX, VxFS,VFAT, NTFS,ISO9660 CD-ROM, UDF DVD,HPFS, HFS, AFFS, ADFS,
- Network 文件系统:NFS, Coda, AFS, CIFS, NCP
- 特殊文件系统:/proc,/tmpfs等
统一文件模型(common file model)
VFS为了提供对不同底层文件系统的统一接口,需要有一个高度的抽象和建模,这就是VFS的核心设计——统一文件模型。目前的Linux系统的VFS都是源于Unix家族,因此这里所说的VFS对所有Unix家族的系统都适用。Unix家族的VFS的文件模型定义了四种对象,这四种对象构建起了统一文件模型。
- superblock:存储文件系统基本的元数据。如文件系统类型、大小、状态,以及其他元数据相关的信息(元元数据)
- index node(inode):保存一个文件相关的元数据。包括文件的所有者(用户、组)、访问时间、文件类型等,但不包括这个文件的名称。文件和目录均有具体的inode对应
- directory entry(dentry):保存了文件(目录)名称和具体的inode的对应关系,用来粘合二者,同时可以实现目录与其包含的文件之间的映射关系。另外也作为缓存的对象,缓存最近最常访问的文件或目录,提示系统性能
- file:一组逻辑上相关联的数据,被一个进程打开并关联使用
统一文件模型是一个标准,各种具体文件系统的实现必须以此模型定义的各种概念来实现。
Superblock
静态:superblock保存了一个文件系统的最基础的元信息,一般都保存在底层存储设备的开头;动态:挂载之后会读取文件系统的superblock并常驻内存,部分字段是动态创建时设置的。superblock的具体定义见linux/include/fs/fs.h,下图展示了内存中维护的superblock:
由于Linux系统支持同时挂载多个文件系统,因此s_list字段用于在内存中构建superblock链表来支持挂载多个文件系统。s_root字段标识该文件系统的根目录,s_bdev标识该文件系统所在的设备信息。其中最重要的字段是s_op,这个指针指向该文件系统所支持的各种操作的结构体,称为“super_operations”,具体定义如下:
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
*
,
int
)
;
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
(
*