ILD

pahtname lookup
作者:Yuan Jianpeng 邮箱:yuanjp89@163.com
发布时间:2026-3-17 站点:Inside Linux Development

首先我们需要了解dentry和inode的基本知识,见参考文档。

哪些地方会查找目录呢,


入口

1 执行stat()去读取一个文件的属性的时候,就需要查找到dentry。

fs/stat.c

vfs_statx() ->

fs/namei.c

filename_lookup()


2 打开文件

fs/open.c

do_sys_openat2() ->

fs/namei.c

do_filep_open() ->path_openat()


3 创建文件

fs/namei.c

do_mknodat() -> filename_create()


filename_lookup()

调用set_nameidata()初始化nameidata,

然后依次调用尝试path_lookupat()带不同的flags,分别执行RCU-walk, REF-walk, revalidate-walk。


nameidata

这个结构体用来保存lookup的中间的状态,因为需要一个目录一个目录去找。这个结构体是定义在fs/namei.c里面的。外界不能访问。

先引入这两个

int dfd,这个是用户传入的fd

struct filename *name,这个是路径转换后的filename。

这两个初始化设置,作为lookup的起点。


path_lookupat()

1 调用const char *s = path_init(nd, flags); 初始化nd。

最主要的目的是,初始化path。返回剩下路径的字符串


2 解析

while (!(err = link_path_walk(s, nd)) &&

    (s = lookup_last(nd)) != NULL);

link_path_walk会解析非结尾的所有目录,并且将结尾的name保存起来。

lookup_last是解析尾巴的部分。

这里需要循环,是因为尾巴可能是个符号链接。


3 调用complete_walk()完成walk

最重要的是调用 dentry->d_op->d_weak_revalidate()进行验证。


path_init()

这里会初始化nameidata的很多字段。

1 如果是RCU-walk,则上rcu锁:rcu_read_lock(),否则初始化seq & next_seq为0.

2 保存全局的mount_lock的序列号到m_seq,rename_lock的序列号到r_seq。

3 如果是绝对路径,调用nd_jump_root(nd),读取root到path。

4 如果是AT_FDCWD,则读取当前pwd到path。

        这里分两种,RCU-walk和REF-walk。它们读取fs->pwd的方式不同,一个用seq,一个用spin lock。

5 否则,读取fd的path到nd->path

        也分RCU-walk和REF-walk。


link_path_walk()

处理所有的中间目录。

对每个目录执行,

1 may_lookup()

    检查父目录权限

2 hash_name()

    计算hash值,以及下一个name,下一个name的类型。


3 如果是最后一个name,那么先返回,此函数,不处理最后一个name。

如果是中间name,那么调用

link = walk_component(nd, WALK_MORE);


中间部分可能是符号链接,这里会保存到nd->stack[--depth]里面,可能有多层符号链接。进行入栈和出栈操作。


may_lookup()

确定父目录是否有权限


如果是RCU-walk

调用inode_permission(),mask为 MAY_NOT_BLOCK|MAY_EXEC。不允许阻塞。

如果权限检查失败,则跳出RCU模式,用阻塞模式查询。


inode_permission正常是检查i_mode,不需要读磁盘,但是ACL可能需要读磁盘,可能会阻塞。

它会调用sb_permission()检查文件系统是否只读。

do_inode_permission(),检查inode的权限。

    generic_permission()或者inode->i_op->permission()


walk_component()

忽略符号链接,

调用lookup_fast(),没找到,再调用lookup_slow()。

找到dentry后,走step_into()


lookup_fast()

执行 fast lockless (but racy) lookup。这个接口不会创建dentry。


这里分两种情况:

1 RCU-walk

    a. 调用 dentry = __d_lookup_rcu(parent, &nd->last, &nd->next_seq);

    使用rcu的方式,查找dcache,如果没找到,会try_to_unlazy(),离开RCU-walk模式,返回NULL。

    dentry的seq保存在next_seq。


   b. 判断parent的seq,是否一致,如果不一致,则返回-ECHILD,

    c. 调用d_revalidate(),由fs验证dentry。如果验证失败,会调用try_to_unlazy_next()离开RCU-walk,

        离开的时候,会拿取ref,然后再次d_revalidate()。


    注意RCU-walk找到的dentry,没有拿到reference code。


2 REF-walk

    a. 调用    dentry = __d_lookup(parent, &nd->last); 获取dentry,获取的dentry。

        注意,此dentry已经拿到了ref。

   b. 调用d_revalidate()


最后,会判断d_revalidate()的返回值,如果status不是正数,则意味着失败,如果是负数,

则调用d_invalidate(dentry)释放dentry。然后dput()释放引用计数。


总结:

lookup_fast,

在RCU模式找到dentry,不会获取引用计数。没找到会离开RCU模式,返回NULL。

如果找到的dentry不合法,会返回错误码。最终调用者会从根目录开始重新走REF-walk


在REF模式,找到dentry,会获取引用计数。没找到返回NULL。


lookup_slow()

fast不会创建dentry,如果没找到,会走slow。

1 先给父目录的inode上锁

    inode_lock_shared(inode);

2 调用 res = __lookup_slow(name, dir, flags);



__lookup_slow()

这个函数,在inode上了共享锁后调用,因此必须调用d_alloc_parallel()分配dentry,这里面会处理多线程竞争的情况。

竞争成功的,会加上DCACHE_PAR_LOOKUP,标志,此时走文件系统的lookup()接口,分配inode。


step_into()

将找到的dentry替换当前的parent,并且处理挂载点的情况。判断dentry是否合法等。处理符号链接等。


lookup_last()

调用基础函数walk_component(nd, WALK_TRAILING)查找路径的最后一个部分。



参考文档

内核文件系统的文档入口

https://www.kernel.org/doc/html/latest/filesystems/index.html


介绍vfs的文档

https://www.kernel.org/doc/html/latest/filesystems/vfs.html


路径查找的文档:

https://www.kernel.org/doc/html/latest/filesystems/path-lookup.html


vfs用到的相关锁的文档:

https://www.kernel.org/doc/html/latest/filesystems/locking.html


目录相关锁的文档:

https://www.kernel.org/doc/html/latest/filesystems/directory-locking.html


Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.