在 Unix 的世界里,有句很经典的话:一切对象皆是文件。这句话的意思是说,可以将 Unix 操作系统中所有的对象都当成文件,然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统,也努力实现这个目标。
虚拟文件系统简介
为了实现一切对象皆是文件这个目标,Linux 内核提供了一个中间层:虚拟文件系统(Virtual File System)。
如果大家使用过面向对象编程语言(如C++/Java等)的话,应该对接口这个概念并不陌生。而虚拟文件系统类似于面向对象中的接口,定义了一套标准的接口。开发者只需要实现这套接口,即可以使用操作文件的接口来操作对象。如下图所示:
上图中的蓝色部分就是虚拟文件系统所在位置。
从上图可以看出,虚拟文件系统为上层应用提供了统一的接口。如果某个文件系统实现了虚拟文件系统的接口,那么上层应用就能够使用诸如open()、read()和write()等函数来操作它们。
今天,我们就来介绍虚拟文件系统的原理与实现。
虚拟文件系统原理
在阐述虚拟文件系统的原理前,我们先来介绍一个 Java 例子。通过这个 Java 例子,我们能够更容易理解虚拟文件系统的原理。
一个Java例子
如果大家使用过 Java 编写程序的话,那么就很容易理解虚拟文件系统了。我们使用 Java 的接口来模拟虚拟文件系统的定义:
publicinterfaceVFSFile{ intopen(Stringfile,intmode); intread(intfd,byte[]buffer,intsize); intwrite(intfd,byte[]buffer,intsize); ... }
上面定义了一个名为VFSFile的接口,接口中定义了一些方法,如open()、read()和write()等。现在我们来定义一个名为Ext3File的对象来实现这个接口:
publicclassExt3FileimplementsVFSFile{ @Override publicintopen(Stringfile,intmode){ ... } @Override publicintread(intfd,byte[]buffer,intsize){ ... } @Override publicintwrite(intfd,byte[]buffer,intsize){ ... } ... }
现在我们就能使用VFSFile接口来操作Ext3File对象了,如下代码:
publicclassMain(){ publicstaticvoidmain(String[]args){ VFSFilefile=newExt3File(); intfd=file.open("/tmp/file.txt",0); ... } }
从上面的例子可以看出,底层对象只需要实现VFSFile接口,就可以使用VFSFile接口相关的方法来操作对象,用户完全不需要了解底层对象的实现过程。
虚拟文件系统原理
上面的 Java 例子已经大概说明虚拟文件系统的原理,但由于 Linux 是使用 C 语言来编写的,而 C 语言并没有接口这个概念。所以,Linux 内核使用了一些技巧来模拟接口这个概念。
下面来介绍一下 Linux 内核是如何实现的。
1. file结构
为了模拟接口,Linux 内核定义了一个名为file的结构体,其定义如下:
structfile{ ... conststructfile_operations*f_op; ... };
在 file 结构中,最为重要的一个字段就是f_op,其类型为file_operations结构。而file_operations结构是由一组函数指针组成,其定义如下:
structfile_operations{ ... loff_t(*llseek)(structfile*,loff_t,int); ssize_t(*read)(structfile*,char__user*,size_t,loff_t*); ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*); ... int(*open)(structinode*,structfile*); ... };
从file_operations结构的定义可以隐约看到接口的影子,所以可以猜想出,如果实现了file_operations结构中的方法,应该就能接入到虚拟文件系统中。
在 Linux 内核中,file结构代表着一个被打开的文件。所以,只需要将file结构的f_op字段设置成不同文件系统实现好的方法集,那么就能够使用不同文件系统的功能。
这个过程在__dentry_open()函数中实现,如下所示:
staticstructfile* __dentry_open(structdentry*dentry, structvfsmount*mnt, tructfile*f, int(*open)(structinode*,structfile*), conststructcred*cred) { ... inode=dentry->d_inode; ... //设置file结构的f_op字段为底层文件系统实现的方法集 f->f_op=fops_get(inode->i_fop); ... returnf; }
设置好file结构的f_op字段后,虚拟文件系统就能够使用通用的接口来操作此文件了。调用过程如下:
2. file_operations结构
底层文件系统需要实现虚拟文件系统的接口,才能被虚拟文件系统使用。也就是说,底层文件系统需要实现file_operations结构中的方法集。
一般底层文件系统会在其内部定义好file_operations结构,并且填充好其方法集中的函数指针。如minix文件系统就定义了一个名为minix_file_operations的file_operations结构。其定义如下:
//文件:fs/minix/file.c conststructfile_operationsminix_file_operations={ .llseek=generic_file_llseek, .read=do_sync_read, .aio_read=generic_file_aio_read, .write=do_sync_write, .aio_write=generic_file_aio_write, .mmap=generic_file_mmap, .fsync=generic_file_fsync, .splice_read=generic_file_splice_read, };
也就是说,如果当前使用的是 minix 文件系统,当使用read()函数读取其文件的内容时,那么最终将会调用do_sync_read()函数来读取文件的内容。
3. dentry结构
到这里,虚拟文件系统的原理基本分析完毕,但还有两个非常重要的结构要介绍一下的:dentry和inode。
dentry结构表示一个打开的目录项,当我们打开文件/usr/local/lib/libc.so文件时,内核会为文件路径中的每个目录创建一个dentry结构。如下图所示:
可以看到,file结构有个指向dentry结构的指针,如下所示:
structfile{ ... structpathf_path; ... conststructfile_operations*f_op; ... }; structpath{ ... structdentry*dentry; };
与文件类似,目录也有相关的操作接口,所以在dentry结构中也有操作方法集,如下所示:
structdentry{ ... structdentry*d_parent;//父目录指针 structqstrd_name;//目录名字 structinode*d_inode;//指向inode结构 ... conststructdentry_operations*d_op;//操作方法集 ... };
其中的d_op字段就是目录的操作方法集。
内核在打开文件时,会为路径中的每个目录创建一个dentry结构,并且使用d_parent字段来指向其父目录项,这样就能通过d_parent字段来追索到根目录。
4. inode结构
在 Linux 内核中,inode结构表示一个真实的文件。为什么有了dentry结构还需要inode结构呢?这是因为 Linux 存在硬链接的概念。
例如使用以下命令为/usr/local/lib/libc.so文件创建一个硬链接:
ln/usr/local/lib/libc.so/tmp/libc.so
现在/usr/local/lib/libc.so和/tmp/libc.so指向同一个文件,但它们的路径是不一样的。所以,就需要引入inode结构了。如下图所示:
由于/usr/local/lib/libc.so和/tmp/libc.so指向同一个文件,所以它们都使用同一个inode对象。
inode 结构保存了文件的所有属性值,如文件的创建时间、文件所属用户和文件的大小等。其定义如下所示:
structinode{ ... uid_ti_uid;//文件所属用户 gid_ti_gid;//文件所属组 ... structtimespeci_atime;//最后访问时间 structtimespeci_mtime;//最后修改时间 structtimespeci_ctime;//文件创建时间 ... unsignedshorti_bytes;//文件大小 ... conststructfile_operations*i_fop;//文件操作方法集(用于设置file结构) ... };
我们注意到 inode 结构有个类型为file_operations结构的字段i_fop,这个字段保存了文件的操作方法集。当用户调用open()系统调用打开文件时,内核将会使用inode结构的i_fop字段赋值给file结构的f_op字段。我们再来重温下赋值过程:
staticstructfile* __dentry_open(structdentry*dentry, structvfsmount*mnt, tructfile*f, int(*open)(structinode*,structfile*), conststructcred*cred) { ... //文件对应的inode对象 inode=dentry->d_inode; ... //使用inode结构的i_fop字段赋值给file结构的f_op字段 f->f_op=fops_get(inode->i_fop); ... returnf; }
总结
本文主要介绍了虚拟文件系统的基本原理,从分析中可以发现,虚拟文件系统使用了类似于面向对象编程语言中的接口概念。正是有了虚拟文件系统,Linux 才能支持各种各样的文件系统。
审核编辑:刘清
-
JAVA
+关注
关注
19文章
2956浏览量
104531 -
UNIX操作系统
+关注
关注
0文章
13浏览量
15298 -
C语言
+关注
关注
180文章
7597浏览量
136117 -
LINUX内核
+关注
关注
1文章
316浏览量
21614
原文标题:细说 Linux 虚拟文件系统原理
文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论