Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1359060
  • 博文数量: 166
  • 博客积分: 46
  • 博客等级: 民兵
  • 技术积分: 4061
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-11 13:45
个人简介

现任职北京某互联网公司运维经理,高级架构师,涉足互联网运维行业已经超过10年。曾服务于京东商城,互动百科等互联网公司,早期运维界新星。 长期专研,C语言开发,操作系统内核,大型互联网架构。http://www.bdkyr.com

文章分类

分类: LINUX

2015-02-02 23:05:20

    现在,我们的“路径行走”只剩下最后一个小问题需要处理了——符号链接。
【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 也就结束了,接下来就要对打开最终目标了。
阅读(1886) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~