本文将对Linux下的VFS作一个简单介绍,主要包括VFS里面的一些概念,以及文件系统是如何与VFS交互的。html
本文所涉及的代码摘自Linux-4.4.0-59node
VFS的全称为virtual File System(虚拟文件系统),能够把它理解为Linux下的文件系统平台。linux
对应用层来讲,只要和VFS打交道就能够了,VFS对外提供了read,write等接口,应用层程序不须要关心底层具体的的文件系统是怎么实现的。git
对具体的文件系统来讲,VFS就是一个框架,提供了文件系统通用的一些数据结构和函数,文件系统只须要提供VFS所要求的数据和实现所要求的函数就能够了,就像是在VFS上开发一个插件同样。github
下面这张图展现了文件系统在系统中的位置编程
+---------------------------------------------------------------+ | +---------------------+ | | | User Applications | | | +---------------------+ | | | ↓ User Space | | | +---------------+ | | | | GNU C Library | | | | +---------------+ | |...................|.........|.................................| | ↓ ↓ | | +-------------------------+ | | | System Call Interface | | | +-------------------------+ | | ↓ | | +-----------+ +-------------------------+ +---------------+ | | |Inode Cache|--| Virtual File System |--|Directory Cache| | | +-----------+ +-------------------------+ +---------------+ | | ↓ | | +-------------------------+ | | | Individual File System | | | +-------------------------+ | | ↓ | | +----------------+ | | | Buffer Cache | Kernel Space | | +----------------+ | | ↓ | | +----------------+ | | | Device Driver | | | +----------------+ | +---------------------------------------------------------------+
应用层程序(User Applications)能够经过libc(GNU C Library),或者直接经过内核提供的系统调用(System Call Interface )来访问文件缓存
在内核里面,相应的系统调用里面调用的其实就是VFS提供的函数数据结构
VFS根据文件路径找到相应的挂载点,就获得了具体的文件系统信息,而后调用具体文件系统的相应函数。(Inode Cache和Directory Cache是VFS中的一部分,用来加快inode和directory的访问)框架
具体的文件系统根据本身对磁盘(多是别的介质,本篇统一以磁盘为例)上数据的组织方式,操做相应的数据。(Buffer Cache是内核中块设备的缓存)函数
下面这张图简要的说明了VFS里面各类object之间的关系
. . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Process A . . . . . . fd . . Kernel . . File System . . +---+ . . +--------------+ . . . . stdin | 0 |-------------->| File Object1 |--------+ . . . . +---+ . . +--------------+ | . . . . stdout | 1 |-------+ . +--------------+ | . . . . +---+ . +------>| File Object2 |------+ | . . . . stderr | 2 |-------+ . +--------------+ | | . . +------------+ . . +---+ . . +--------------+ | | . +------->|Inode Object|--+ +-----------------+ . . | 3 |-------------->| File Object3 |----+ | | DEntry Cache . | . +------------+ | +->|Superblock Object| . . +---+ . . +--------------+ | | | +-------------+ . | . +------------+ | | +-----------------+ . . |...| . . +--+-+-+->|DEntry Object|------+ +---->|Inode Object|--+ | | . . +---+ . . | | | +-------------+ . | . +------------+ +--+ | . . . . . . .. . . . . . | | +--->|DEntry Object|---------+ . +------------+ | | ↓ . . | | +-------------+ . .+->|Inode Object|--+ | +-----------------+ . . | +----->|DEntry Object|------------+ +------------+ | | | | . . . . . . .. . . . . . | | +-------------+ . . +------------+ | +->| Disk | . . Process B . . | | +-->|DEntry Object|-------------->|Inode Object|--+ | | . . fd . . +--------------+ | | | +-------------+ . . +------------+ +-----------------+ . . +---+ . +------>| File Object4 |-+ | | | ..... | . . . . stdin | 0 |-------+ . +--------------+ | | +-------------+ . . . . +---+ . . +--------------+ | | . . . . stdout | 1 |-------------->| File Object5 |----+ | . . . . +---+ . . +--------------+ | | . . . . stderr | 2 |-------+ . +--------------+ | | . . . . +---+ . +------>| File Object6 |----+ | . . . . | 3 |---+ . . +--------------+ | . . . . +---+ | . . +--------------+ | . . . . |...| +---------->| File Object7 |-------+ . . . . +---+ . . +--------------+ . . . . . . . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
在进程的眼里,看到的是fd列表,列表里面存放的是指向file object的指针,因此一样的fd在不一样的进程中可能会指向不一样的file object
file object是内核中的对象,表明了一个被进程打开的文件,和具体的进程相关联
这里是它的数据结构(只摘取部分)
//linux/fs.h struct file { mode_t f_mode; /*rwx权限及文件类型,来自inode object*/ struct path f_path; /*文件的路径,由dentry object组成*/ struct inode *f_inode; /*指向的inode*/ const struct file_operations *f_op; /*和文件相关的函数,好比read,write等,来自inode object*/ loff_t f_pos; /*当前文件访问到的位置*/ } //linux/path.h struct path { struct vfsmount *mnt; /*所属的挂载点信息*/ struct dentry *dentry; /*指向的dentry object*/ };
当应用程序调用open函数的时候,VFS就会建立相应的file object,除了f_pos是进程私有的数据外,其余的数据都来自于inode和dentry,和全部进程共享,不一样进程的file object能够指向同一个dentry和inode
DEntry Object由VFS维护,全部文件系统共享,不和具体的进程关联,每一个DEntry Object都指向一个Inode object,在加载inode时根据inode自动生成。
当调用open函数打开一个文件时,内核会第一时间到dentry cache里面根据path来找相应的dentry,找到了就直接构造file object并返回,若是没找到的话,就会根据找到的最近的目录一级一级的往下加载,直到找到相应的文件。(加载的过程就是从磁盘加载inode的过程,全部被加载的inode都会生成相应的dentry而后缓存起来)
dentry chache的做用就是用来加快根据path找到相应文件的速度,它里面有本身设计的便于快速查找的数据结构,dentry只存在于内存中,不会被存储在磁盘上,因为内存有限,因此并非磁盘上全部inode所对应的dentry都会被缓存起来,VFS有本身的缓存策略。
这里是它的数据结构(只摘取部分)
//linux/dcache.h struct dentry { struct dentry *d_parent; /* 父目录 */ struct qstr d_name; /* 名字 */ struct inode *d_inode; /* 关联的inode */ */ struct list_head d_child; /* 父目录中的子目录和文件 */ struct list_head d_subdirs; /* 当前目录中的子目录和文件 */ };
注意:dentry虽然名字叫directory entry,实际上它对应的inode也能够是普通文件
inode的结构体由VFS定义,表明了磁盘上的一个文件、目录、连接或者其它类型的文件。
inode里面包含的数据存放在磁盘上,由具体的文件系统进行组织,当须要访问一个inode时,会由文件系统从磁盘上加载相应的数据并构造好相应的inode
一个inode可能被多个dentry所关联,好比多个hard links指向同一个文件的状况
这里是它的数据结构(只摘取部分)
//linux/fs.h struct inode { umode_t i_mode; /*rwx权限及文件类型*/ kuid_t i_uid; /*user id*/ kgid_t i_gid; /*group id*/ const struct inode_operations *i_op; /*inode相关的操做函数,如创create,mkdir,lookup,rename等,请参考fs.h里的定义*/ struct super_block *i_sb; unsigned long i_ino; /*inode的编号*/ loff_t i_size; /*文件大小*/ struct timespec i_atime; /*最后访问时间*/ struct timespec i_mtime; /*文件内容最后修改时间*/ struct timespec i_ctime; /*文件元数据最后修改时间(包括文件名称)*/ const struct file_operations *i_fop; /* 文件操做函数,包括open,llseek,read,write等,请参考fs.h里的定义 */ void *i_private; /* 文件系统的私有数据,通常能够从这里面知道文件对应的数据在哪 */ };
它的结构体由VFS定义,但里面的数据由具体的文件系统填充,每一个superblock表明了一个具体的分区。
每一个磁盘分区上都有一份superblock,里面包含了当前磁盘分区的信息,如文件系统类型、剩余空间等。
因为superblock很是重要,因此通常文件系统都会在磁盘上存储多份,防止数据损坏致使整个分区没法读取。
这里是它的数据结构(只摘取部分)
//linux/fs.h struct super_block { unsigned long s_blocksize; /* block的大小,常见文件系统通常都是4K */ loff_t s_maxbytes; /* 支持的最大文件大小 */ struct file_system_type *s_type; /* 文件系统系统类型 */ struct dentry *s_root; /* 分区内文件树的根节点 */ struct list_head s_mounts; /* 一个分区能够mount到多个地方,这里是mount的相关信息*/ struct block_device *s_bdev; /* 对应的物理设备信息 */ u8 s_uuid[16]; /* 分区的UUID */ void *s_fs_info; /* 文件系统的私有数据*/ };
文件系统主要负责管理磁盘上的空间,磁盘上至少要包含三部分数据: superblock,inodes和数据块。
首先得有一个建立文件系统的工具(如ext2文件系统的mke2fs),用来将磁盘分区格式化成想要的格式,主要是初始化superblock和root inode。
写一个内核模块,在里面注册本身的文件系统,而且初始化mount函数
当用户在应用层调用mount命令时,VFS就会根据指定的文件系统类型找到咱们写的内核模块,而且调用里面的mount函数
在mount函数里面读取磁盘上的superblock和root inode
初始化root inode的inode_operations和file_operations,而后返回给VFS
这样VFS就能根据root inode里提供的函数一级一级的往下找到path对应文件的inode
读取inode所指向的数据块(一个或者多个),根据文件的类型,解析数据块的内容。若是文件类型是普通文件,那么数据块里面就是文件的内容;若是文件类型是目录,那么数据块里面存储的就是目录下面全部子目录和文件的名称及它们对应的inode号;若是文件类型是软连接,那么数据块里面存储的就是连接到的文件路径。
总的来讲,实现文件系统就是怎么在磁盘上组织文件,而后实现VFS所要求的superblock,inode以及inode_operations和file_operations。
本篇只是介绍了VFS里面的基本概念,没有深刻细节(由于我也没有相关经验),但愿对你们理解文件系统有所帮助。
要实现一个真正的文件系统除了须要了解VFS外,还须要不少内核编程及磁盘操做的知识,若是感兴趣,能够参考ext2文件系统的实现,相对简单且代码少(不到1万行)。