Linux 文件系统的灵活性和扩展性支持直接源于一组抽象接口。这组接口的核心就是虚拟文件系统交换器(VFS)。
VFS 为上层应用程序提供一组标准接口,用于对不同的文件系统执行文件 I/O。这组接口在一个或多个底层设备上支持多个并发文件系统。另外,这些文件系统可以不是静态的,可以根据储存设备而变化。
VF 与 VFS
您还可以看到 VFS 还被定义为虚拟文件系统(virtual file system),但定义为虚拟文件系统转换器(virtual file system switch)更能说明其作用,因为虚拟层跨多个文件系统转换(即多路复用)请求。/proc
文件系统在这里带来很多的混淆,因为它通常也被称为虚拟文件系统。
例如,一个典型的 Linux 桌面在可用硬盘上支持 ext3 文件系统,并且在可用的 CD-ROM(或者称为 CD-ROM 文件系统 或 CDFS)上支持 ISO 9660 文件系统。因为 CD-ROM 是可以插入和移除的,所以 Linux 内核必须适应这些包含不同内容和结构的新文件系统。可以通过网络文件系统(Network File System,NFS)访问远程文件系统。在此时,Linux 还可以挂载来自本地硬盘的 Windows®/Linux 双引导系统的 NT File System (NTFS) 分区,并且能够向其读写数据。
最后,可移除的 USB flash 启动(UFD)是可以热插拔的,它构成另一个文件系统。概而言之,可以在这些设备中使用同一组文件 I/O 接口,从而允许底层的文件系统和物理设备能够从用户中抽象出来(见图 1)。
现在,我们向 Linux VFS 提供的抽象特性添加一些具体的架构。图 2 从 VFS 的角度显示 Linux 结构的高级视图。在 VFS 之上的是标准的内核系统调用接口(SCI)。这个接口允许用户空间发出要求转换到内核的调用(在不同地址空间中)。在这个域中,调用 POSIX open
调用的用户空间应用程序经过 GNU C 库(glibc
)进入内核和系统调用去多元化(de-multiplexing)。最后,使用 sys_open
调用 VFS。
早期的 VFS 实现
Linux 并不是第一个包含虚拟层以支持通用文件模型的操作系统。早期的 VFS 实现包括 Sun 的 VFS(SunOS version 2.0,大约出现在 1985 年),以及 IBM 和 Microsoft® 的 “Installable File System” for IBM OS/2。这些虚拟化文件系统层的方法为 Linux VFS 铺平了道路。
VFS 提供抽象层,从而将 POSIX API 与特定文件系统如何实现该行为的细节分离开来。这里的关键之处是,不管底层文件系统是 ext3 还是 Btrfs,Open、Read、Write 或 Close API 系统调用都能正常工作。VFS 提供一个由底层文件系统(它们必须为各种 POSIX API 函数实现行为)继承的通用文件模型。另一个在 VFS 范围之外的深层抽象隐藏了底层物理设备(可能是磁盘、磁盘分区、网络储存实体、内存或其他能够储存信息的媒介 —— 即使是暂时性的)。
除了从底层文件系统抽象文件操作的细节之外,VFS 还将底层块设备绑定到可用的文件系统。让我们看看 VFS 的内部结构及其工作原理。
在查看 VFS 子系统的总体架构之前,我们先看看所使用的主要对象。这个小节探索了超块(superblock)、索引节点(或 inode)、目录条目(或 dentry)和文件对象。在这里,其他一些组成部分也很重要,比如缓存。不过我将在后面的总体架构中讨论它们。
超块(superblock)是关于文件系统的高级元数据的容器。超块是存在于磁盘上(实际上位于磁盘的多个位置上,以提供冗余)的结构。它是处理磁盘上的文件系统的基础,因为它定义文件系统的管理参数(例如,块的总数、空闲块和根索引节点)。
在磁盘上,超块向内核提供关于磁盘上的文件系统的结构的信息。在内存中,超块为管理活动的(已挂载)文件系统提供必要的信息和状态。因为 Linux 支持同时挂载多个并发文件系统,所以在一个列表中维护每个 super_block
结构(super_blocks
在 ./linux/fs/super.c 中定义,结构在 /linux/include/fs/fs.h 中定义)。
图 3 提供了超块及其元素的简化视图。super_block
结构是指封装了其他信息的许多其他结构。例如,file_system_type
结构维护文件系统的名称(比如 ext3)以及各种锁和函数,以获取和删除 super_block
。file_system_type
对象由常见的 register_file system
和 unregister_file system
函数管理(见 ./linux/fs/file systems.c)。super_operations
结构为读写节点和高级操作(比如重新挂载)定义大量函数。根目录条目(dentry
)对象也缓存在这里,因为它是文件系统所在的块设备。最后,提供许多用于管理节点的列表,包括 s_inodes
(列出所有节点的列表)、s_dirty
(列出所有脏节点的列表)、s_io
和 s_more_io
以及 s_files
(列出特定文件系统的所有打开文件的列表)。
注意,在内核内部,另一个称为 vfsmount
的管理对象提供关于已挂载的文件系统的信息。这些对象的列表引用超块,并定义挂载点、文件系统所在的 /dev 设备的名称以及其他高级附加信息。
Linux 通过一个称为 inode(index node 的缩写)的对象管理文件系统中的所有对象。一个 inode 可以引用一个文件、目录或另一个对象的符号链接。注意,因为文件用于表示其他类型的对象(比如设备或内存),所以也使用 inode 来表示它们。
我在这里所指的 inode 是 VFS 层 inode(常驻 inode)。每个文件系统也包含一个位于磁盘上的 inode,并且提供关于特定文件系统的特定对象的细节。
VFS inode 使用 slab 分配器进行分配(来自 inode_cache
;参考资料 部分提供一个介绍 slab 分配器的链接)。inode 由描述 inode、inode 内容和可能在 inode 上发生的各种操作的数据和操作组成。图 4 简单展示了一个 VFS inode,该 inode 包含许多列表,其中一个列表指向引用该 inode 的 dentry。这里还包含对象级别的元数据,包括熟悉的操作时间(创建时间、访问时间和修改时间)和所有者和权限数据(组 ID、用户 ID 和权限)。inode 引用它所允许的文件操作,大部分这些操作直接映射到系统调用接口(例如,open
、read
、write
和 flush
)。inode 还引用特定于 inode 的操作(create
、lookup
、link
和 mkdir
等等)。最后,对于由地址空间对象表示的对象的数据,有一个管理结构。地址空间对象 是为 inode 管理页缓存中的各种页的对象。地址空间对象用于为文件管理页,也用于将文件部分映射到独立的进程地址空间。地址空间对象有自己的操作集(writepage
、readpage
和 releasepage
等等)。
注意,可以在 ./linux/include/linux/fs.h 中找到所有这些信息。
文件系统的层次结构由 VFS 中的另一个称为 dentry 的对象管理。文件系统有一个根 dentry(在超块中引用),这是唯一没有父对象的 dentry。所有其他 dentry 都有父对象,并且一部分 dentry 有子对象。例如,如果打开一个由 /home/user/name 组成的文件,那么将创建 4 个 dentry 对象:一个针对根 /
、一个针对根目录 home
的条目、一个针对 user
目录的 name
条目,以及一个针对 user 目录的 name
条目。通过这种方式,dentry 简洁地映射到现在使用的文件系统。
dentry 对象由 dentry 结构定义(在 ./linux/include/fs/dcache.h 中)。它由许多元素组成,这些元素在文件系统和物理数据中跟踪条目之间的关系(比如文件名)。图 5 展示了 dentry 对象的简化图示。dentry 引用 super_block
,super_block
定义包含该对象的特定文件系统实例。接下来是该对象的父 dentry(父目录),其后是包含在一个列表中的子 dentry(如果该对象刚好是一个目录的话)。然后,为 dentry 定义操作(比如 hash
、compare
、delete
和 release
等等)。接着定义对象的名称,在这里名称保存在 dentry 中而不是 inode 中。最后,提供一个到 VFS inode 的引用。
注意,dentry 对象仅存在文件系统内存中,而不能储存在磁盘上。仅永久储存文件系统 inode,dentry 对象的目的是改善性能。您可以在 ./linux/include/dcache.h 中看到 dentry 的完整描述。
在 Linux 系统中打开的每个文件都都存在一个 file
对象。这个对象为特定用户提供打开的实例的信息。图 6 提供了文件对象的简化视图。在图中可以看到,path
结构提供到 dentry
和 vfsmount
的引用。为每个文件定义了一组文件操作,常见的文件操作包括 open
、close
、read
、write
和 flush
等。接着定义一组标志和权限(包括组和所有者)。最后,为特定文件实例定义状态数据,比如文件的当前偏移量。
我们已经查看了 VFS 层中的各种重要对象,现在我们通过一个图表展示它们之间的关系。到目前为止,我都是以一种自下而上的方式探索对象,现在我们采用自上而下方式,从用户透视图中考察对象(见 图 7)。
在顶层是打开的 file
对象,它由进程的文件描述符列表引用。file
对象引用 dentry
对象,后者引用 inode
。inode
和 dentry
对象都引用底层的 super_block
对象。可能有多个文件对象引用同一个 dentry(当两个用户共享同一个文件时)。注意,在图 7 中一个 dentry
对象还引用另一个 dentry
对象。在这里,目录引用文件,而文件反过来引用特定文件的 inode。
VFS 的内部架构由一个调度层(提供文件系统抽象)和许多缓存(用于改善文件系统操作的性能)组成。这个小节探索内部架构和主要对象之间的交互(见图 8)。
在 VFS 中动态管理的两个主要对象是 dentry
和 inode
对象。缓存这两个对象,以改善访问底层文件系统的性能。当打开一个文件时,dentry 缓存将被表示目录级别(目录级别表示路径)的条目填充。此外,还为该对象创建一个表示文件的 inode。使用散列表创建 dentry 缓存,并且根据对象名分配缓存。dentry 缓存的条目从 dentry_cache
slab 分配器分配,并且在缓存存在压力时使用最近不使用(least-recently-used,LRU)算法删除条目。您可以在 ./linux/fs/dcache.c 和 ./linux/include/linux/dcache.h 中找到与 dentry 缓存相关的函数。
为了实现更快的查找速度,inode 缓存被实现为两个列表和一个散列表。第一个列表定义当前使用的 inode;第二个列表定义未使用的 inode。正在使用的 inode 还储存在散列表中。从 inode_cache
slab 分配器分配单个 inode 缓存对象。您可以在 ./linux/fs/inode.c 和 ./linux/include/fs.h 中找到与 inode 缓存相关的函数。在现在的实现中,dentry 缓存支配着 inode 缓存。如果存在一个 dentry
对象,那么 inode 缓存中也将存在一个 inode
对象。查找是在 dentry 缓存中执行的,这将导致 inode 缓存中出现一个对象。
本文探索了 VFS 的基础概念,以及为访问不同文件系统提供统一接口的对象。Linux 具有很大的可伸缩性和灵活性,并且可以从子系统进行扩展。您可以从 参考资料 部分提供的链接更多地了解 VFS。
学习
- 用于管理文件系统的 Linux 子系统庞大复杂。您可以在 Tim 的 “Linux 文件系统剖析”(developerWorks,2007 年)中更多地了解更大的文件系统子系统。
- Tim 的 “剖析 ext4”(developerWorks,2009 年)介绍并概述了这个下一代的日志文件系统。
- Tim 的 “Linux slab 分配器详解”(developerWorks,2007 年 5 月)讨论了 Linux 如何通过 slab 分配管理内存。
- 从用户空间处理 VFS 涉及到系统调用接口,该接口管理在用户空间和内核之间转换的命令。您可以在 Tim 的 “使用 Linux 系统调用的内核命令”(developerWorks,2007 年)中更多地了解这个过程。
- IBM AIX® 操作系统提供了另一个 VFS 实现。文章 Virtual File System Overview 介绍了 AIX VFS 实现,并描述了它包含的各种对象。
- 来自 Institut für Informatik 的操作系统课程 Linux VFS 概述 出色地介绍了 VFS,并通过 ext2 文件系统探索了它的使用。印度孟买的 Indian Institute of Technology 的报告 简洁明了地介绍了 VFS。
- 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门)准备的更多参考资料,查阅我们 最受欢迎的文章和教程。
- 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程。
- 随时关注 developerWorks 技术活动和网络广播。
获得产品和技术
- 使用可直接从 developerWorks 下载的 IBM 产品评估试用版软件 构建您的下一个 Linux 开发项目。
讨论
- 加入 My developerWorks 社区;您可以通过个人信息和定制主页获得自己感兴趣的 developerWorks 文章,并与其他 developerWorks 用户进行交流。
M. Tim Jones 是一名嵌入式固件架构师,同时也是 Artificial Intelligence: A Systems Approach, GNU/Linux Application Programming(第二版)、AI Application Programming(第二版)和 BSD Sockets Programming from a Multilanguage Perspective 的作者。他的工程背景包括地球同步航天器内核开发、嵌入式系统架构和网络协议开发等。Tim 还是科罗拉多州朗蒙特市 Emulex Corp. 的顾问工程师。
- 本文固定链接: http://www.wy182000.com/2011/01/15/linux-虚拟系统文件交换器剖析/
- 转载请注明: wy182000 于 Studio 发表