Linux中文件路径解析函数path_walk的实现
VFS路径解析整体架构
路径组件处理流程
1. 权限检查 (exec_permission_lite → permission)
2. 名称哈希计算 (init_name_hash → partial_name_hash)
3. 特殊目录处理 (., .. 通过 follow_dotdot)
4. 目录项查找 (do_lookup → __d_lookup)
5. 挂载点处理 (follow_mount)
6. 符号链接解析 (do_follow_link)
边界情况处理
- 根目录:防止超出chroot环境
- 挂载点:正确处理文件系统边界
- 符号链接循环:深度限制和错误检测
- 无效
inode:完善的错误处理和资源释放
VFS层路径解析path_walk
int fastcall path_walk(const char * name, struct nameidata *nd)
{current->total_link_count = 0;return link_path_walk(name, nd);
}
int fastcall link_path_walk(const char * name, struct nameidata *nd)
{struct path next;struct inode *inode;int err;unsigned int lookup_flags = nd->flags;while (*name=='/')name++;if (!*name)goto return_reval;inode = nd->dentry->d_inode;if (nd->depth)lookup_flags = LOOKUP_FOLLOW;/* At this point we know we have a real path component. */for(;;) {unsigned long hash;struct qstr this;unsigned int c;err = exec_permission_lite(inode, nd);if (err == -EAGAIN) { err = permission(inode, MAY_EXEC, nd);}if (err)break;this.name = name;c = *(const unsigned char *)name;hash = init_name_hash();do {name++;hash = partial_name_hash(c, hash);c = *(const unsigned char *)name;} while (c && (c != '/'));this.len = name - (const char *) this.name;this.hash = end_name_hash(hash);/* remove trailing slashes? */if (!c)goto last_component;while (*++name == '/');if (!*name)goto last_with_slashes;/** "." and ".." are special - ".." especially so because it has* to be able to know about the current root directory and* parent relationships.*/if (this.name[0] == '.') switch (this.len) {default:break;case 2: if (this.name[1] != '.')break;follow_dotdot(&nd->mnt, &nd->dentry);inode = nd->dentry->d_inode;/* fallthrough */case 1:continue;}/** See if the low-level filesystem might want* to use its own hash..*/if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {err = nd->dentry->d_op->d_hash(nd->dentry, &this);if (err < 0)break;}nd->flags |= LOOKUP_CONTINUE;/* This does the actual lookups.. */err = do_lookup(nd, &this, &next);if (err)break;/* Check mountpoints.. */follow_mount(&next.mnt, &next.dentry);err = -ENOENT;inode = next.dentry->d_inode;if (!inode)goto out_dput;err = -ENOTDIR; if (!inode->i_op)goto out_dput;if (inode->i_op->follow_link) {mntget(next.mnt);err = do_follow_link(next.dentry, nd);dput(next.dentry);mntput(next.mnt);if (err)goto return_err;err = -ENOENT;inode = nd->dentry->d_inode;if (!inode)break;err = -ENOTDIR; if (!inode->i_op)break;} else {dput(nd->dentry);nd->mnt = next.mnt;nd->dentry = next.dentry;}err = -ENOTDIR; if (!inode->i_op->lookup)break;continue;/* here ends the main loop */last_with_slashes:lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:nd->flags &= ~LOOKUP_CONTINUE;if (lookup_flags & LOOKUP_PARENT)goto lookup_parent;if (this.name[0] == '.') switch (this.len) {default:break;case 2: if (this.name[1] != '.')break;follow_dotdot(&nd->mnt, &nd->dentry);inode = nd->dentry->d_inode;/* fallthrough */case 1:goto return_reval;}if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {err = nd->dentry->d_op->d_hash(nd->dentry, &this);if (err < 0)break;}err = do_lookup(nd, &this, &next);if (err)break;follow_mount(&next.mnt, &next.dentry);inode = next.dentry->d_inode;if ((lookup_flags & LOOKUP_FOLLOW)&& inode && inode->i_op && inode->i_op->follow_link) {mntget(next.mnt);err = do_follow_link(next.dentry, nd);dput(next.dentry);mntput(next.mnt);if (err)goto return_err;inode = nd->dentry->d_inode;} else {dput(nd->dentry);nd->mnt = next.mnt;nd->dentry = next.dentry;}err = -ENOENT;if (!inode)break;if (lookup_flags & LOOKUP_DIRECTORY) {err = -ENOTDIR; if (!inode->i_op || !inode->i_op->lookup)break;}goto return_base;
lookup_parent:nd->last = this;nd->last_type = LAST_NORM;if (this.name[0] != '.')goto return_base;if (this.len == 1)nd->last_type = LAST_DOT;else if (this.len == 2 && this.name[1] == '.')nd->last_type = LAST_DOTDOT;elsegoto return_base;
return_reval:/** We bypassed the ordinary revalidation routines.* We may need to check the cached dentry for staleness.*/if (nd->dentry && nd->dentry->d_sb &&(nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {err = -ESTALE;/* Note: we do not d_invalidate() */if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))break;}
return_base:return 0;
out_dput:dput(next.dentry);break;}path_release(nd);
return_err:return err;
}
函数功能概述
这两个函数是Linux内核VFS层路径解析的核心,负责逐组件遍历文件路径,处理目录查找、符号链接、挂载点、特殊目录(.和…)等复杂情况
代码详细解释
第一部分:path_walk函数
int fastcall path_walk(const char * name, struct nameidata *nd)
{current->total_link_count = 0;return link_path_walk(name, nd);
}
- 符号链接计数器重置:
total_link_count = 0,防止符号链接无限递归 - 委托实际工作:直接调用
link_path_walk进行路径遍历
第二部分:link_path_walk初始化
int fastcall link_path_walk(const char * name, struct nameidata *nd)
{struct path next;struct inode *inode;int err;unsigned int lookup_flags = nd->flags;while (*name=='/')name++;if (!*name)goto return_reval;
- 变量声明:
next:存储查找到的下一个路径组件inode:当前目录的inodelookup_flags:保存查找标志的副本
- 跳过前导斜杠:
while (*name=='/') name++,处理多个连续斜杠 - 空路径检查:如果路径只剩空字符串,跳转
inode = nd->dentry->d_inode;if (nd->depth)lookup_flags = LOOKUP_FOLLOW;
- 获取当前
inode:从nameidata中的dentry获取 - 符号链接深度处理:如果已经在符号链接中(
nd->depth),强制设置LOOKUP_FOLLOW
第三部分:主循环开始
/* At this point we know we have a real path component. */for(;;) {unsigned long hash;struct qstr this;unsigned int c;err = exec_permission_lite(inode, nd);if (err == -EAGAIN) { err = permission(inode, MAY_EXEC, nd);}if (err)break;
- 无限循环:逐个处理路径组件
- 执行权限检查:
- 先尝试快速检查
exec_permission_lite - 如果需要完整检查(
-EAGAIN),调用完整的permission函数 - 检查失败则跳出循环
- 先尝试快速检查
第四部分:路径组件解析
this.name = name;c = *(const unsigned char *)name;hash = init_name_hash();do {name++;hash = partial_name_hash(c, hash);c = *(const unsigned char *)name;} while (c && (c != '/'));this.len = name - (const char *) this.name;this.hash = end_name_hash(hash);
- 组件信息设置:
this.name:组件起始位置c:当前字符
- hash计算:
init_name_hash():初始化hash值- 循环计算每个字符的hash,直到遇到’/'或字符串结束
end_name_hash(hash):完成hash计算
- 组件长度:
this.len计算组件名字长度
/* remove trailing slashes? */if (!c)goto last_component;while (*++name == '/');if (!*name)goto last_with_slashes;
- 路径结束判断:
- 如果没有更多字符(
!c),是最后一个组件 - 跳过后续斜杠,如果路径结束,确认为带斜杠的最后一个组件
- 如果没有更多字符(
第五部分:特殊目录处理
if (this.name[0] == '.') switch (this.len) {default:break;case 2: if (this.name[1] != '.')break;follow_dotdot(&nd->mnt, &nd->dentry);inode = nd->dentry->d_inode;/* fallthrough */case 1:continue;}
- “.” 和 “…” 处理:
- 单点".":直接继续下一个组件(
continue) - 双点"…":调用
follow_dotdot转到父目录,更新inode后继续
- 单点".":直接继续下一个组件(
- 这里
break是跳出switch继续正常查找流程,continue是结束本次循环,进行下一次循环
第六部分:目录项查找
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {err = nd->dentry->d_op->d_hash(nd->dentry, &this);if (err < 0)break;}nd->flags |= LOOKUP_CONTINUE;err = do_lookup(nd, &this, &next);if (err)break;
- 文件系统特定hash:如果支持
d_hash操作,调用它 - 设置继续标志:
LOOKUP_CONTINUE表示还有更多组件 - 实际查找:
do_lookup在目录中查找当前组件
第七部分:挂载点和inode检查
/* Check mountpoints.. */follow_mount(&next.mnt, &next.dentry);err = -ENOENT;inode = next.dentry->d_inode;if (!inode)goto out_dput;err = -ENOTDIR; if (!inode->i_op)goto out_dput;
- 挂载点处理:
follow_mount处理挂载点覆盖 inode有效性检查:- 检查
inode是否存在(-ENOENT) - 检查
inode操作是否有效(-ENOTDIR)
- 检查
第八部分:符号链接处理
if (inode->i_op->follow_link) {mntget(next.mnt);err = do_follow_link(next.dentry, nd);dput(next.dentry);mntput(next.mnt);if (err)goto return_err;err = -ENOENT;inode = nd->dentry->d_inode;if (!inode)break;err = -ENOTDIR; if (!inode->i_op)break;} else {dput(nd->dentry);nd->mnt = next.mnt;nd->dentry = next.dentry;}
- 符号链接分支:
- 增加引用计数
mntget do_follow_link处理符号链接解析- 释放临时
dentrydput - 检查新的
inode有效性
- 增加引用计数
- 普通目录项分支:
- 更新
nameidata为新的目录项
- 更新
err = -ENOTDIR; if (!inode->i_op->lookup)break;continue;
- 目录验证:确保当前
inode支持查找操作(是目录) - 继续循环:处理下一个路径组件
第九部分:最后一个组件处理
last_with_slashes:lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:nd->flags &= ~LOOKUP_CONTINUE;if (lookup_flags & LOOKUP_PARENT)goto lookup_parent;
- 带斜杠的最后组件:强制跟随符号链接和目录检查
- 清除继续标志:没有更多组件了
- 父目录查找:如果请求父目录查找,跳转到相应处理
if (this.name[0] == '.') switch (this.len) {default:break;case 2: if (this.name[1] != '.')break;follow_dotdot(&nd->mnt, &nd->dentry);inode = nd->dentry->d_inode;/* fallthrough */case 1:goto return_reval;}
- 最后组件的特殊目录处理:与之前类似,但跳转到重新验证
第十部分:最后组件查找
if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {err = nd->dentry->d_op->d_hash(nd->dentry, &this);if (err < 0)break;}err = do_lookup(nd, &this, &next);if (err)break;follow_mount(&next.mnt, &next.dentry);inode = next.dentry->d_inode;
- 最后组件的查找:与中间组件类似的查找流程
if ((lookup_flags & LOOKUP_FOLLOW)&& inode && inode->i_op && inode->i_op->follow_link) {mntget(next.mnt);err = do_follow_link(next.dentry, nd);dput(next.dentry);mntput(next.mnt);if (err)goto return_err;inode = nd->dentry->d_inode;} else {dput(nd->dentry);nd->mnt = next.mnt;nd->dentry = next.dentry;}
- 最后组件的符号链接:根据
LOOKUP_FOLLOW决定是否跟随
第十一部分:目录检查和返回处理
err = -ENOENT;if (!inode)break;if (lookup_flags & LOOKUP_DIRECTORY) {err = -ENOTDIR; if (!inode->i_op || !inode->i_op->lookup)break;}goto return_base;
- 目录要求检查:如果要求是目录,验证有lookup操作
lookup_parent:nd->last = this;nd->last_type = LAST_NORM;if (this.name[0] != '.')goto return_base;if (this.len == 1)nd->last_type = LAST_DOT;else if (this.len == 2 && this.name[1] == '.')nd->last_type = LAST_DOTDOT;elsegoto return_base;
- 父目录查找处理:
- 保存最后组件信息
- 标记特殊目录类型(LAST_DOT, LAST_DOTDOT)
return_reval:if (nd->dentry && nd->dentry->d_sb &&(nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {err = -ESTALE;if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))break;}
return_base:return 0;
- 重新验证检查:某些文件系统需要重新验证".“和”…"
- 成功返回:返回0表示成功
第十二部分:错误处理
out_dput:dput(next.dentry);break;}path_release(nd);
return_err:return err;
- 错误处理:
- 释放临时
dentry - 释放整个路径资源
path_release - 返回错误码
- 释放临时
函数功能总结
- 路径解析:逐组件解析文件路径
- 权限检查:对每个目录组件执行执行权限检查
- 特殊目录处理:正确处理".“和”…"
- 哈希计算:为目录项名称计算哈希值
- 目录查找:在文件系统中查找目录项
- 挂载点处理:正确处理文件系统挂载点
- 符号链接解析:处理和跟随符号链接
- 引用计数管理:正确管理
dentry和vfsmount的引用计数 - 错误处理:全面的错误检测和资源清理
- 重新验证支持:支持文件系统特定的重新验证
处理"…"目录follow_dotdot
static inline void follow_dotdot(struct vfsmount **mnt, struct dentry **dentry)
{while(1) {struct vfsmount *parent;struct dentry *old = *dentry;read_lock(¤t->fs->lock);if (*dentry == current->fs->root &&*mnt == current->fs->rootmnt) {read_unlock(¤t->fs->lock);break;}read_unlock(¤t->fs->lock);spin_lock(&dcache_lock);if (*dentry != (*mnt)->mnt_root) {*dentry = dget((*dentry)->d_parent);spin_unlock(&dcache_lock);dput(old);break;}spin_unlock(&dcache_lock);spin_lock(&vfsmount_lock);parent = (*mnt)->mnt_parent;if (parent == *mnt) {spin_unlock(&vfsmount_lock);break;}mntget(parent);*dentry = dget((*mnt)->mnt_mountpoint);spin_unlock(&vfsmount_lock);dput(old);mntput(*mnt);*mnt = parent;}follow_mount(mnt, dentry);
}
static int follow_mount(struct vfsmount **mnt, struct dentry **dentry)
{int res = 0;while (d_mountpoint(*dentry)) {struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);if (!mounted)break;mntput(*mnt);*mnt = mounted;dput(*dentry);*dentry = dget(mounted->mnt_root);res = 1;}return res;
}
函数功能概述
follow_dotdot用于处理"…"目录(转到父目录),需要考虑根目录、挂载点等边界情况。follow_mount用于处理挂载点覆盖,确保进入正确的文件系统
代码详细解释
第一部分:follow_dotdot - 根目录检查
static inline void follow_dotdot(struct vfsmount **mnt, struct dentry **dentry)
{while(1) {struct vfsmount *parent;struct dentry *old = *dentry;read_lock(¤t->fs->lock);if (*dentry == current->fs->root &&*mnt == current->fs->rootmnt) {read_unlock(¤t->fs->lock);break;}read_unlock(¤t->fs->lock);
- 无限循环:
while(1)因为可能需要跨多个挂载点 - 保存旧引用:
old = *dentry用于后续释放引用计数 - 根目录检查:
- 获取读锁保护进程文件系统信息
- 检查是否到达进程的根目录:
*dentry == current->fs->root && *mnt == current->fs->rootmnt - 如果是根目录,不能再向上,跳出循环
- 释放读锁
第二部分:follow_dotdot - 当前文件系统内处理
spin_lock(&dcache_lock);if (*dentry != (*mnt)->mnt_root) {*dentry = dget((*dentry)->d_parent);spin_unlock(&dcache_lock);dput(old);break;}spin_unlock(&dcache_lock);
- 检查是否在挂载点根目录:
- 获取
dcache锁保护目录项结构 *dentry != (*mnt)->mnt_root:如果当前dentry不是这个挂载点的根目录
- 获取
- 普通父目录处理:
*dentry = dget((*dentry)->d_parent):获取父目录,增加引用计数- 释放
dcache锁 dput(old):释放旧dentry的引用计数break:跳出循环,处理完成
第三部分:follow_dotdot - 挂载点跨越处理
spin_lock(&vfsmount_lock);parent = (*mnt)->mnt_parent;if (parent == *mnt) {spin_unlock(&vfsmount_lock);break;}
- 挂载点边界处理:
- 获取
vfsmount锁保护挂载点结构 parent = (*mnt)->mnt_parent:获取父挂载点- 自引用检查:
parent == *mnt如果是自引用(最顶层挂载点),跳出循环
- 获取
mntget(parent);*dentry = dget((*mnt)->mnt_mountpoint);spin_unlock(&vfsmount_lock);dput(old);mntput(*mnt);*mnt = parent;}
- 跨越挂载点:
mntget(parent):增加父挂载点的引用计数*dentry = dget((*mnt)->mnt_mountpoint):获取在父文件系统中的挂载点目录- 释放
vfsmount锁 - 释放资源:
dput(old)和mntput(*mnt) - 更新当前挂载点:
*mnt = parent - 循环继续,可能还需要继续向上
第四部分:follow_dotdot - 最终处理
follow_mount(mnt, dentry);
}
- 挂载点检查:处理可能当前目录正好是挂载点的情况
第五部分:follow_mount - 多重挂载处理
static int follow_mount(struct vfsmount **mnt, struct dentry **dentry)
{int res = 0;while (d_mountpoint(*dentry)) {struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);if (!mounted)break;
- 初始化:
res = 0表示默认没有进行挂载点跳转 - 挂载点循环:
while (d_mountpoint(*dentry))当前dentry是挂载点 - 查找已挂载的文件系统:
mounted = lookup_mnt(*mnt, *dentry):查找在这个挂载点上的文件系统- 如果找不到(
!mounted),跳出循环
mntput(*mnt);*mnt = mounted;dput(*dentry);*dentry = dget(mounted->mnt_root);res = 1;}return res;
- 切换到挂载的文件系统:
mntput(*mnt):释放旧挂载点的引用计数*mnt = mounted:切换到新的挂载点dput(*dentry):释放旧dentry的引用计数*dentry = dget(mounted->mnt_root):设置为新文件系统的根目录res = 1:标记进行了挂载点跳转
- 返回结果:表示是否进行了挂载点处理
详细执行场景分析
场景1:普通目录中的"…"
当前: /home/user/documents/
执行"..": /home/user/
follow_dotdot 在dcache_lock分支处理,直接获取d_parent
场景2:存在挂载点的处理
挂载结构:
/ (根文件系统) [vfsmount: rootfs]
└── /mnt (目录) └── /mnt/nested (挂载点) [vfsmount: nested_mnt, 挂载了 /dev/sdb1]当前路径: /mnt/nested
执行命令: cd ..
follow_dotdot 当前在挂载点根目录,需要跨越挂载点边界 dentry->/dev/sdb1
follow_dotdot 跨越挂载点边界,回到原来文件系统 dentry->/mnt/nested
follow_dotdot 当前不在挂载点根目录,直接获取d_parent dentry->/mnt
场景3:多重挂载的处理
mkdir -p /mnt/nested/test
先 sudo mount /dev/sdc1 /mnt/nested/test
再 sudo mount /dev/sdb1 /mnt/nested
挂载结构:
/ (根文件系统) [vfsmount: rootfs]
└── /mnt (目录) └── /mnt/nested (挂载点) [vfsmount: nested_mnt, 挂载了 /dev/sdb1]└── /mnt/nested/test (挂载点) [vfsmount: test_mnt, 挂载了 /dev/sdc1]当前路径: /mnt/nested/test
执行命令: cd ..
follow_dotdot 当前在挂载点根目录,需要跨越挂载点边界 dentry->/dev/sdc1
follow_dotdot 跨越挂载点边界,回到原来文件系统 dentry->/mnt/nested/test
follow_dotdot 当前不在挂载点根目录,直接获取d_parent dentry->/mnt/nested
follow_mount 当前dentry是挂载点,自动进入挂载的文件系统 dentry->/dev/sdb1
**注意:**这里必须先挂载目录/mnt/nested/test,再挂载/mnt/nested,这样挂载点才都是原文件系统。如果先挂载/mnt/nested,再挂载/mnt/nested/test,因为挂载/mnt/nested以后会隐藏原先文件系统,访问/mnt/nested/test的是挂载的文件系统目录,所以挂载点将会在挂载的文件系统上
函数功能总结
- "…"语义实现:正确处理转到父目录的请求
- 根目录边界:防止超出进程根目录(包括chroot)
- 挂载点跨越:正确处理文件系统边界
- 嵌套挂载支持:支持多层挂载点结构
- 挂载点覆盖:自动进入挂载的文件系统
目录项查找函数do_lookup
static int do_lookup(struct nameidata *nd, struct qstr *name,struct path *path)
{struct vfsmount *mnt = nd->mnt;struct dentry *dentry = __d_lookup(nd->dentry, name);if (!dentry)goto need_lookup;if (dentry->d_op && dentry->d_op->d_revalidate)goto need_revalidate;
done:path->mnt = mnt;path->dentry = dentry;return 0;need_lookup:dentry = real_lookup(nd->dentry, name, nd);if (IS_ERR(dentry))goto fail;goto done;need_revalidate:if (dentry->d_op->d_revalidate(dentry, nd))goto done;if (d_invalidate(dentry))goto done;dput(dentry);goto need_lookup;fail:return PTR_ERR(dentry);
}
struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
{unsigned int len = name->len;unsigned int hash = name->hash;const unsigned char *str = name->name;struct hlist_head *head = d_hash(parent,hash);struct dentry *found = NULL;struct hlist_node *node;rcu_read_lock();hlist_for_each_rcu(node, head) {struct dentry *dentry; struct qstr *qstr;dentry = hlist_entry(node, struct dentry, d_hash);if (dentry->d_name.hash != hash)continue;if (dentry->d_parent != parent)continue;spin_lock(&dentry->d_lock);/** Recheck the dentry after taking the lock - d_move may have* changed things. Don't bother checking the hash because we're* about to compare the whole name anyway.*/if (dentry->d_parent != parent)goto next;/** It is safe to compare names since d_move() cannot* change the qstr (protected by d_lock).*/qstr = &dentry->d_name;if (parent->d_op && parent->d_op->d_compare) {if (parent->d_op->d_compare(parent, qstr, name))goto next;} else {if (qstr->len != len)goto next;if (memcmp(qstr->name, str, len))goto next;}if (!d_unhashed(dentry)) {atomic_inc(&dentry->d_count);found = dentry;}spin_unlock(&dentry->d_lock);brek;
next:spin_unlock(&dentry->d_lock);}rcu_read_unlock();return found;
}
函数功能概述
do_lookup和__d_lookup协同工作,用于在目录中查找指定的目录项(dentry)
代码详细解释
第一部分:do_lookup - 主查找逻辑
static int do_lookup(struct nameidata *nd, struct qstr *name,struct path *path)
{struct vfsmount *mnt = nd->mnt;struct dentry *dentry = __d_lookup(nd->dentry, name);
- 参数说明:
nd: 路径查找状态name: 要查找的组件名称path: 输出参数,存储查找结果
- 保存挂载点:
mnt = nd->mnt保存当前挂载点 dcache查找:__d_lookup(nd->dentry, name)在目录项缓存中查找
if (!dentry)goto need_lookup;if (dentry->d_op && dentry->d_op->d_revalidate)goto need_revalidate;
- 缓存未命中:如果
__d_lookup返回NULL,跳转到实际查找 - 需要重新验证:如果
dentry有d_revalidate操作,需要验证有效性
第二部分:do_lookup - 成功处理
done:path->mnt = mnt;path->dentry = dentry;return 0;
- 设置结果:将找到的
dentry和mnt设置到输出path中 - 返回成功:返回0表示查找成功
第三部分:do_lookup - 缓存未命中处理
need_lookup:dentry = real_lookup(nd->dentry, name, nd);if (IS_ERR(dentry))goto fail;goto done;
- 实际文件系统查找:
real_lookup调用文件系统特定的查找方法 - 错误检查:如果查找失败,跳转到错误处理
- 成功处理:跳转到done设置结果
第四部分:do_lookup - 重新验证处理
need_revalidate:if (dentry->d_op->d_revalidate(dentry, nd))goto done;if (d_invalidate(dentry))goto done;dput(dentry);goto need_lookup;
- 重新验证:调用
d_revalidate验证dentry有效性- 返回非0表示有效,跳转到done
- 尝试失效:
d_invalidate尝试标记dentry为失效- 成功则跳转到done(使用失效的
dentry)
- 成功则跳转到done(使用失效的
- 重新查找:释放旧
dentry,跳转到need_lookup重新查找
第五部分:do_lookup - 错误处理
fail:return PTR_ERR(dentry);
- 返回错误:将错误指针转换为错误码返回
第六部分:__d_lookup - 初始化准备
struct dentry * __d_lookup(struct dentry * parent, struct qstr * name)
{unsigned int len = name->len;unsigned int hash = name->hash;const unsigned char *str = name->name;struct hlist_head *head = d_hash(parent,hash);
- 参数提取:从
qstr中获取名称长度、hash值和字符串指针 - 计算hash桶:
d_hash(parent,hash)根据父目录和名称hash计算hash桶位置
第七部分:__d_lookup - RCU保护遍历
struct dentry *found = NULL;struct hlist_node *node;rcu_read_lock();hlist_for_each_rcu(node, head) {struct dentry *dentry; struct qstr *qstr;dentry = hlist_entry(node, struct dentry, d_hash);
- 初始化结果:
found = NULL默认未找到 - RCU读锁:
rcu_read_lock()保护RCU遍历过程 - 遍历hash桶:
hlist_for_each_rcu安全遍历RCU保护的链表 - 获取dentry:
hlist_entry从链表节点获取dentry结构
第八部分:__d_lookup - 快速筛选
if (dentry->d_name.hash != hash)continue;if (dentry->d_parent != parent)continue;
- hash值匹配:比较
dentry的hash值与目标hash,不匹配则跳过 - 父目录匹配:比较
dentry的父目录与目标父目录,不匹配则跳过
第九部分:__d_lookup - 精确比较
spin_lock(&dentry->d_lock);if (dentry->d_parent != parent)goto next;
- 获取
dentry锁:保护dentry结构不被并发修改 - 重新检查父目录:防止在获取锁的过程中被修改
qstr = &dentry->d_name;if (parent->d_op && parent->d_op->d_compare) {if (parent->d_op->d_compare(parent, qstr, name))goto next;} else {if (qstr->len != len)goto next;if (memcmp(qstr->name, str, len))goto next;}
- 名称比较:
- 文件系统特定比较:如果父目录有
d_compare操作,调用它进行比较 - 默认内存比较:比较长度和内存内容
- 文件系统特定比较:如果父目录有
第十部分:__d_lookup - 成功处理
if (!d_unhashed(dentry)) {atomic_inc(&dentry->d_count);found = dentry;}spin_unlock(&dentry->d_lock);break;
next:spin_unlock(&dentry->d_lock);}
- 有效性检查:
!d_unhashed(dentry)确保dentry没有从hash表中移除 - 增加引用计数:
atomic_inc(&dentry->d_count)防止dentry被释放 - 设置结果:
found = dentry记录找到的dentry - 跳出循环:找到匹配项后立即跳出
- next标签:释放锁,继续遍历下一个
dentry
第十一部分:__d_lookup - 清理返回
rcu_read_unlock();return found;
}
- 释放RCU锁:
rcu_read_unlock()结束RCU读临界区 - 返回结果:返回找到的
dentry或NULL
符号链接跟随函数do_follow_link
static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd)
{int err = -ELOOP;if (current->link_count >= MAX_NESTED_LINKS)goto loop;if (current->total_link_count >= 40)goto loop;BUG_ON(nd->depth >= MAX_NESTED_LINKS);cond_resched();err = security_inode_follow_link(dentry, nd);if (err)goto loop;current->link_count++;current->total_link_count++;nd->depth++;touch_atime(nd->mnt, dentry);nd_set_link(nd, NULL);err = dentry->d_inode->i_op->follow_link(dentry, nd);if (!err) {char *s = nd_get_link(nd);if (s)err = __vfs_follow_link(nd, s);if (dentry->d_inode->i_op->put_link)dentry->d_inode->i_op->put_link(dentry, nd);}current->link_count--;nd->depth--;return err;
loop:path_release(nd);return err;
}
函数功能概述
do_follow_link是Linux内核中处理符号链接跟随的核心函数,负责安全地解析符号链接内容,防止符号链接无限递归,并管理链接跟随的深度和资源
代码详细解释
第一部分:符号链接深度检查
static inline int do_follow_link(struct dentry *dentry, struct nameidata *nd)
{int err = -ELOOP;if (current->link_count >= MAX_NESTED_LINKS)goto loop;if (current->total_link_count >= 40)goto loop;BUG_ON(nd->depth >= MAX_NESTED_LINKS);
- 初始化错误码:
err = -ELOOP默认错误是符号链接循环 - 嵌套链接检查:
current->link_count:当前调用链中的符号链接深度MAX_NESTED_LINKS:最大嵌套链接数(通常为5)- 如果超过限制,跳转到错误处理
- 总链接数检查:
current->total_link_count:整个路径解析过程中的总链接数- 限制为40,防止过度复杂的链接结构
- 链接深度断言:
BUG_ON(nd->depth >= MAX_NESTED_LINKS)如果nd->depth异常,触发内核崩溃
第二部分:调度和安全检查
cond_resched();err = security_inode_follow_link(dentry, nd);if (err)goto loop;
- 主动调度:
cond_resched()在长时间操作前提供调度机会,避免内核软锁定 - LSM安全检查:
security_inode_follow_link(dentry, nd)调用Linux安全模块检查符号链接跟随权限SELinux、AppArmor等可以在这里实施策略- 如果安全检查失败,跳转到错误处理
第三部分:链接计数管理
current->link_count++;current->total_link_count++;nd->depth++;
- 增加嵌套计数:
current->link_count++跟踪当前调用栈中的链接深度 - 增加总计数:
current->total_link_count++跟踪整个路径解析的链接总数 - 增加深度计数:
nd->depth++在nameidata中记录当前深度
第四部分:链接内容获取准备
touch_atime(nd->mnt, dentry);nd_set_link(nd, NULL);
- 更新访问时间:
touch_atime(nd->mnt, dentry)更新符号链接文件的访问时间 - 清空链接缓存:
nd_set_link(nd, NULL)确保nameidata中的链接内容指针为空
第五部分:文件系统特定链接解析
err = dentry->d_inode->i_op->follow_link(dentry, nd);if (!err) {char *s = nd_get_link(nd);if (s)err = __vfs_follow_link(nd, s);if (dentry->d_inode->i_op->put_link)dentry->d_inode->i_op->put_link(dentry, nd);}
-
调用文件系统方法:
dentry->d_inode->i_op->follow_link(dentry, nd)- 不同的文件系统(ext4,
xfs等)实现自己的符号链接解析 - 通常会将链接目标路径设置到
nd->saved_names[nd->depth]中
- 不同的文件系统(ext4,
-
成功处理:
char *s = nd_get_link(nd)获取解析出的链接目标路径- 如果获取成功,调用
__vfs_follow_link(nd, s)递归解析链接目标 - 调用
put_link方法(如果存在)释放文件系统特定的资源
第六部分:资源清理和返回
current->link_count--;nd->depth--;return err;
loop:path_release(nd);return err;
}
-
减少嵌套计数:
current->link_count--恢复嵌套深度计数 -
减少深度计数:
nd->depth--恢复nameidata深度 -
正常返回:返回错误码或成功(0)
-
错误处理标签:
loop:释放路径资源path_release(nd)- 返回错误码
-ELOOP
关键数据结构说明
计数器作用
current->link_count:防止栈溢出,限制单次调用链中的递归深度current->total_link_count:防止复杂路径导致的性能问题nd->depth:跟踪当前解析的嵌套深度
实际执行场景
场景1:简单符号链接
链接: /bin -> /usr/bin
解析: /bin/ls
流程:
1. 在/bin目录发现符号链接
2. do_follow_link解析链接目标/usr/bin
3. __vfs_follow_link继续解析/usr/bin/ls
场景2:嵌套符号链接
链接: link1 -> link2link2 -> link3 link3 -> /target
解析: link1
流程:
1. 解析link1 → link2 (深度1)
2. 解析link2 → link3 (深度2)
3. 解析link3 → /target (深度3)
4. 解析/target
场景3:符号链接循环
链接: loop1 -> loop2loop2 -> loop1
解析: loop1
流程:
1. 解析loop1 → loop2 (深度1)
2. 解析loop2 → loop1 (深度2)
3. 解析loop1 → 检测到循环,返回-ELOOP
函数功能总结
- 深度限制:防止符号链接无限递归导致栈溢出
- 安全控制:集成LSM安全模块的权限检查
- 资源管理:正确管理链接计数和深度状态
- 文件系统抽象:支持不同文件系统的符号链接实现
- 性能保护:限制总链接数防止性能问题
- 时间更新:维护符号链接文件的访问时间
