标签:
版权声明:本文为博主原创文章,未经博主允许不得转载。
Linux虚拟文件系统是一个内核软件层,用来处理与UNIX标准文件系统相关的所有系统调用。其健壮性表现在能为各种文件系统提供一个通用的接口。
Linux虚拟文件系统支持的文件系统可以划分为三种主要的类型:
1.磁盘文件系统
这些文件系统管理在本地磁盘分区中可用的磁盘空间或者其他可以起到磁盘作用的设备(比如说一个USB闪存)。
2.网络文件系统
如nfs,这些文件系统允许访问属于其他网络计算机的文件系统所包含的文件。
3.特殊文件系统
这些文件系统不管理本地或者远程磁盘空间
Glusterfs作为一个分布式文件系统,也是基于VFS实现了文件系统的相关接口,实现的两种协议Gluster Native Client(fuse)和nfs直接实现了VFS相关接口,并且在此基础之上实现了分布式相关功能。因此我们对VFS理解的深度直接会影响到glusterfs对文件操作的深度,当然VFS的操作也是由内核完成调度的。
VFS在单台机器中的位置
VFS在glusterfs中的位置
接下来,我们将对VFS一些重要部分进行简单分析。
从本质上讲,文件系统是特殊的数据分层存储结构,它包含文件、目录和相关的控制信息。为了描述这个结构,Linux引入了一些基本概念:
1)文件一组在逻辑上具有完整意义的信息项的系列。在Linux中,除了普通文件,其他诸如目录、设备、套接字等也以文件被对待。总之,“一切皆文件”。
2) 目录 目录好比一个文件夹,用来容纳相关文件。因为目录可以包含子目录,所以目录是可以层层嵌套,形成文件路径。在Linux中,目录也是以一种特殊文件被对待的,所以用于文件的操作同样也可以用在目录上。
3)目录项在一个文件路径中,路径中的每一部分都被称为目录项;如路径/home/source/helloworld.c中,目录 /, home, source和文件helloworld.c都是一个目录项。
索引节点用于存储文件的元数据的一个数据结构。文件的元数据,也就是文件的相关信息,和文件本身是两个不同的概念。它包含的是诸如文件的大小、拥有者、创建时间、磁盘位置等和文件相关的信息。
4) 超级块 用于存储文件系统的控制信息的数据结构。描述文件系统的状态、文件系统类型、大小、区块数、索引节点数等,存放于磁盘的特定扇区中。
VFS依靠四个主要的数据结构和一些辅助的数据结构来描述其结构信息,这些数据结构表现得就像是对象; 每个主要对象中都包含由操作函数表构成的操作对象,这些操作对象描述了内核针对这几个主要的对象可以进行的操作。
存储一个已安装的文件系统的控制信息,代表一个已安装的文件系统;每次一个实际的文件系统被安装时, 内核会从磁盘的特定位置读取一些控制信息来填充内存中的超级块对象。一个安装实例和一个超级块对象一一对应。超级块通过其结构中的一个域s_type记录它所属的文件系统类型。下面给出它的结构体和各个域的描述:
/*存放已安装文件系统的有关信息,通常对应于存放在磁盘上的文件系统控制块 每挂载一个文件系统对应一个超级块对象*/ struct super_block { struct list_head s_list;/*指向超级块链表的指针*//* Keep this first */ dev_t s_dev;/*设备标识符*//* search index; _not_ kdev_t */ unsigned long s_blocksize;/*以字节为单位的块大小*/ unsigned char s_blocksize_bits;/*以位为单位的块大小*/ unsigned char s_dirt;/*修改标志*/ loff_t s_maxbytes;/*文件的最长长度*/ /* Max file size */ 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;/*安装标识*/ 。。。。。。。。。。。。。。。。。。。。。。 };
struct super_operations { //超级块方法 …… //该函数在给定的超级块下创建并初始化一个新的索引节点对象 struct inode *(*alloc_inode)(struct super_block *sb); …… //该函数从磁盘上读取索引节点,并动态填充内存中对应的索引节点对象的剩余部分 void (*read_inode) (struct inode *); …… }; |
索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供对一个文件进行操作时所必需的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。
/*索引节点,要访问一个文件时,一定要通过他的索引才能知道 这个文件是什么类型的文件、是怎么组织的、文件中存储这 多少数据、这些数据在什么地方以及其下层的驱动程序在哪儿等 必要的信息*/ struct inode { struct hlist_node i_hash;/*用于散列链表的指针*/ struct list_head i_list;/*用于描述索引节点当前状态的链表指针*/ /* backing dev IO list */ struct list_head i_sb_list;/*用于超级快的索引节点链表的指针*/ struct list_head i_dentry;/*引用索引节点的目录项对象链表头*/ unsigned long i_ino;/*索引节点号*/ atomic_t i_count;/*引用计数器*/ unsigned int i_nlink;/*硬链接数目*/ uid_t i_uid;/*所有者标识符*/ gid_t i_gid;/*主标识符*/ dev_t i_rdev;/*实设备标识符*/ u64 i_version;/*版本号,每次使用后自动增加*/ loff_t i_size;/*文件的字节数*/ 。。。。。。。。。。。。。。。。。。。。。 };
struct inode_operations { //索引节点方法 …… //该函数为dentry对象所对应的文件创建一个新的索引节点,主要是由open()系统调用来调用 int (*create) (struct inode *,struct dentry *,int, struct nameidata *);
//在特定目录中寻找dentry对象所对应的索引节点 struct dentry * (*lookup) (struct inode *,struct dentry *, struct nameidata *); …… }; |
引入目录项的概念主要是出于方便查找文件的目的。一个路径的各个组成部分,不管是目录还是 普通的文件,都是一个目录项对象。如,在路径/home/source/test.c中,目录/, home, source和文件 test.c都对应一个目录项对象。通过目录项的名称通过散列表和相应的散列函数来快速地将其解析为相关的目录项对象。不同于前面的两个对象,目录项对象没有对应的磁盘数据结构,VFS在遍历路径名的过程中现场将它们逐个地解析成目录项对象。
struct dentry {//目录项结构 …… struct inode *d_inode; /*相关的索引节点*/ struct dentry *d_parent; /*父目录的目录项对象*/ struct qstr d_name; /*目录项的名字*/ …… struct list_head d_subdirs; /*子目录*/ …… struct dentry_operations *d_op; /*目录项操作表*/ struct super_block *d_sb; /*文件超级块*/ …… };
struct dentry_operations { //判断目录项是否有效; int (*d_revalidate)(struct dentry *, struct nameidata *); //为目录项生成散列值; int (*d_hash) (struct dentry *, struct qstr *); …… }; |
文件对象是已打开的文件在内存中的表示,主要用于建立进程和磁盘上的文件的对应关系。它由sys_open() 现场创建,由sys_close()销毁。文件对象和物理文件的关系有点像进程和程序的关系一样。当我们站在用户空间来看待VFS,我们像是只需与文件对象打交道,而无须关心超级块,索引节点或目录项。因为多个进程可以同时打开和操作 同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件,它反过来指向目录项对象(反过来指向索引节点)。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和 目录项对象无疑是惟一的。
struct file { …… struct list_head f_list; /*文件对象链表*/ struct dentry *f_dentry; /*相关目录项对象*/ struct vfsmount *f_vfsmnt; /*相关的安装文件系统*/ struct file_operations *f_op; /*文件操作表*/ …… }; struct file_operations { …… //文件读操作 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); …… //文件写操作 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); …… int (*readdir) (struct file *, void *, filldir_t); …… //文件打开操作 int (*open) (struct inode *, struct file *); …… }; |
每个已经挂装的文件系统用挂接点对象结构vfsmout描述,所有的结构vfsmount实例形成了一个链表,用全局变量vfsmntlist指向链表头,该链表可称为已挂接文件系统链表。Linux支持一个文件系统挂接多次,但他们仅有一个超级块,每个挂接点用挂接点结构vfsmount描述。
struct vfsmount { struct list_head mnt_hash; struct vfsmount *mnt_parent; /* fs we are mounted on */ struct dentry *mnt_mountpoint;/*安装点的目录,该目录属于安装到的文件系统*/ /* dentry of mountpoint */ struct dentry *mnt_root;/*安装的文件系统根目录*/ /* root of the mounted tree */ struct super_block *mnt_sb; /* pointer to superblock */ struct list_head mnt_mounts; /* list of children, anchored here */ struct list_head mnt_child; /* and going through their mnt_child */ …… }; |
Linux内核支持多种文件系统,各个文件系统可以作为内核模块或者作为内核一部分进行编译,linux内核使用文件系统类型结构file_system_type对各种文件系统进行跟踪。
struct file_system_type { const char *name;/*文件系统的名称*/ int fs_flags;/*文件系统类型标志*/ int (*get_sb) (struct file_system_type *, int, const char *, void *, struct vfsmount *);/*读超级快的方法*/ void (*kill_sb) (struct super_block *);/*删除超级块的方法*/ struct module *owner;/*指向实现文件系统的模块指针*/ struct file_system_type * next;/*指向文件系统类型链表中下一个元素的指针*/ struct list_head fs_supers;/*具有相同文件系统类型的超级块对象链表头*/ …… }; |
该部分在glusterfs中是需要我们明白的,所以在此处列出来
目录项对象有三中状态:被使用,未被使用和负状态。一个被使用的目录项对应一个有效的索引节点(即d_inode指向相应的索引节点),并且表明该对象存在一个或多个使用者。
一个未被使用的目录项对应一个有效的索引节点(d_inode指向一个索引节点),但是VFS当前没有打开它(d_count为0),一个负状态目录项没有对应的有效索引节点(d_inode为NULL)
如果VFS层遍历路径名中所有的元素并将他们逐个地解析成目录对象,这将是一件非常费力的工作,会浪费大量时间。内核将目录项对象缓存在目录项缓存(简称dcache)中,最近最常使用的目录项对象被放在所谓的目录项高速缓存的磁盘高速缓存中,以加速从文件路径名到最后一个路径分量的索引节点的转换过程。
我们通常所说的文件描述符其实是进程打开的文件对象数组的索引值。比如对于一个文件/hello.txt,文件对象通过域f_dentry(文件名)找到它对应的dentry对象,再由dentry对象的域d_inode(文件inode)找到它对应的索引结点,这样就建立了文件对象与实际的物理文件的关联。最后,还有一点很重要的是, 文件对象所对应的文件操作函数 列表是通过索引结点的域i_fop(文件操作)得到的。
到此,我们也就能够解释在Linux中为什么能够跨文件系统地操作文件了。举个例子,将vfat格式的磁盘上的一个文件a.txt拷贝到ext3格式的磁盘上,命名为b.txt。这包含两个过程,对a.txt进行读操作,对b.txt进行写操作。读写操作前,需要先打开文件。由前面的分析可知,打开文件时,VFS会知道该文件对应的文件系统格式,以后操作该文件时,VFS会调用其对应的实际文件系统的操作方法。所以,VFS调用vfat的读文件方法将a.txt的数据读入内存;在将a.txt在内存中的数据映射到b.txt对应的内存空间后,VFS调用ext3的写文件方法将b.txt写入磁盘;从而实现了最终的跨文件系统的复制操作。
3.2“一切皆是文件”的实现根本
不论是普通的文件,还是特殊的目录、设备等,VFS都将它们同等看待成文件,通过同一套文件操作界面来对它们进行操作。操作文件时需先打开;打开文件时,VFS会知道该文件对应的文件系统格式;当VFS把控制权传给实际的文件系统时,实际的文件系统再做出具体区分,对不同的文件类型执行不同的操作。这也就是“一切皆是文件”的根本所在。
VFS即虚拟文件系统是Linux文件系统中的一个抽象软件层;因为它的支持,众多不同的实际文件系统才能在Linux中共存,跨文件系统操作才能实现。VFS借助它四个主要的数据结构即超级块、索引节点、目录项和文件对象以及一些辅助的数据结构,向Linux中不管是普通的文件还是目录、设备、套接字等都提供同样的操作界面,如打开、读写、关闭等。只有当把控制权传给实际的文件系统时,实际的文件系统才会做出区分,对不同的文件类型执行不同的操作。由此可见,正是有了VFS的存在,跨文件系统操作才能执行,Unix/Linux中的“一切皆是文件”的口号才能够得以实现。
标签:
原文地址:http://blog.csdn.net/p656456564545/article/details/51636613