【fs/namei.c】sys_open->do_sys_open->do_filp_open->path_openat->link_path_walk
点击(此处)折叠或打开
...
-
err = walk_component(nd, &next, LOOKUP_FOLLOW);
-
if (err < 0)
-
return err;
-
-
if (err) {
-
err = nested_symlink(&next, nd);
-
if (err)
-
return err;
- }
...
【fs/namei.c】sys_open->do_sys_open->do_filp_open->path_openat->link_path_walk->walk_component
点击(此处)折叠或打开
-
static inline int walk_component(struct nameidata *nd, struct path *path,
-
int follow)
- {
...
-
if (unlikely(nd->last_type != LAST_NORM))
-
return handle_dots(nd, nd->last_type);
...
【fs/namei.c】sys_open->do_sys_open->do_filp_open->path_openat->link_path_walk->walk_component->handle_dots
点击(此处)折叠或打开
-
static inline int handle_dots(struct nameidata *nd, int type)
-
{
-
if (type == LAST_DOTDOT) {
-
if (nd->flags & LOOKUP_RCU) {
-
if (follow_dotdot_rcu(nd))
-
return -ECHILD;
-
} else
-
follow_dotdot(nd);
-
}
-
return 0;
- }
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > walk_component > handle_dots > follow_dotdot_rcu
点击(此处)折叠或打开
-
static int follow_dotdot_rcu(struct nameidata *nd)
-
{
- set_root_rcu(nd);
...
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > walk_component > handle_dots > follow_dotdot_rcu
点击(此处)折叠或打开
...
-
while (1) {
-
if (nd->path.dentry == nd->root.dentry &&
-
nd->path.mnt == nd->root.mnt) {
-
break;
-
}
-
if (nd->path.dentry != nd->path.mnt->mnt_root) {
-
struct dentry *old = nd->path.dentry;
-
struct dentry *parent = old->d_parent;
-
unsigned seq;
-
-
seq = read_seqcount_begin(&parent->d_seq);
-
if (read_seqcount_retry(&old->d_seq, nd->seq))
-
goto failed;
-
nd->path.dentry = parent;
-
nd->seq = seq;
-
break;
-
}
-
if (!follow_up_rcu(&nd->path))
-
break;
-
nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
- }
...
首先,如果当前路径就是预设根目录的话(1141)就什么也不做直接跳出循环(都已经到根目录了不退出还等啥呢,大家可以在根目录试试这个命令“cd ../../”,看看有什么效果);其次,当前路径不是预设根目录,但也不是当前文件系统的根目录(1145),那么向上走一层也是很简单的事,直接将父目录项拿过来就是了(1153);到最后,当前路径一定是某个文件系统的根目录,往上走有可能就会走到另一个文件系统里去了。
看到这里可能有人要问了,啥叫文件系统的根目录,文件系统和文件系统又有啥关系?别着急,我们祭出本次旅途的第二张导游图,再配合我的讲解相信大家很快就会明白的。
【mount 结构图】

这是一个关于 mount(挂载)的故事。在 Kernel 世界里,挂载是一项很了不起的特性,它可以将不同类型的文件系统组合成一个有机的整体,从使用者角度来看不同的文件系统并没有什么区别,那么 Kernel 是怎么做到呢?首先,Kernel 会为每个文件系统准备一个 mount 结构,然后再把这个结构加入到 vfs 这颗大树上就好了。这么一个小小的 mount 结构就这么神奇?请看图,一个 mount 中有三个很重要的成员,他们分别指向父 mount 结构(6)、本文件系统自己的根目录(7)和本文件系统的挂载点(8),前两个很好理解,那么挂载点是什么呢?简单地说挂载点就是父级文件系统的某个目录,一旦将某个文件系统挂载到某个目录上,这个目录就成了该文件系统的根目录了。并且该目录的标志位 DCACHE_MOUNTED 将被置位,这将表明这个目录已经是一个挂载点了,如果要访问这个目录的话就要顺着 mount 结构访问另一个文件系统了,原来的内容将变得不可访问。
现在我们从图的左边讲起,带你一窥 mount 的风采。一个进程有一个叫 root 的 path 结构,它就是本进程的根目录(大多数情况下它就是系统根目录),root 中两个成员分别指向某个文件系统的 mount 结构(其实是指向 mount.mnt 但这样理解没问题)(1)和该文件系统的根目录(2),这个文件系统就是所谓根文件系统(在图中就是 rootfs)。由于它是根文件系统,所以它的父 mount 结构就是它自己(4)它的挂载点就是它自己的根目录(5)。但是 rootfs 只是一个临时的根文件系统,在 Kernel 的启动过程中加载完 rootfs 之后会紧接着解压缩 initramfs 到 rootfs 中,这里面包括了驱动以及加载真正的根文件系统的工具,Kernel 通过加载这些驱动、使用这些工具实现了挂载真正的根文件系统。之后 rootfs 将推出历史舞台,但作为文件系统的总根 rootfs 并不会被卸载(注)。图中 fs1 就是所谓的真正的根文件系统,Kernel 把它挂载到了 rootfs 的根目录上(8),并且将它的父 mount 结构指向了 rootfs(6)。这时访问根目录的话就会直接访问到 fs1 的根目录,而 rootfs 就好像不存在了一样。
再看 fs1,他有一个子目录“mnt/”,以及“mnt/”的子目录“a”,此时路径“/mnt/a/”是可访问的。但现在我们还有另一个文件系统 fs2,我们把它挂载到“/mnt/”上会发生什么呢?首先 fs2 的父 mount 将指向 fs1(9),然后 fs2 的挂载点将指向 “/mnt/”(10),同时“mnt/”的 DCACHE_MOUNTED 将被置位。此时路径“/mnt/a/”就不可访问了,取而代之的是“/mnt/b/”。本着不怕麻烦的精神我们再折腾一下,把 fs3 也挂载到“/mnt/”上,这时和挂载 fs2 一样父 mount 将指向 fs2(11),但是挂载点应该指向哪里呢?答案是 fs2 的根目录(12)。这时“/mnt/b/”也消失了,我们只能看见“/mnt/c”了。这样整个结构就形成了一个挂载的序列,最后挂载的在序列末尾,Kernel 可以很容易的通过这个序列找到最初的挂载点和最终的文件系统。
在顺序查找的情景下,当遇到一个目录时 Kernel 会判断这个目录是不是挂载点(检查 DCACHE_MOUNTED 标志位),如果是就要找到挂载到这个目录的文件系统,继而找到该文件系统的根目录,然后在判断这个根目录是不是挂载点,如果是那就再往下找直到某个文件系统的根目录不再是挂载点。
反向查找也和顺序查找类似,我们结合代码来看:
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > walk_component > handle_dots > follow_dotdot_rcu > follow_up_rcu
点击(此处)折叠或打开
-
static int follow_up_rcu(struct path *path)
-
{
-
struct mount *mnt = real_mount(path->mnt);
-
struct mount *parent;
-
struct dentry *mountpoint;
-
-
parent = mnt->mnt_parent;
-
if (&parent->mnt == path->mnt)
-
return 0;
-
mountpoint = mnt->mnt_mountpoint;
-
path->dentry = mountpoint;
-
path->mnt = &parent->mnt;
-
return 1;
- }
当跳出这个 while(1) 循环时我们已经站在某个目录上了,一般来说这个目录就是我们想要的目标,而不会是一个挂载点,但也有例外。请看 while(1) 循环中第一个 if 和 follow_up_rcu 中的那个 if,想必大家已经发现了,当遇到(预设)根目录的时候会直接退出循环,而这时我们的位置就相当于站在图中 rootfs 的根目录上,这显然不是我们想要的,我们想要站在 fs1 的根目录上。这就需要接下来的循环,再顺着 mount 结构往下走。
【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > walk_component > handle_dots > follow_dotdot_rcu
点击(此处)折叠或打开
...
-
while (d_mountpoint(nd->path.dentry)) {
-
struct mount *mounted;
-
mounted = __lookup_mnt(nd->path.mnt, nd->path.dentry);
-
if (!mounted)
-
break;
-
nd->path.mnt = &mounted->mnt;
-
nd->path.dentry = mounted->mnt.mnt_root;
-
nd->seq = read_seqcount_begin(&nd->path.dentry->d_seq);
-
if (!read_seqretry(&mount_lock, nd->m_seq))
-
goto failed;
-
}
-
nd->inode = nd->path.dentry->d_inode;
- return 0;
...
从 follow_dotdot_rcu 返回后,对“.”和“..”的处理也完成了,程序将直接返回 link_path_walk 进入对下一个子路径的处理。
休息一下,我们马上回来。
注:摘自《深度探索 Linux 操作系统》王柏生