走马观花: Linux 系统调用 open 七日游(四)

简介:     现在,我们的“路径行走”只剩下最后一个小问题需要处理了——符号链接。【fs/namei.c】sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk 点击(此处)折叠或打开   .
    现在,我们的“路径行走”只剩下最后一个小问题需要处理了——符号链接。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk

点击(此处)折叠或打开

  ...

  1.         if (err) {
  2.             err = nested_symlink(&next, nd);
  3.             if (err)
  4.                 return err;
  5.         }

  ...

    nested_symlink 就是用来处理符号链接的,现在 nd 还“站”在原来的目录上,next 才指向了当前这个符号链接。咱们进去看看:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink

点击(此处)折叠或打开

  1. static inline int nested_symlink(struct path *path, struct nameidata *nd)
  2. {
  3.     int res;

  4.     if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
  5.         path_put_conditional(path, nd);
  6.         path_put(&nd->path);
  7.         return -ELOOP;
  8.     }
  9.     BUG_ON(nd->depth >= MAX_NESTED_LINKS);

  10.     nd->depth++;
  11.     current->link_count++;

  ...

    从本质上来讲符号链接就是一个路径字符串,这和硬连接有着本质上的区别(请参考【dentry-inode 结构图】)。而且对于创建目录的链接,硬连接有着严格的限制(比如普通用户就不允许创建目录的硬连接),但是符号链接就没有那么多的限制,用户可以随意创建目录的链接,甚至可以创建一个链接的死循环(比如:a->b;b->c;c->a)。既然不限制创建各式各样的符号链接,那么在读取的时候就需要格外小心了,Kernel 设置了两个限制位,这里我们就遇到了第一个:MAX_NESTED_LINKS,它的值是 8,它限制了符号链接的嵌套(递归)层数,那么什么时候会发生嵌套(递归)呢,别着急,等我们游览完符号链接的时候自然就清楚了,这里先卖个关子;还有一个是链接长度,这个比较好理解,马上我们就会遇到。
    回到我们的旅程,这里先检查嵌套层数,一共有两个地方来对嵌套进行控制:一个是进程(1581);另一个是当前的“路径行走”(1586)。没问题的话就可以进行下一步了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink

点击(此处)折叠或打开

  ...

  1.     do {
  2.         struct path link = *path;
  3.         void *cookie;

  4.         res = follow_link(&link, nd, &cookie);
  5.         if (res)
  6.             break;
  7.         res = walk_component(nd, path, LOOKUP_FOLLOW);
  8.         put_link(nd, &link, cookie);
  9.     } while (res > 0);

  ...

    符号链接的目标很有可能也是一个符号链接,所以应该用一个循环来跟踪它。仔细观察这个循环体,我们发现了一个老朋友 walk_component,还记得吗,当它返回正值的时候(其实就是 1)就表明当前目标是一个符号链接(我们就是这么进来的,当然应该记得了),这时就需要再一次循环(1600)。既然这样的话那我们就大胆的猜测 follow_link 也应该和 link_path_walk 差不多,返回时 nd 也应该是“站在”最终目标所在的目录上,然后在 walk_component 的帮助下“站上”最终目标。
    现在就来验证我们的猜测:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link

点击(此处)折叠或打开

  1. static __always_inline int
  2. follow_link(struct path *link, struct nameidata *nd, void **p)
  3. {

  ...

  1.     error = -ELOOP;
  2.     if (unlikely(current->total_link_count >= 40))
  3.         goto out_put_nd_path;

  4.     cond_resched();
  5.     current->total_link_count++;

  6.     touch_atime(link);
  7.     nd_set_link(nd, NULL);

  ...

    在这里就是 Kernel 给符号链接设置的第二个限制:链接长度——简单地说就是在一个路径中符号链接的个数——不能超过 40 个。因为我门刚刚从 rcu-walk 模式切换过来,而 rcu-walk 是不允许抢占的,所以现在需要看看刚才有没有想要抢占但又抢占失败的进程,如果有的话就让人家先运行,毕竟我们现在是 ref-walk 模式,不是那么着急的,这就是 cond_resched 所做的(838)。接下来就是更新当前这个符号链接的访问时间(841),然后 nd_set_link 先把当前递归深度相应的路径字符串指针初始化成 NULL。揭晓来就要真正的“跟随链接”了。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link

点击(此处)折叠或打开

  ...

  1.     nd->last_type = LAST_BIND;
  2.     *p = dentry->d_inode->i_op->follow_link(dentry, nd);
  3.     error = PTR_ERR(*p);
  4.     if (IS_ERR(*p))
  5.         goto out_put_nd_path;

  ...

    这里主要就是启动具体文件系统自己的“跟随链接”机制,等它返回后 nd 中的 saved_names[nd->depth] 就会保存一个指向路径字符串的指针,随后我们就用着个字符串启动一次“全新的”路径查找。
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink > follow_link

点击(此处)折叠或打开

  ...

  1.     error = 0;
  2.     s = nd_get_link(nd);
  3.     if (s) {
  4.         if (unlikely(IS_ERR(s))) {
  5.             path_put(&nd->path);
  6.             put_link(nd, link, *p);
  7.             return PTR_ERR(s);
  8.         }
  9.         if (*s == '/') {
  10.             set_root(nd);
  11.             path_put(&nd->path);
  12.             nd->path = nd->root;
  13.             path_get(&nd->root);
  14.             nd->flags |= LOOKUP_JUMPED;
  15.         }
  16.         nd->inode = nd->path.dentry->d_inode;
  17.         error = link_path_walk(s, nd);
  18.         if (unlikely(error))
  19.             put_link(nd, link, *p);
  20.     }

  21.     return error;

  ...

  1. }
    s 就是上面提到的那个路径字符串,看看这个 if (s) 里的代码,是不是似曾相似?其中第一个 if(857)是错误处理我们不用关心。请看看第二个,如果路径以“/”开头就设置一下预设根目录和当前路径,是不是很像当年那个 path_init?如果不是“/”开头呢,那就以当前路径为起点,而这时 nd 就是“站在”当前路径上的,所以不用做啥处理直接就可以使用。接下来又是一个老朋友 link_path_walk,其实都不能说是老朋友,因为我们现在还在 link_path_walk 的肚子里呢。但现在还不能算是符号链接的嵌套(递归),还记得这个嵌套(递归)计数在哪里么?没错是在 nested_symlink,也就是说只有再次进入 nested_symlink 才能算是递归一次。想想我们是怎么进来的,别忘了 link_path_walk 并不处理路径中的最终目标,所以要想递归就必须在子路径中加入符号链接。简而言之,符号链接的嵌套(递归)是发生在该符号链接所指目标的路径中存在另一个符号链接。
    举个例子,比如现在有一个符号链接“a”它指向一个目录“/tmp/dir/”,就像这样“a -> /tmp/dir/”。这个目录下还有一个文件“/tmp/dir/file”,这时有另一个符号链接“b”,我们把它指向“a/file”,就像这样“b -> a/file”。如果这时访问“b”,就会递归一次,因为“b”所指的路径中“a/”就是一个符号链接且是一个子路径。
    通过这样的解释,大家应该了解在什么情况下会发生嵌套(递归)了吧。从 link_path_walk 返回之后,nd 应该已经“站在”最终目录等待着对最终目标的处理了。这正好应证了刚开始我们的猜测,接下来队最终目标的处理就会交给 walk_component,如果这个最终目标也是一个符号链接那么 walk_component 就会返回 1 并再一次 nested_symlink 里的 do-while 循环。
    当最终目标不是符号链接的时候,nested_symlink 的使命也就完成了:
【fs/namei.c】 sys_open > do_sys_open > do_filp_open > path_openat > link_path_walk > nested_symlink

点击(此处)折叠或打开

  ...

  1.     current->link_count--;
  2.     nd->depth--;
  3.     return res;
  4. }
    在这里就会恢复嵌套(递归)计数。
    现在符号链接我们也参观完毕了,那么 link_path_walk 也就结束了,接下来就要对打开最终目标了。
目录
相关文章
|
3月前
|
Linux 开发工具
Linux yum 使用时提示 获取 GPG 密钥失败Couldn‘t open file RPM-GPG-KEY-EPEL-7
Linux yum 使用时提示 获取 GPG 密钥失败Couldn‘t open file RPM-GPG-KEY-EPEL-7
227 3
|
20天前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
34 1
|
21天前
|
Linux
揭秘Linux心脏:那些让你的编程事半功倍的主要系统调用
【8月更文挑战第31天】Linux中的系统调用是操作系统提供给应用程序的接口,用于请求内核服务,如文件操作、进程控制等。本文列举了22种主要系统调用,包括fork()、exec()、exit()、wait()、open()、close()、read()、write()等,并通过示例代码展示了如何使用fork()创建新进程及使用open()、write()、close()操作文件。这些系统调用是Linux中最基本的接口,帮助应用程序与内核交互。
28 1
|
25天前
|
C语言
Linux0.11 系统调用进程创建与执行(九)(下)
Linux0.11 系统调用进程创建与执行(九)
20 1
|
25天前
|
存储 Linux 索引
Linux0.11 系统调用进程创建与执行(九)(上)
Linux0.11 系统调用进程创建与执行(九)
38 1
|
29天前
|
安全 Linux 程序员
在Linux中,系统调用是什么?
在Linux中,系统调用是什么?
|
1月前
|
安全 Linux 程序员
在Linux中,什么是系统调用?举例说明其作用是什么?
在Linux中,什么是系统调用?举例说明其作用是什么?
|
10天前
|
存储 Linux 程序员
Linux中的主要系统调用
【9月更文挑战第11天】在Linux操作系统中,通过系统调用`fork`创建新进程,子进程继承父进程的数据结构与代码,但可通过`execve`执行不同程序。`fork`返回值区分父子进程,`waitpid`让父进程等待子进程结束。
|
11天前
|
Linux 开发者 Python
从Windows到Linux,Python系统调用如何让代码飞翔🚀
【9月更文挑战第10天】在编程领域,跨越不同操作系统的障碍是常见挑战。Python凭借其“编写一次,到处运行”的理念,显著简化了这一过程。通过os、subprocess、shutil等标准库模块,Python提供了统一的接口,自动处理底层差异,使代码在Windows和Linux上无缝运行。例如,`open`函数在不同系统中以相同方式操作文件,而`subprocess`模块则能一致地执行系统命令。此外,第三方库如psutil进一步增强了跨平台能力,使开发者能够轻松编写高效且易维护的代码。借助Python的强大系统调用功能,跨平台编程变得简单高效。
13 0
|
1月前
|
存储 Linux API
Linux源码阅读笔记08-进程调度API系统调用案例分析
Linux源码阅读笔记08-进程调度API系统调用案例分析