本质上,一个文件打开的过程就是建立fd,file,dentry,inode,address_space的关联过程。关联过程中关键的一个过程是如何根据路径名寻找到对应的dentry,对于用户程序来说路径只是一个特殊的字符串来表示全路径,但是内核中不是以全路径来管理文件的,而是分解成多个dentry,他们之间相互组成树的形式,从该dentry到根文件系统的root dentry之间所有的dentry的名称组合到一起并且在其中加上“/”组成了全路径字符串;中间有一个映射过程,需要将用户程序中路径名映射到某个dentry上。
linux系统支持两种路径:绝对路径和相对路径,绝对路径就是以根文件系统的root dentry为起点的路径名,例如/mnt/test/a,相对路径以当前进程所在的工作目录为起点的路径名,例如./a/b
另外系统还支持软链接文件和挂载,在查找路径的时候还需要对其进行转换,该问题在下面路径查找的时候进一步说明。

文件对象关联

文件打开的过程中主要涉及到几种对象,fd,file,dentry,inode,address_space,最后建立的映射关系如下图。

  • files_struct中默认内嵌有一个fdtable和file的指针数组fd_array,一般进程打开的文件很少,所以内嵌的对象一般就能满足使用。对于进程打开大量文件的情况,它会新申请fdtable对象并且对file指针数组进行扩张来容纳更多的文件信息,并且将旧的fdtable信息和file指针数组拷贝到新的对象上去。fd代表索引,对应着fdtable->fd指针数组的下标和fdtable中open_fds和close_on_exec的两个bitmap的位置。

  • fdtable中分别有open_fds和close_on_exec两个指针,分别指向两个bitmap,前者标识文件描述符是否可用,而后者描述了在进程exec时是否将文件描述符进行关闭。当进程exec时默认是继承原有的fdtable信息的,这会造成两种情况,一个是文件描述符的泄露,另一个是文件打开异常。例如当程序hello启动时通过flock文件锁的形式来检查是否可以运行,第一次打开文件对他进行上锁,之后通过exec启动它自身,有进行文件打开和上锁,此时就上锁失败了,这种情况时需要在open时指定O_CLOEXEC,在exec时自动关闭这个描述符。

  • 文件的各种信息都是拿到path之后进行实际关联的,path中指向具体的dentry,进而关联到inode和address_space。找到对应的dentry之后在do_dentry_open进一步关联,file可以直接到inode和address_space,file的file_operations的对象更是原来的inode的i_fop,这样接下来的文件读写就基本上可以直接面向address_space和它管理的page cache了。不同类型的文件file的file_operations是不同的,例如普通文件有readdir就毫无意义,反过来目录文件有llseek操作也很奇怪,所以在根据文件系统上信息创建inode对象的时候根据文件类型为他指定了不同的file_operations,这样在和file关联的时候就能得到正确的文件操作方法。

路径查找过程

path:文件路径,内核中一个文件所在的路径使用vfsmount,dentry的形式即挂载点下的某个dentry来表示唯一路径。主要是一个分区可能被多次挂载到不同的路径,文件路径虽然不同但是都是同一个文件。
nameidata来源于name to inode文件路径到inode的解析,也可以理解为路径解析(name interprets),表示一次路径查找的过。主要有几个重要的成员:struct path path,root;int last_type;struct qstr last。在查找之前会对其进行初始化,root成员指向进程的根路径,根据路径是否以/开头来决定path成员指向当前进程的工作目录还是根路径,这样nameidata就能处理绝对路径和相对路径两种形式了。path指向当前解析到的路径,当查找结束的时候指向目标路径。last_type表示上一次路径的类型,总共有LAST_NORM, LAST_ROOT, LAST_DOT,LAST_DOTDOT几种类型。struct qstr last表示下一次要查找的文件名称。

struct nameidata {struct path path;  //当前路径,随着路径名的解析它会逐渐的变化,最后指向路径名的pathstruct qstr last;  //在路径名解析过程中,会根据“/”将路径名分解成目录项,例如“/mnt/test”则会分解成mnt和test两个目录项;代表当前目录下要查找的文件名struct path root;   //进程的根文件路径struct inode    *inode; /* path.dentry.d_inode */ unsigned int    flags; unsigned    seq, m_seq;int     last_type; unsigned    depth; struct file *base;  char *saved_names[MAX_NESTED_LINKS + 1];          //主要是防止软链接的不断循环嵌套
};

在查找的过程中,它将文件路径以/为分隔符来分解成一串路径,我们先不考虑特殊的路径查找,以普通的路径查找为例。
上图中是文件打开的基本过程:

  1. 分配空闲的文件描述符,当文件成功打开之后就能在最后的fd_install进行关联
  2. 分配空闲的file,当路径查找成功的时候可以进行和对应的dentry,inode进行关联
  3. 文件路径的分解link_path_walk,它将一个路径分解成两部分:最后一级文件名和其他部分,例如在图中/mnt/test/a文件,分解成/mnt/test/a两部分,当然这是一个最简单的实例,实际的路径更加复杂:最后一级文件可以是普通文件,目录文件,软连接文件,其他部分可能只是普通的路径,也可能包含软连接文件,还有可能经过挂载点。对于查找一个/mnt/test/a文件,它将路径名拆解成mnt,test,通过walk_component循环查找每一级路径并且更新last_type和last:查找/下是否有mnt,之后查找mnt下test,一直到剩下最后一级a。
  4. 最后do_last处理最后一级路径a的问题,即真正开始进行文件打开的操作,从文件系统中读取信息创建对应的dentry和inode,address_space信息并且关联起来。

walk_component是路径查找的主要实现,它里面需要处理很多琐碎的问题,最主要的就是并发问题。加入正在将a/b通过rename操作变成a/c/b,另一个进程正在查找a/b/..它可能最终返回的是a/c目录文件。rename本身的操作应该是原子性的,查找的结果应该有两种,当rename操作之前返回的是a,操作之后应该这个路径就是非法的,无论如何也不应该返回给一个a/c目录文件。上面的查找过程总体而言没啥难度,但是这种并发操作让这个过程的实现太晦涩了,而且也绕不开这个问题,所以里面会使用各种的锁和引用来防止并发。

首先说一下普通的慢速查找lookup_slow,对于/mnt/test/a路径查找,例如查找/时是否有mnt目录项,它首先需要获取到(/)dentry->d_inode->i_mutex,之后通过目录的inode->i_op->lookup进行查找,这个可能是需要进行实际读取文件系统数据来进行查找的,文件系统可能是基于块设备的也可能是网络文件系统类型,意味着它需要发起磁盘IO或者网络查询来得到确切的答案。
内核中除了使用dcache来缓存文件系统的信息,避免进行底层文件系统操作之外,还通过dentry_hashtable哈希表来维护dentry,这样可以通过父dentry和当前文件名信息struct qstr来计算hash值来查找当前文件的dentry,这样的速度是最快的了。为了应对并发问题,它使用RCU技术来保证对象不会在使用中被释放掉,整个的link_path_walk过程都处于read临界区,rcu_read_lock();link_path_walk();rcu_read_unlock()

目前的查找过程中,优先使用RCU walk的形式,当失败之后再次使用slow的方式去文件系统中获取对应的路径。

软链接文件

follow_link处理软链接文件的路径问题,通过dentry->d_inode->i_op->follow_link将软链接文件的内容读取到nameidata->save_names[nameidata->depth]中,之后根据链接路径来重新初始化path。软链接形式是绝对路径时即以/开头,复位nameidata->path到nameidata->root再次通过link_path_walk进行查找。
对于路径中间存在软链接形式的文件时,walk_component负责跳转到软链接中。
对于最后一级文件是软链接时,当open时允许跟踪软链接文件时,do_last尝试打开时会返回错误并且should_follow_link返回真,之后它会通过trailing_symlink再次进行查找和打开。

挂载点

当一个目录被挂载之后,它和下面所有的文件都被隐藏掉了,后续的文件操作是看不到这些文件的,直至被卸载掉。当路径查找的过程经过挂载点时,即当前dentry->d_flags|DCACHE_MOUNTED时表明当前目录是一个挂载点,需要转换到挂载点中的路径也就是当前的可见路径。
挂载点mount通过mount_hashtable表管理,它的hash值是通过挂载点test所在的vfsmount(test)和dentry(test)计算出来的,之后通过mount->mnt_parent->mnt == vfsmount(test) && mount->mnt_mountpoint == dentry(test)来确定mount;内核中使用lookup_mnt来查找一个挂载点dentry对应的mount信息,linux允许一个目录可以被多次挂载,第一次查询返回最后被挂载的信息,以后逐次返回上一次挂载的信息,最后返回空。

 * mount /dev/sda1 /mnt                                                             * mount /dev/sda2 /mnt                                                             * mount /dev/sda3 /mnt                                                             *                                                                                  * Then lookup_mnt() on the base /mnt dentry in the root mount will                 * return successively the root dentry and vfsmount of /dev/sda1, then              * /dev/sda2, then /dev/sda3, then NULL.   struct vfsmount *lookup_mnt(const struct path *path)

当经过挂载点路径时,通过follow_managed来切换路径,查找到对应的mount信息之后,改变nameidata->path中的信息:

path->mnt = mounted;
path->dentry = dget(mounted->mnt_root);

link_path_walk

link_path_walk只是循环查找中间的路径名而不处理最后一级的路径问题,除了普通的open之外还有不同的场景也会进行文件名解析,例如通过stat或者chmod系统调用也需要进行文件名解析,它只需在最后一级文件存在的情况下返回对应的dentry就足够了。而当通过open系统调用进行文件打开时,它允许添加各种参数里,例如O_CREAT,O_NOFOLLOW时,O_CREAT在最后一级dentry没有对应的inode时进行创建inode动作,O_NOFOLLOW在最后一级dentry是个软连接文件时不再进行follow link而是直接打开文件,此时读到的就是软链接文件的内容:它的链接路径:/home/linux/project/linux/scripts/gdb/vmlinux-gdb.py,不带O_NOFOLLOW时会根据它的链接路径继续查找link_path_walk找到最终的普通文件。

dcache的精确性问题

内核使用dentry和inode cache来保存底层文件系统的信息,这样就不需要频发查找磁盘了,但是对于网络文件系统,dcache完全不能保证和实际文件系统的一致性,实际的文件系统在另外一台机器上,可能被几个机器网络共享访问,dcache不能精确代表当前的状态,所以在查找的过程需要重新revalidate操作。

文件的关闭

因为每一层存在一对多的关系,例如多个fd引用同一个file,多个file引用同一个inode,它通过引用计数来表示当前的使用状态,对于文件的关闭操作直接触发的就非常简单,仅仅是解除fd和file的关联,清除fdtable中的bitmap相关位,并对file引用计数减一。fput时当file的引用计数为零时准备释放file对象,它这里使用了RCU异步延迟释放的方式进行释放,真正干活的是__fput,对dentry减一,之后dput触发inode的引用计数,一环扣一环,环环相扣来实现了精准引用统计,避免内存泄露和错误释放。

VFS本身看起来比较简单,实际上它非常复杂,处理的细节问题非常多,fs/namei.c文件就有5000行,还有几个主要的问题没有解决,留待下一篇吧

  1. dentry的各种锁及应用场景分析,VFS本身支持多场景并发,并且它要处理各种底层文件系统的特性,这就有了各种锁和引用计数
  2. 在查找过程中如何防止循环软链接的
  3. RCU形式的查找和slow形式查找的切换

linux虚拟文件系统-文件的打开相关推荐

  1. linux 进入文件系统路径,Linux虚拟文件系统--文件路径名的解析(1)--整体过程

    注意之前传递进来的dfd为AT_FDCWD,因此path_init中只有可能出现前两种情况:1.路径以绝对路径的方式给出 2.路径以相对路径的方式给出.此时nd中的path保存了查找的起始目录,对于第 ...

  2. Linux虚拟文件系统:数据结构与文件系统注册、文件打开读写

    数据结构 超级块 - super_block 索引节点 - inode 目录项 - dentry 文件结构 - file 虚拟文件系统实现 注册文件系统 - register_filesystem 打 ...

  3. linux虚拟文件系统浅析

    linux虚拟文件系统浅析 虚拟文件系统(VFS) 在我看来, "虚拟"二字主要有两层含义: 1, 在同一个目录结构中, 可以挂载着若干种不同的文件系统. VFS隐藏了它们的实现细 ...

  4. linux 文件系统 vfs,linux虚拟文件系统vfs

    <操作系统>课程设计报告课程设计题目:操作系统课程设计 设计时间:2016/1/10一. 课程设计目的与要求需要完成的内容:(1) 安装虚拟机:Vmware.Vmware palyer ( ...

  5. Linux虚拟文件系统

    从文件 I/O 看 Linux 的虚拟文件系统 1 引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux ...

  6. Linux 虚拟文件系统(一)概述

    Linux 虚拟文件系统(一)概述 tags: Linux源码 Linux 虚拟文件系统一概述 文章梗概 正文 文件系统 虚拟文件系统架构 虚拟文件系统如何知道可用的文件系统有哪些的 不太喜欢的环节 ...

  7. Linux 虚拟文件系统四大对象:超级块、inode、dentry、file之间关系

    一:文件系统 1. 什么是文件系统? 操作系统中负责管理和存储文件信息的软件机构称为文件管理系统,简称文件系统. 通常文件系统是用于存储和组织文件的一种机制,便于对文件进行方便的查找与访问. 文件系统 ...

  8. linux 目录防篡改,一种基于Linux虚拟文件系统的防篡改方法及系统的制作方法

    一种基于Linux虚拟文件系统的防篡改方法及系统的制作方法 [技术领域] [0001]本发明涉及文件防护技术领域,特别涉及一种基于Linux虚拟文件系统的防篡改方法及系统. [背景技术] [0002] ...

  9. Linux文件关联详解 linux 修改默认文件关联打开程序

    linux 修改默认文件关联打开程序 从总体上讲 /etc/gnome/defaults.list 保存了全局的打开方式 /.local/share/applications/mimeapps.lis ...

最新文章

  1. MIT的这个AI,专治抗生素滥用,二次抗生素直降67%
  2. 清华大学《操作系统》(六):非连续内存分配 段式、页式、段页式存储管理
  3. Java并发编程实战~Copy-on-Write模式
  4. linux下查看usb插拔日志,Linux:如何检测usb键盘是否已插入和拔出
  5. html range样式修改,自定义(滑动条)input[type=range]样式
  6. 计算机通信子网的作用,通信子网
  7. Ubuntu/CentOS查看系统启动项
  8. zabbix短信告警oracle,zabbix自定义脚本实现短信报警提醒
  9. TDengine 在IT运维监控领域的应用
  10. 用 Python 找出了拉黑 QQ 空间屏蔽我的大人物
  11. AVPlayerItem的播放时间
  12. 初探密码破解工具JTR
  13. 五款数字孪生软件大比拼:优缺点分析测评报告
  14. 11.Unity ShaderGraph实例(LWRP+PBRMaster节点制作全息效果的精灵)
  15. 利用C 制作公章 续
  16. gridStudio 安装(linux)
  17. 关于BIM本质的思考
  18. 集合还在只用list吗?不如看一下Set集合
  19. 系统OA在线演示和下载地址
  20. 上海北大青鸟教学:学习jsp的步骤

热门文章

  1. 上海ICPC Mine Sweeper II
  2. 华为防火墙-7-dhcp
  3. 拉勾网主页面HTML+CSS布局代码,附结果图精灵图
  4. shit: Lao Da Ge Ni Zai Na
  5. centos7中设置防火墙
  6. 记录一次批量处理文档的过程
  7. 自己做仙剑奇侠传四 免激活补丁PAL4Extend.dll(原创)
  8. JS 触发 validate 校验方法
  9. python aes 128 gcm 防沉迷身份认证
  10. 创成汇丨2019年参加创新创业大赛都能获得什么?