1.前言
用户进程在能够读/写一个文件之前必须要先“打开”这个文件。对文件的读/写从概念上说是一种进程与文件系统之间的一种“有连接”通信,所谓“打开文件”实质上就是在进程与文件之间建立起链接。在文件系统的处理中,每当一个进程重复打开同一个文件时就建立起一个由file数据结构代表的独立的上下文。通常,一个file数据结构,即一个读/写文件的上下文,都由一个打开文件号(fd)加以标识。
2. do_sys_open
打开文件的系统调用是open(),在内核中通过do_sys_open()实现,其代码在 fs/open.c中:
1 |
|
dfd是传入的AT_FDCWD,其定义在 include/uapi/linux/fcntl.h。该值表明当 filename 为相对路径的情况下将当前进程的工作目录设置为起始路径。相对而言, 你可以在另一个系统调用 openat 中为这个起始路径指定一个目录, 此时 AT_FDCWD 就会被该目录的描述符所替代。
进程的task_struct结构中有个指针files,指向本进程的files_struct数据结构。与打开文件有关的信息都保存在这个数据结构中,其定义在include/linux/sched.h 中:
1 | struct files_struct { |
其结构如下图所示。
3.do_filp_open
sys_do_open主要调用的函数为do_filp_open。
1 |
|
参数 dfd 是相对路径的基准目录对应的文件描述符,参数 name 指向文件路径,参数 op 是查找标志。=
在路径查找中有个很重要的数据结构 nameidata 用来向解析函数传递参数,保存解析结果。
1 |
|
成员 last 存放需要解析的文件路径的分量(以前提到的组件),是一个快速字符串(quick string),
不仅包字符串,还包含长度和散列值。
成员 path 存放解析得到的挂载描述符和目录项,成员 iode 存放目录项对应的索引节点。
path 保存已经成功解析到的信息,last 用来存放当前需要解析的信息,如果 last 解析成功
那么就会更新 path。
函数 do_flp_open 三次调用函数 path_openat以解析文件路径。
- 第一次解析传入标志 LOOKUP_RCU,使用 RCU 查找(rcu-walk)方式。在散列表中根据{父目录, 名称}查找目录的过程中,使用 RCU 保护散列桶的链表,使用序列号保护目录,其他处理器可以并行地修改目录, RCU 查找方式速度最快。
- 如果在第一次解析的过程中发现其他处理器修改了正在查找的目录,返回错误号-ECHILD,那么第二次使用引用查找(ref-walk)REF 方式,在散列表中根据{父目录, 名称}查找目录的过程中,使用 RCU 保护散列桶的链表,使用自旋锁保护目录,并且把目录的引用计数加1。引用查找方式速度比较慢。
- 网络文件系统的文件在网络的服务器上,本地上次查询得到的信息可能过期,和服务器的当前状态不一致。如果第二次解析发现信息过期,返回错误号 -ESTALE,那么第三次解析传入标志 LOOKUP_REVAL,表示需要重新确认信息是否有效。
调用 set_nameidata() 保护当前进程现场信息。 接着调用 filp = path_openat(&nd, op, flags | LOOKUP_RCU);
4.path_openat
1 |
|