(转)虚拟文件系统(VFS)浅析

http://www.cnblogs.com/zsw-1993/p/5048144.html

在我看来, "虚拟"二字主要有两层含义:

1, 在同一个目录结构中, 可以挂载着若干种不同的文件系统. VFS隐藏了它们的实现细节, 为使用者提供统一的接口;

2, 目录结构本身并不是绝对的, 每个进程可能会看到不一样的目录结构. 目录结构是由"地址空间(namespace)"来描述的, 不同的进程可能拥有不同的namespace, 不同的namespace可能有着不同的目录结构(因为它们可能挂载了不同的文件系统).

操作已打开的文件

VFS的使用者是进程(用户访问文件系统总是需要启动进程). 描述进程的task_struct结构中files指针指向了一个files_struct结构, 后者描述了进程已打开的文件集合.

files_struct结构维护了一个已打开文件所对应的file结构的指针数组, 数组下标被用作用户程序操作已打开文件的句柄(通常称作fd). files_struct还维护着已使用的fd位图, 以便在需要打开文件时, 为其分配一个未使用的fd.

file结构是一个已打开文件实例. 用户程序通过fd操作一个已打开文件的过程比较简单, 由fd索引到对应的file结构, 再执行file结构的f_op中对应的操作即可(比如read, write).

不同的file结构可能拥有不同的f_op, 因为它们的文件类型不同(比如, 普通文件, socket, fifo, 等等).

而这个对应的f_op是在文件打开时被赋值的, 对于已打开的文件, 只管使用f_op中的函数即可, 不用再判断到底这个文件是什么类型. 而至于具体的f_op中的函数是如何实现的, 本文不作描述(实际上这一部分也是很复杂的, 参见<linux内核文件读写浅析>).

用户程序操作一个已打开的文件也未必就会调用到f_op中的函数, 有些操作是只涉及file结构本身的. 比如file结构中维护了文件的当前位置(f_pos), lseek系统调用只负责移动这个pos值.

类似f_pos, f_mode(文件的访问模式), 等这样的属性, 是存放在file结构中的, 这意味着这些属性都是跟一个已打开文件的实例相关的. 一个文件可能会打开多个实例(在一个或多个进程中), 每个实例中的这些值都有可能不同.

比如, 两个进程同时打开同一个文件, 进行读操作. 由于两个实例(file结构)对应的f_pos不同, 两个读操作互不影响.

而有时候多个进程也会共享同一个打开文件实例, 当使用clone系统调用创建子进程时, 如果设置了CLONE_FILES标志, 则父子进程将共享files_struct结构, 从而共享全部已打开的文件实例. 典型的例子是多线程.

打开文件

相比于对已打开文件的操作的简单, 打开一个文件的过程却是很复杂的. 从上面的图中也可以看出, 操作已打开的文件只占了很少的篇幅, 而其他的内容则都与打开文件有关.

要打开一个文件, 首先需要文件路径, 如"dir0/dir1/file". 这个路径被‘/‘拆分成多级, 每一级都是一个文件(目录也是文件, 如dir0, dir1).

在寻找这个文件路径的一开始, 我们需要一个起点. 如果文件路径以‘/‘开头, 则以根目录为起点; 否则以当前路径为起点.

这两个可能的起点都保存在进程的task_struct所对应的fs_struct结构中. 每个文件在目录结构中由目录项(dentry)结构来表示, "起点"本身也是一个dentry结构.

我们在shell中执行cd命令时, 实际上就是改变了fs_struct结构中代表当前路径的那个dentry.

进程也可以通过chroot系统调用来改变fs_struct结构中代表根路径的那个dentry. 这样一来, 这个dentry之上的那些路径对该进程将不可见.

作为文件的索引结构, 若干dentry描绘了一个树型的目录结构, 这就是用户所看到的目录结构. (我们暂且将其称为dentry树.)

每个dentry指向一个索引节点(inode)结构, 后者才是实际描述这个文件信息的结构. 而多个dentry可以指向同一个inode, 这样就实现了link.

dentry中实现了一组方法(d_op), 主要是用于匹配子节点. dentry实现了一个散列表, 以便于查找子节点.

d_op可能随文件系统类型的不同而不同, 比如, 散列方法可能不同, 节点的匹配方法也可能不同(有的文件系统文件名大小写敏感, 有的则不).

寻找文件路径的过程就是在这个dentry树中不断查找子dentry, 直到找到路径中的最后一个dentry的过程.

虽然dentry树描绘了文件系统的目录结构, 但是, 这些dentry结构并不是常驻内存的. 整个目录结构可能会非常大, 以致于内存根本装不下.

初始状态下, 系统中只有代表根目录的dentry和它所指向的inode(这是在根文件系统挂载时生成的, 见下文). 此时要打开一个文件, 文件路径中对应的节点都是不存在的, 根目录的dentry无法找到需要的子节点(它现在还没有子节点). 这时候就要通过inode->i_op中的lookup方法来寻找需要的inode的子节点(这往往是通过特定的文件系统类型定义的方法, 从文件系统存储介质中去查找的。参见《linux文件系统实现浅析》), 找到以后(此时inode已被载入内存), 再创建一个dentry与之关联上.

由这一过程可见, 其实是先有inode再有dentry. inode本身是存在于文件系统的存储介质上的, 而dentry则是在内存中生成的. dentry的存在加速了对inode的查询.

既然整个目录结构可能不能全部载入内存, 在内存中生成的dentry将在无人使用时被释放. d_count字段记录了dentry的引用计数, 引用为0时, dentry将被释放.

这里所谓的释放dentry并不是直接销毁并回收, 而是将dentry放入一个"最近最少使用(LRU)"队列(与对应的超级块相关联). 当队列过大, 或系统内存紧缺时, 最近最少使用的一些dentry才真正被释放.

这个LRU队列就像是一个缓存池, 加速了对重复的路径的访问. 而当dentry被真正释放时, 它所对应的inode将被减引用. 如果引用为0, inode也被释放.

当寻找一个文件路径时, 对于其中经历的每一个节点, 有三种情况:

1, 对应的dentry引用计数尚未减为0, 它们还在dentry树中, 直接使用即可;

2, 如果对应的dentry不在dentry树中, 则试图从LRU队列去寻找. LRU队列中的dentry同时被散列到一个散列表中, 以便查找. 查找到需要的dentry后, 这个dentry被从LRU队列中拿出来, 重新添加到dentry树中;

3, 如果对应的dentry在LRU队列中也找不到, 则只好去文件系统的存储介质里面查找inode了. 找到以后dentry被创建, 并添加以dentry树中;

文件系统挂载

VFS允许多种不同的文件系统挂载在同一个目录结构中, 文件系统挂载的路径称为挂载点.

如, 磁盘有两个分区A和B, A作为根文件系统被挂载在"/"路径下, 而B作为A的子文件系统, 挂载在"/mnt/B/"下.

要完成这一挂载, A文件系统中必须有"/mnt/"这个目录. 而不管A中有没有"/mnt/B", 都会生成一个dentry与之对应, 但是这个dentry并不对应A中的"/mnt/B"所对应的inode(即使这个inode存在). 这个dentry中的d_mounted标记被置位, 表示这是一个挂载点.

如果在寻找文件路径的过程中遇到这样的一个挂载点, 则代表当前路径的指针将从当前dentry切换到挂载的文件系统的"/"所对应的dentry. 即是说, 访问A分区中的"/mnt/B"这个路径时, 实际访问到的是B分区中的"/"路径.

文件系统使用vfsmount结构来描述, 多个挂载的文件系统也被组织成树型结构.

vfsmount结构中有两个指向dentry的指针, mnt_mountpoint指向其父文件系统的挂载点dentry(例如A分区中的"/mnt/B"), 而mnt_root指向本文件系统的根路径dentry(例如B分区中的"/"). 通过这两个指针, 可以完成上面提到的当前路径的切换.

于是, 寻找文件路径的过程中, 除了要记录当前dentry, 还要记录当前vfsmount. 如果当前dentry是一个挂载点, 则通过当前vfsmount, 找到其儿子中挂载点为当前dentry的子vfsmount, 然后得到这个子vfsmount的mnt_root.

可能会有多个vfsmount都挂载在同一个dentry上, 这时候, 只有其中一个vfsmount会被选中, 而其他vfsmount将被隐藏. 直到被选中的那个vfsmount被卸载后, 被隐藏的vfsmount才可能被选中. 利用这个特点, 我们可以实现目录的隐藏. 比如/home/kouu/secret下保存着一些不希望别人看到的文件, 可以在这个目录上mount一下tmpfs, 以达到隐藏的目的.

子文件系统总是被挂载在父文件系统的某个dentry上, 而根文件系统则是由mnt_namespace对象来引用的. 不同的mnt_namespace可以引用不同的根文件系统, 组织不同的文件系统挂载树, 形成不同的目录结构.

一般而言, 新创建的进程总是与其父进程共用mnt_namespace. 而所有进程都是1号进程(init)的子孙进程, 则一般情况下所有进程都使用相同的mnt_namespace, 都生活在相同的目录结构中.

但是在通过clone系统调用创建新进程时, 可以指定CLONE_NEWNS标志, 为子进程创建新的名字空间(其中就包含了mnt_namespace, 此外名字空间还有其他内容).

前面只是说某个设备被挂载, 其实挂载文件系统除了要添加相应的存储介质的设备文件, 还要在内核中注册文件系统类型(对应file_system_type结构)(如ext2, ext3, tmpfs). 一个文件系统总是包含设备和类型两个要素的.

已注册file_system_type被存储在链表结构中, 通过它们注册的名字(比如ext3)来找到它们. 它们是文件数据的解释器, 解释设备文件所对应的物理存储介质中的数据.

每个文件系统都有一个超级块(对应super_block结构), 这个超级块通过file_system_type结构的get_sb方法从块设备中读出来.

而一个文件系统可以被挂载多次, 形成多个vfsmount结构. 它们都对应同一个super_block. 实际上只有文件系统第一次被挂载时, 才会去读它的super_block. 否则这个super_block已经是存在的, 直接引用即可.

在get_sb的过程中, 这个文件系统的根路径所对应的inode也会从存储介质中载入, 并创建对应的dentry. super_block->s_root就指向根路径的dentry.

数据结构总结

最后, 我们对上面的一些数据结构及其函数指针集合进行一下整理, 这些东西实在容易让人找不着北.

file_system_type

含义: 文件系统类型, 如ext2, ext3, 等等

创建: 内核启动或内核模块加载时, 为每一种文件系统类型创建一个对应的file_system_type结构

函数: get_sb, 获取超级块的方法. 在注册文件系统类型时提供

super_block

含义: 超级块, 对应一个存储文件的设备

创建: 文件系统挂载时, 通过对应的file_system_type->get_sb从设备中读取, 并初始化(可见, super_block结构中一部分信息是保存在设备中的, 一部分则是在内在中初始化的)

函数: s_op, 超级块的函数集, 主要包含对索引节点和文件系统实例的操作. file_system_type->get_sb从设备中读取超级块后, 用file_system_type对应的特定函数集进行初始化

inode

含义: 索引节点, 对应设备上存放的一个文件

创建: 1)在超级块被载入时, 作为根的inode一并被载入; 2)通过mknod调用创新新的索引节点; 3)在寻找文件路径的过程中, 从设备中读取, 并初始化(跟super_block一样, inode结构中一部分信息是保存在设备中的, 一部分则是在内在中初始化的)

函数: i_op, 索引节点函数集, 主要包含对子inode的创建, 删除等操作. f_op, 文件函数集, 主要包含对本inode的读写等操作. 在inode被创建后, 1)如果是特殊文件, 则根据对应文件的类型(包括块设备, 字符设备, fifo, 等等)赋予特定的函数集(并不直接与设备和文件系统类型相关); 2)否则, 对应的文件系统类型会提供相应的函数集, 并且目录和文件函数集很可能不同

dentry

含义: 目录项, 寻找文件路径的过程中使用的树型结构, 与inode关联

创建: inode被创建后, dentry就要被创建并初始化

函数: d_op, 目录项函数集, 主要包含对子dentry的查询操作. 由文件系统类型确定

file

含义: 打开文件的实例

创建: 在open调用时创建, 并与一个inode对应

函数: f_op, 文件读写等操作. 1)等于inode->f_op, 对于普通文件, 块设备文件, 等; 2)由inode->f_op->open函数在文件打开时指定, 典型的情况是字符设备. 所有字符设备具有相同的inode->f_op, 在inode->f_op->open过程中, 找到对应设备驱动注册的f_op, 赋给file->f_op

摘自kouu‘s home

来自:http://www.2cto.com/os/201110/108845.html

时间: 2024-10-27 05:31:58

(转)虚拟文件系统(VFS)浅析的相关文章

使用 /proc 文件系统来访问 linux操作系统 内核的内容 &amp;&amp; 虚拟文件系统vfs及proc详解

http://blog.163.com/he_junwei/blog/static/19793764620152743325659/ http://www.01yun.com/other/20130422/366044.html 使用 /proc 文件系统来访问 Linux 内核的内容 这个虚拟文件系统在内核空间和用户空间之间打开了一个通信窗口 简介: /proc 文件系统是一个虚拟文件系统,通过它可以使用一种新的方法在 Linux? 内核空间和用户空间之间进行通信.在 /proc 文件系统中,

Linux虚拟文件系统VFS解析

参考<Linux内核设计与实现> 虚拟文件系统(VFS)是linux内核和具体I/O设备之间的封装的一层共通访问接口,通过这层接口,linux内核可以以同一的方式访问各种I/O设备. 虚拟文件系统本身是linux内核的一部分,是纯软件的东西,并不需要任何硬件的支持. 1. 虚拟文件系统的作用 虚拟文件系统(VFS)是linux内核和存储设备之间的抽象层,主要有以下好处. - 简化了应用程序的开发:应用通过统一的系统调用访问各种存储介质 - 简化了新文件系统加入内核的过程:新文件系统只要实现VF

虚拟文件系统-VFS

1.什么是虚拟文件系统? OpenCms将所有的资源都存放在数据库中,这些资源的节点共同构成了虚拟文件系统(VFS,Virtual File System).虚拟文件系统可以在传统工作空间中查看. 虚拟文件系统可以看做一个实际的文件系统,它提供实际文件系统中提供的功能: 文件.文件夹的移动.复制.删除. 权限设置. 资源编辑锁定. 资源创建时间.最后修改时间记录. 此外,它还扩展了一些功能: 资源类型定义(不仅限于文件.文件夹). 自定义资源属性. 资源关联. 资源历史记录保存与恢复. 资源移动

linux文件系统体系结构 和 虚拟文件系统(VFS)

图 1. Linux 文件系统组件的体系结构 用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系统调用(打开.读取.写和关闭)提供用户接口.系统调用接口的作用就像是交换器,它将系统调用从用户空间发送到内核空间中的适当端点. VFS 是底层文件系统的主要接口.这个组件导出一组接口,然后将它们抽象到各个文件系统,各个文件系统的行为可能差异很大.有两个针对文件系统对象的缓存(inode 和 dentry).它们缓存最近使用过的文件系统对象. 每个文件系统实

Linux虚拟文件系统(VFS)学习

虚拟文件系统(Virtual Filesystem)也可称之为虚拟文件系统转换(Virtual Filesystem Switch),是一个内核软件层,用来处理与Unix标准文件系统相关的所有系统调用.其健壮性表现在能为各种文件系统提供一个通用的接口. 通用文件系统模型 VFS所隐含的主要思想在于引入一个通用的文件系统模型(common file model),这个模型能够表示所有支持的文件系统.在通用文件模型中,每个目录被看做一个文件,可以包含若干文件和其他的子目录. 通用文件模型由下列对象类

linux的虚拟文件系统VFS

虚拟文件系统(virtual file system),别名虚拟文件系统开关,是linux中的一个软件层,向用户空间提供文件系统操作接口. VFS包含的系统调用包括open(2).stat(2).read(2).write(2).chmod(2)等等,这些系统调用在进程环境中执行.下面几个重要的数据结构是VFS(虚拟文件系统中涉及到的数据结构): 1.Directory Entry Cache(dcache) VFS实现了系统调用open(2).stat(2).chmod(2),和其他类似的文件

linux虚拟文件系统vfs

linux可以挂载不同的文件系统(EXT2,FAT,NTFS),用同一的样式呈现给用户,读写操作用起来都一样,这是怎样做到的呢? linux内核在各种不同的文件系统格式上做了一个抽象层,使得文件.目录.读写访问等概念成为抽象层的概念,因此各种文件系统看起来用起来都一样,这个抽象层称为虚拟文件系统(VFS). 每个进程在PCB(Process Control Block)中都保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针,现在我们明确一下:已打开的文件在内

linux虚拟文件系统浅析

linux虚拟文件系统浅析 虚拟文件系统(VFS)在我看来, "虚拟"二字主要有两层含义:1, 在同一个目录结构中, 可以挂载着若干种不同的文件系统. VFS隐藏了它们的实现细节, 为使用者提供统一的接口;2, 目录结构本身并不是绝对的, 每个进程可能会看到不一样的目录结构. 目录结构是由"地址空间(namespace)"来描述的, 不同的进程可能拥有不同的namespace, 不同的namespace可能有着不同的目录结构(因为它们可能挂载了不同的文件系统).操作

Linux内核源代码情景分析-虚拟文件系统

我们先来看两张图: 第一张是VFS与具体文件系统的关系示意图: 第二张是Linux文件系统的层次结构: 特殊文件:用来实现"管道"的文件,特别是"命名管道"的FIFO文件,还有Unix域的socket,也都属于特殊文件:还有在/proc目录下的一系列文件. 磁盘文件:就是存在硬盘上的文件. 设备文件:sudo mount -t ext2 /dev/sdb1 /mnt/sdb,这里的/dev/sdb1就是设备文件.如果硬盘上的节点raw_inode->i_blo