文件系统主要有三类
1. 位于磁盘的文件系统,在物理磁盘上存储文件,比如NTFS, FAT, ext3, ext4
2. 虚拟文件系统,在内核中生成,没有物理的存储介质
3. 网络文件系统,位于远程主机上的文件系统,通过网络访问
一个操作系统可以支持多种底层不同的文件系统,为了给内核和用户进程提供统一的文件系统视图,Linux在用户进程和底层文件系统之间加入了一个抽象层,即虚拟文件系统(Virtual File System, VFS),进程所有的文件操作都通过VFS,由VFS来适配各种底层不同的文件系统,完成实际的文件操作。
通俗的说,VFS就是定义了一个通用文件系统的接口层和适配层,一方面为用户进程提供了一组统一的访问文件,目录和其他对象的统一方法,另一方面又要和不同的底层文件系统进行适配。
VFS采用了面向对象的思路来设计它的核心组件,只是VFS是用C写的,没有对象的语法,只能用struct来表示。我们按照面向对象的思路来理解VFS。
它有4个主要的对象类型:
1. 超级块对象,代表一个具体的已安装(mount)的文件系统
2. inode对象,表示一个具体的文件
3. 目录项对象,代表一个目录项,是路径的一部分,比如一个路径 /home/foo/hello.txt,那么目录项有home, foo, hello.txt
4. 打开文件对象,表示一个打开的文件,有读写的pos位置,也叫文件句柄,说白了就是open系统调用在内核创建的一个数据结构
VFS给每个对象都定义了一组操作对象(函数指针),给出了这些操作的默认实现,底层不同的文件系统可以重写(override)VFS的操作函数来给出自己的具体操作实现,也可以复用VFS的默认实现。实际情况是底层文件系统部分操作由自己单独实现,部分复用了VFS的默认实现。
操作对象有:
1. super_operations对象,针对超级块对象,包含了内核对特定文件系统所能调用的方法,比如wirte_inode(),sync_fs()等
2. inode_operations对象,针对inode对象,包含了内核对特定文件所能调用的方法,比如create(), link()等
3. dentry_operations对象(directory entry),针对目录项对象,包含了内核对特定目录所能调用的方法,比如d_compare()和d_delete()方法等
文件系统说白了就是文件内容和存储系统对应的块的映射关系,是来管理文件的存储的。inode-block结构把文件分为了两部分,inode表示元数据,block表示存储文件内容的具体的逻辑块。VFS没有用单独的对象来表示block,block的属性在超级块和inode块中包含了。
下面这张图包含了VFS的主要对象和操作对象,以及对象之间的指针指向关系,
1. 可以看到对象都维护了一个X_op指针指向它所对应的操作对象。
2. 超级块维护了一个s_files指针指向了内核所有的打开文件对象的链表,这个信息是所有进程共享的
3. 目录向对象和inode对象都维护了一个X_sb指针指向超级块对象,从而可以获得整个文件系统的元数据信息
4. 目录项对象和inode对象各自维护了指向对方的指针,可以找到对方的数据
5. 打开文件对象维护了一个f_dentry对象,指向了它对应的目录项对象,从而可以根据目录项对象找到它对应的inode信息
6. task_struct表示进程对象,维护了一个files指针,指向了进程打开的文件链表,这个是进程单独的视图,进程还维护了文件描述符表(file descriptor, fd),所谓的文件描述符就是一个整数,这个数字就是文件描述符表的索引,表项里面存着对应的打开文件对象的指针,所以进程操作打开文件的系统调用只需要传递一个文件描述符即可。由内核来维护打开文件对象,进程只能看到文件描述符这个整数
7. address_space也是一个重要的对象,它表示一个文件在页缓存中已经缓存了的物理页,内部维护了一个树结构来指向所有的物理页结构page,同时维护了一个host指针指向inode对象来获得文件的元数据。会在说页缓存的时候再来看address_space