转自http://hi.baidu.com/_kouu/item/4e9db87580328244ef1e53d0

######

虚拟文件系统(VFS)
在我看来, "虚拟"二字主要有两层含义:
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

(转)linux内核虚拟文件系统浅析的更多相关文章

  1. (转)linux内核虚拟文件系统浅析【转】

    转自:https://www.cnblogs.com/woainilsr/p/3590716.html 转自http://hi.baidu.com/_kouu/item/4e9db8758032824 ...

  2. 小白自制Linux开发板 三. Linux内核与文件系统移植

    上一篇完成了uboot的移植,但是想要愉快的在开发板上玩耍还需要移植Linux内核和文件系统. 1.Linux内核 事实上对于F1C100S/F1C200S,Linux官方源码已经对licheepi ...

  3. 嵌入式Linux内核+根文件系统构建工具-Buildroot 快速入手指导【转】

    本文转载自:https://my.oschina.net/freeblues/blog/596448 嵌入式Linux内核+根文件系统构建工具-Buildroot 快速入手指导 buildroot 是 ...

  4. [C++]Linux之虚拟文件系统[/proc]中关于CPU/内存/网络/内核等的一些概要性说明

    声明:如需引用或者摘抄本博文源码或者其文章的,请在显著处注明,来源于本博文/作者,以示尊重劳动成果,助力开源精神.也欢迎大家一起探讨,交流,以共同进步- 0.0 1.Linux虚拟文件系统 首先要明白 ...

  5. Linux 的虚拟文件系统(强烈推荐)

    1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系 ...

  6. linux的虚拟文件系统VFS

    虚拟文件系统(virtual file system),别名虚拟文件系统开关,是linux中的一个软件层,向用户空间提供文件系统操作接口. VFS包含的系统调用包括open(2).stat(2).re ...

  7. 从文件 I/O 看 Linux 的虚拟文件系统

    1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系 ...

  8. Linux内核,文件系统移植过程中出现的一些问题与解决办法

    1.bootm地址和load address一样 此种情况下,bootm不会对uImage header后的zImage进行memory move的动作,而会直接go到entry point开始执行. ...

  9. 用SD卡下载uboot、linux内核和文件系统

    1. 移植mtd-utils: a) 下载utd-utils 下载地址为ftp://ftp.infradead.org/pub/mtd-utils/b) 交叉编译mtd-utilsi   修改Make ...

随机推荐

  1. 菜鸟学习WCF笔记-概念

    背景 WCF这个词语一直不陌生,以前也使用过多次在实际的项目中,但是一直没有时间来做个系统的学习,最近抽点时间,看看 蒋金楠的<WCF全面解析>学习下,顺带做些笔记,如有错误,欢迎各路大神 ...

  2. python两个文件的对比

    #encoding=utf-8 class SyncPagemaptoDB(object): def loadOldmap(self,oldpage,newpage,new_version): map ...

  3. 如何成为一位优秀的创业CEO

    英文原文:How to Be Startup CEO 编者按:本文来自 Ryan Allis,是一位来自旧金山的创业者和投资人.在 2003 年创立了 iContact,并任 CEO. 做创业公司的 ...

  4. 初学Linux

    一直觉得Linux敲命令很蛋疼,今天开始学习一下吧,主要以练习(想到啥就查啥)命令和练习在Linux中编程(Python)为主吧. 不记得什么时候安装的Ubuntu 12.04.3 LTS虚拟机,连密 ...

  5. Web app 的性能瓶颈与性能调优方法

    1. web app 性能测试工具使用 2. mysql 性能分析与调优方法

  6. 解决 The Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path

    到 http://tomcat.heanet.ie/native/ 下载最新的tcnative-1.dll放到相应目录即可,我目前下载的是 http://tomcat.heanet.ie/native ...

  7. Thinkpad X240使用U盘安装Win7系统

    更改BIOS设置 不同电脑的进入BIOS的方式可能不太一样,Thinkpad X240的进入方式是在电脑启动的时候按下回车键,然后按F1进入BIOS. 1. 修改secure boot为Disable ...

  8. HTTP2 学习

    一.HTTP1.x存在的问题 Http1.0时Connection无法复用,同一时间一个Connection只能处理一个request.Http1.1引入了Request pipelining来解决这 ...

  9. C++虚函数与虚函数表

    多态性可分为两类:静态多态和动态多态.函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的. 每个含有虚函数的类有一张虚函数表(vtbl),表中每一项是一个虚函数的地址, 也就是说 ...

  10. Sencha Touch+PhoneGap打造超级奶爸之喂养记(一) 源码免费提供

    起源 非常高兴我的宝宝健康平安的出生了.对于初次做奶爸的我,喜悦过后,面临着各中担心,担心宝宝各项指标是否正常.最初几天都是在医院待着,从出生那一天开始,护士妹妹隔一段时间就会来问宝宝的喂奶,大小便, ...