2020-08-25 Linux虚拟文件系统(VFS)
1、概述
- 什么是虚拟文件系统?
虚拟文件系统(VFS,virtual filesystem),是一个内核软件层,是物理文件系统与服务之间的一个接口层,它对Linux的每个文件系统的所有细节进行抽象,使得不同的文件系统在Linux核心以及系统中运行的其他进程看来,都是相同的。严格说来,VFS并不是一种实际的文件系统。它只存在于内存中,不存在于任何外存空间。VFS在系统启动时建立,在系统关闭时消亡。
VFS支持文件系统主要有三种类型:
- 磁盘文件系统:管理本地磁盘分区中可用的存储空间或者其他可以起到磁盘作用的的设备(USB闪存)。常见磁盘文件系统有Ext2,Ext3,SystemV,BSD,等。
- 网络文件系统: 访问属于其他网络计算机的文件系统所包含的文件。常用的网络文件系统有NFS,AFS,CIFS,等。
- 特殊文件系统:不管理本地或者远程磁盘空间。/proc文件系统是特殊文件系统的一个典型的范例。
-
基本概念
文件: 一组在逻辑上具有完整意义的信息项的系列。在Linux中,除了普通文件,其他诸如目录、设备、套接字等 也以文件被对待。总之,“一切皆文件”。
目录: 目录好比一个文件夹,用来容纳相关文件。因为目录可以包含子目录,所以目录是可以层层嵌套,形成文件路径。在Linux中,目录也是以一种特殊文件被对待的,所以用于文件的操作同样也可以用在目录上。
目录项: 在一个文件路径中,路径中的每一部分都被称为目录项;如路径/home/source/helloworld.c中,目录 /, home, source和文件 helloworld.c都是一个目录项。
索引节点: 用于存储文件的元数据的一个数据结构。文件的元数据,也就是文件的相关信息,和文件本身是两个不同的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。
超级块: 用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节 点数等,存放于磁盘的特定扇区中。
如上的几个概念在磁盘中的位置关系如下图所示。
磁盘与文件系统
关于文件系统的三个易混淆的概念:
创建: 以某种方式格式化磁盘的过程就是在其之上建立一个文件系统的过程。创建文现系统时,会在磁盘的特定位置写入关于该文件系统的控制信息。
**注册: **向内核报到,声明自己能被内核支持。一般在编译内核的时侯注册;也可以加载模块的方式手动注册。注册过程实际上是将表示各实际文件系统的数据结构struct file_system_type 实例化。
安装: 也就是我们熟悉的mount操作,将文件系统加入到Linux的根文件系统的目录树结构上;这样文件系统才能被访问。
2、通用文件模型
前边提到,VFS对Linux的每个文件系统的所有细节进行抽象,使得不同的文件系统在Linux核心以及系统中运行的其他进程看来,都是相同的。把这个抽象的结构称为通用文件模型。在通用文件模型中,每个目录都被看做是一个文件,可以包含若干目录和文件。
通用文件模型由下列对象组成:
- 超模块对象(superblock object)
存放已安装文件系统的有关信息,对基于磁盘的文件系统,这类对象通常对应于存放在磁盘上的文件系统的控制块(filesystem contorl block).
- 索引节点对象(inode object)
存放关于具体文件的一般信息,对基于磁盘的文件系统,这类对象通常对应于存放在磁盘上的文件控制块(file contorl block),每个索引节点,都有一个索引节点号,这个节点号,唯一的标识文件系统中的文件。
- 目录项对象(dentry object)
存放目录项(文件的特定名称)与对应文件进行链接的有关信息。每个磁盘文件系统都以自己特有的方式将该信息存在磁盘上。
- 文件对象(file object)
存放打开文件与进程之间进行的交互的有关信息,这类信息仅当进程访问文件期间存在于内核内存中。
- 目录项高速缓存(dentry cache)
它属于磁盘高速缓存,用于存放最近最常使用的目录项对象。以加速从文件路径名到最后一个路径分量的索引节点的转换过程。
如下图,说明进程怎样和文件进行交互。三个不同的进程,打开同一个文件,其中,两个进程使用同一个硬链接。在这中情况下,每个进程都使用自己的文件对象,单只需要2个目录项对象,每个硬链接对应一个目录项对象,这2个目录项对象指向同一个索引节点对象,该索引节点对象标识超级块对象,以及随后的普通磁盘文件。
3、VFS数据结构
3.1超级对象块
超级对象块用来存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时, 内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。 超级块通过其结构中的一个域s_type记录它所属的文件系统类型。其结构如下
struct super_block {
struct list_head s_list;/* 指向所有超级块的链表 */
dev_t s_dev;/* 设备标识符 */
unsigned char s_dirt;/* 修改(脏)标志*/
unsigned char s_blocksize_bits;/* 以位为单位的块的大小 */
unsigned long s_blocksize;/* 以字节为单位的块的大小 */
loff_t s_maxbytes;/* 文件大小上限 */
struct file_system_type s_type;/ 文件系统类型 */
const struct super_operations s_op;/ 超级块方法 */
const struct dquot_operations dq_op;/ 磁盘限额方法 */
const struct quotactl_ops s_qcop;/ 限额控制方法 */
const struct export_operations s_export_op;/ 导出方法 */
unsigned long s_flags;/* 挂载标志 */
unsigned long s_magic;/* 文件系统的幻数 */
struct dentry s_root;/ 目录挂载点 */
struct rw_semaphore s_umount;/* 卸载信号量 */
struct mutex s_lock;/* 超级块信号量 */
int s_count;/* 超级块引用计数 */
atomic_t s_active;/* 活动引用计数 */
void s_security;/ 安全模块 */
const struct xattr_handler *s_xattr;/ 扩展的属性操作 */
struct list_head s_inodes;/* inodes 链表 */
struct hlist_head s_anon;/* 匿名目录项 */
struct list_head s_files;/* 被分配文件列表 */
struct list_head s_dentry_lru;/* 未使用目录项链表 */
int s_nr_dentry_unused;/* 链表中目录项的数目 */
struct block_device s_bdev;/ 相关的块文件 */
struct backing_dev_info s_bdi;/ */
struct mtd_info s_mtd;/ 存储磁盘信息 */
struct list_head s_instances;/* 该文件类型系统 */
struct quota_info s_dquot;/* 限额相关选项 */
int s_frozen;/* frozen标志位 */
wait_queue_head_t s_wait_unfrozen;/* 冻结的等待队列 */
char s_id[32];/* 文本名字 */
void s_fs_info;/ 文件系统特殊信息 */
fmode_t s_mode;/* 安装权限 */
u32 s_time_gran;/* 时间戳粒度 */
struct mutex s_vfs_rename_mutex;/* 重命名信号量 */
char s_subtype;/ 子类型名称 */
char s_options;/ 已存安装选项 */
};
3.2索引节点对象
索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个 文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操 作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。
<pre style="overflow-wrap: break-word; margin: 0px; padding: 0px; white-space: normal; background: rgb(84, 82, 82); color: rgb(238, 238, 238); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">struct inode { //........ unsigned long i_ino; //节点号 atomic_t i_count; //引用计数 //........ uid_t i_uid; //使用者id gid_t i_gid; //使用者id组 //........ struct timespec i_atime; //最后访问时间 struct timespec i_mtime; //最后修改(modify)时间 struct timespec i_ctime; //最后改变(change)时间 unsigned long i_blocks; //文件的块数 unsigned short i_bytes; //使用的字节数 unsigned long i_state; //状态标志 struct list_head i_devices; //块设备链表 unsigned char i_sock; //是否套接字 atomic_t i_writecount; //写者计数 //........ };</pre>
3.3目录项对象
引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是普通的文件,都是一个目录项对象。如,在路径/home/source/test.c中,目录 /, home, source和文件 test.c都对应一个目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS在遍历路径名的过程中现场将它们逐个地解析成目录项对象。
struct dentry {
atomic_t d_count; //目录项对象使用计数器,可以有未使用态,使用态和负状态
unsigned int d_flags; //目录项标志
struct inode *d_inode; //与文件名关联的索引节点
struct dentry *d_parent; //父目录的目录项对象
struct list_head d_hash; //散列表表项的指针
struct list_head d_lru; //未使用链表的指针
struct list_head d_child; //父目录中目录项对象的链表的指针
struct list_head d_subdirs; //对目录而言,表示子目录目录项对象的链表
struct list_head d_alias; //相关索引节点(别名)的链表
int d_mounted; //对于安装点而言,表示被安装文件系统根项
struct qstr d_name; //文件名
unsigned long d_time; /* used by d_revalidate */
struct dentry_operations *d_op; //目录项方法
struct super_block *d_sb; //文件的超级块对象
vunsigned long d_vfs_flags;
void *d_fsdata; //与文件系统相关的数据
unsigned char d_iname [DNAME_INLINE_LEN]; //存放短文件名
};
3.4文件对象
文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由sys_open() 现场创建,由sys_close()销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。当我们站在用户空间来看 待VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作 同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件,它 反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和 目录项对象无疑是惟一的。
<pre style="overflow-wrap: break-word; margin: 0px; padding: 0px; white-space: normal; background: rgb(84, 82, 82); color: rgb(238, 238, 238); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">struct file { union { struct list_head fu_list; //文件对象链表指针linux/include/linux/list.h struct rcu_head fu_rcuhead; //RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制 } f_u; struct path f_path; //包含dentry和mnt两个成员,用于确定文件路径 #define f_dentry f_path.dentry //f_path的成员之一,当前文件的dentry结构 #define f_vfsmnt f_path.mnt //表示当前文件所在文件系统的挂载根目录 const struct file_operations *f_op; //与该文件相关联的操作函数 atomic_t f_count; //文件的引用计数(有多少进程打开该文件) unsigned int f_flags; //对应于open时指定的flag mode_t f_mode; //读写模式:open的mod_t mode参数 off_t f_pos; //该文件在当前进程中的文件偏移量 struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。 unsigned int f_uid, f_gid; //文件所有者id,所有者组id struct file_ra_state f_ra; //在linux/include/linux/fs.h中定义,文件预读相关 unsigned long f_version; //记录文件的版本号,每次使用后都自动递增。 #ifdef CONFIG_SECURITY void *f_security; //用来描述安全措施或者是记录与安全有关的信息。 #endif /* needed for tty driver, and maybe others */ void *private_data; //可以用字段指向已分配的数据 #ifdef CONFIG_EPOLL /* Used by fs/eventpoll.c to link all the hooks to this file */ struct list_head f_ep_links; 文件的事件轮询等待者链表的头, spinlock_t f_ep_lock; f_ep_lock是保护f_ep_links链表的自旋锁。 #endif /* #ifdef CONFIG_EPOLL */ struct address_space *f_mapping; 文件地址空间的指针 };</pre>
<pre style="overflow-wrap: break-word; margin: 0px; padding: 0px; white-space: normal; background: rgb(84, 82, 82); color: rgb(238, 238, 238); font-size: 16px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">struct files_struct { atomic_t count; /* 共享该表的进程数 */ rwlock_t file_lock; /* 保护该结构体的锁*/ int max_fds; /*当前文件对象的最大数*/ int max_fdset; /*当前文件描述符的最大数*/ int next_fd; /*已分配的文件描述符加1*/ struct file ** fd; /* 指向文件对象指针数组的指针 */ fd_set *close_on_exec; /*指向执行exec()时需要关闭的文件描述符*/ fd_set *open_fds; /*指向打开文件描述符的指针*/ fd_set close_on_exec_init; /* 执行exec()时关闭的初始文件*/ fd_set open_fds_init; /*文件描述符的初值集合*/ struct file * fd_array[32]; /* 文件对象指针的初始化数组*/ };</pre>