当前位置: 首页 > news >正文

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:当前目录的inode
    • lookup_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处理符号链接解析
    • 释放临时dentry dput
    • 检查新的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
    • 返回错误码

函数功能总结

  1. 路径解析:逐组件解析文件路径
  2. 权限检查:对每个目录组件执行执行权限检查
  3. 特殊目录处理:正确处理".“和”…"
  4. 哈希计算:为目录项名称计算哈希值
  5. 目录查找:在文件系统中查找目录项
  6. 挂载点处理:正确处理文件系统挂载点
  7. 符号链接解析:处理和跟随符号链接
  8. 引用计数管理:正确管理dentryvfsmount的引用计数
  9. 错误处理:全面的错误检测和资源清理
  10. 重新验证支持:支持文件系统特定的重新验证

处理"…"目录follow_dotdot

static inline void follow_dotdot(struct vfsmount **mnt, struct dentry **dentry)
{while(1) {struct vfsmount *parent;struct dentry *old = *dentry;read_lock(&current->fs->lock);if (*dentry == current->fs->root &&*mnt == current->fs->rootmnt) {read_unlock(&current->fs->lock);break;}read_unlock(&current->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(&current->fs->lock);if (*dentry == current->fs->root &&*mnt == current->fs->rootmnt) {read_unlock(&current->fs->lock);break;}read_unlock(&current->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的是挂载的文件系统目录,所以挂载点将会在挂载的文件系统上

函数功能总结

  1. "…"语义实现:正确处理转到父目录的请求
  2. 根目录边界:防止超出进程根目录(包括chroot)
  3. 挂载点跨越:正确处理文件系统边界
  4. 嵌套挂载支持:支持多层挂载点结构
  5. 挂载点覆盖:自动进入挂载的文件系统

目录项查找函数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,跳转到实际查找
  • 需要重新验证:如果dentryd_revalidate操作,需要验证有效性

第二部分:do_lookup - 成功处理

done:path->mnt = mnt;path->dentry = dentry;return 0;
  • 设置结果:将找到的dentrymnt设置到输出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
  • 重新查找:释放旧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保护的链表
  • 获取dentryhlist_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安全模块检查符号链接跟随权限
    • SELinuxAppArmor等可以在这里实施策略
    • 如果安全检查失败,跳转到错误处理

第三部分:链接计数管理

	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]
  • 成功处理

    • 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

函数功能总结

  1. 深度限制:防止符号链接无限递归导致栈溢出
  2. 安全控制:集成LSM安全模块的权限检查
  3. 资源管理:正确管理链接计数和深度状态
  4. 文件系统抽象:支持不同文件系统的符号链接实现
  5. 性能保护:限制总链接数防止性能问题
  6. 时间更新:维护符号链接文件的访问时间
http://www.dtcms.com/a/530061.html

相关文章:

  • 群辉wordpress东莞市网络优化推广服务机构
  • 网站建设的安全威胁wordpress customize.php
  • 学做快餐的视频网站传奇网页版游戏开服表
  • 镇江网站搜索排名云主机网站如何备份
  • 学校网站设计及代码国内最好的摄影网站
  • 公文写作网站公司宣传册设计模板
  • 民权做网站哪家好wordpress ip_hash失效
  • 计算机网络自顶向下方法2——网络、ISP连接结构介绍
  • 速卖通网站怎样做店面的二维码百度网站广告怎么做
  • 【笔记】修复 ComfyUI 启动报错 KeyError: ‘luts‘ 和 KeyError: ‘tensorrt‘
  • 建设网站应注意什么网站制作公司美股上市
  • 网站源码怎么下载网站建设课程设计报告php
  • 为什么电脑打开那个做网站都是那一个信息系统管理工程师
  • 【右值引用完美转发】右值引用与完美转发的“天罡北斗阵”
  • 链表-循环双向链表【node4】
  • 襄阳网站建设找下拉哥科技sem seo是什么意思呢
  • 移动端网站建设的软件有哪些建设银行投诉网站首页
  • 乌鲁木做兼职的网站三合一网站建设方案
  • 西安装修公司网站制作做一个网站赚钱吗
  • 公司网站建设技术企业品牌推广策划
  • 汽车电子运用目的,如何学习simulink?
  • Vue3 组件挂载流程(源码解读)
  • 老榕树建站软件wordpress 站点网络
  • 岗巴网站建设优秀企业网站模板下载
  • 自己动手建设网站国家企业信用网官网
  • 呼市网站制作大连网站关键词
  • wordpress搜索页分类怎样建设的网站好优化好排名
  • Java的注解
  • 专业公司网站 南通免费素材库大全
  • 哪家网站设计公司好出货入货库存的软件