首先我们需要了解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()
调用set_nameidata()初始化nameidata,
然后依次调用尝试path_lookupat()带不同的flags,分别执行RCU-walk, REF-walk, revalidate-walk。
这个结构体用来保存lookup的中间的状态,因为需要一个目录一个目录去找。这个结构体是定义在fs/namei.c里面的。外界不能访问。
先引入这两个
int dfd,这个是用户传入的fd
struct filename *name,这个是路径转换后的filename。
这两个初始化设置,作为lookup的起点。
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()进行验证。
这里会初始化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。
处理所有的中间目录。
对每个目录执行,
1 may_lookup()
检查父目录权限
2 hash_name()
计算hash值,以及下一个name,下一个name的类型。
3 如果是最后一个name,那么先返回,此函数,不处理最后一个name。
如果是中间name,那么调用
link = walk_component(nd, WALK_MORE);
中间部分可能是符号链接,这里会保存到nd->stack[--depth]里面,可能有多层符号链接。进行入栈和出栈操作。
确定父目录是否有权限
如果是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()
忽略符号链接,
调用lookup_fast(),没找到,再调用lookup_slow()。
找到dentry后,走step_into()
执行 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。
fast不会创建dentry,如果没找到,会走slow。
1 先给父目录的inode上锁
inode_lock_shared(inode);
2 调用 res = __lookup_slow(name, dir, flags);
这个函数,在inode上了共享锁后调用,因此必须调用d_alloc_parallel()分配dentry,这里面会处理多线程竞争的情况。
竞争成功的,会加上DCACHE_PAR_LOOKUP,标志,此时走文件系统的lookup()接口,分配inode。
将找到的dentry替换当前的parent,并且处理挂载点的情况。判断dentry是否合法等。处理符号链接等。
调用基础函数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