细说 Linux 虚拟文件系统原理
在 Unix 的世界里,有句很经典的话:一切对象皆是文件。这句话的意思是说,可以将 Unix 操作系统中所有的对象都当成文件,然后使用操作文件的接口来操作它们。Linux 作为一个类 Unix 操作系统,也努力实现这个目标。
虚拟文件系统简介
为了实现
一切对象皆是文件
这个目标,Linux 内核提供了一个中间层:虚拟文件系统(Virtual File System)
。如果大家使用过面向对象编程语言(如C++/Java等)的话,应该对
接口
这个概念并不陌生。而虚拟文件系统类似于面向对象中的接口,定义了一套标准的接口。开发者只需要实现这套接口,即可以使用操作文件的接口来操作对象。如下图所示:上图中的蓝色部分就是虚拟文件系统所在位置。
从上图可以看出,虚拟文件系统为上层应用提供了统一的接口。如果某个文件系统实现了虚拟文件系统的接口,那么上层应用就能够使用诸如
open()
、read()
和 write()
等函数来操作它们。今天,我们就来介绍虚拟文件系统的原理与实现。
虚拟文件系统原理
在阐述虚拟文件系统的原理前,我们先来介绍一个 Java 例子。通过这个 Java 例子,我们能够更容易理解虚拟文件系统的原理。
一个Java例子
如果大家使用过 Java 编写程序的话,那么就很容易理解虚拟文件系统了。我们使用 Java 的接口来模拟虚拟文件系统的定义:
publicinterfaceVFSFile{
intopen(String file, int mode);
intread(int fd, byte[] buffer, int size);
intwrite(int fd, byte[] buffer, int size);
...
}
上面定义了一个名为
VFSFile
的接口,接口中定义了一些方法,如 open()
、read()
和 write()
等。现在我们来定义一个名为 Ext3File
的对象来实现这个接口:publicclassExt3FileimplementsVFSFile{
@Override
publicintopen(String file, int mode){
...
}
@Override
publicintread(int fd, byte[] buffer, int size){
...
}
@Override
publicintwrite(int fd, byte[] buffer, int size){
...
}
...
}
现在我们就能使用
VFSFile
接口来操作 Ext3File
对象了,如下代码:public class Main(){
publicstaticvoidmain(String[] args){
VFSFile file = new Ext3File();
int fd = 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) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, constchar __user *, size_t, loff_t *);
...
int (*open) (struct inode *, struct file *);
...
};
从
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);
...
return f;
}
设置好
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_t i_uid; // 文件所属用户
gid_t i_gid; // 文件所属组
...
structtimespeci_atime;// 最后访问时间
structtimespeci_mtime;// 最后修改时间
structtimespeci_ctime;// 文件创建时间
...
unsigned short i_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);
...
return f;
}
总结
本文主要介绍了
虚拟文件系统
的基本原理,从分析中可以发现,虚拟文件系统使用了类似于面向对象编程语言中的接口概念。正是有了 虚拟文件系统
,Linux 才能支持各种各样的文件系统。- EOF -
加主页君微信,不仅Linux技能+1
主页君日常还会在个人微信分享Linux相关工具、资源和精选技术文章,不定期分享一些有意思的活动、岗位内推以及如何用技术做业余项目
加个微信,打开一扇窗
看完本文有收获?请分享给更多人
推荐关注「Linux 爱好者」,提升Linux技能
点赞和在看就是最大的支持❤️
阅读原文 最新评论
推荐文章
作者最新文章
你可能感兴趣的文章
Copyright Disclaimer: The copyright of contents (including texts, images, videos and audios) posted above belong to the User who shared or the third-party website which the User shared from. If you found your copyright have been infringed, please send a DMCA takedown notice to [email protected]. For more detail of the source, please click on the button "Read Original Post" below. For other communications, please send to [email protected].
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。
版权声明:以上内容为用户推荐收藏至CareerEngine平台,其内容(含文字、图片、视频、音频等)及知识版权均属用户或用户转发自的第三方网站,如涉嫌侵权,请通知[email protected]进行信息删除。如需查看信息来源,请点击“查看原文”。如需洽谈其它事宜,请联系[email protected]。