用户传一个字符串
/usr/bin/ls,内核要一级一级查 dcache /
磁盘,穿越 mount 点、symlink,最终拿到目标 inode。这个过程叫
namei(name-to-inode),是 VFS
最频繁的操作。
一、先看图
flowchart LR
PATH["/usr/bin/ls"] --> SLASH[/ → root dentry]
SLASH --> USR[lookup "usr"]
USR --> BIN[lookup "bin"]
BIN -->|mount 穿越| BINMNT[/usr/bin 挂载点]
BINMNT --> LS[lookup "ls"]
LS --> INODE[inode 返回]
classDef step fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef mount fill:#a371f722,stroke:#a371f7,color:#adbac7;
class SLASH,USR,BIN,LS,INODE step
class BINMNT mount
二、三阶段查找
2.1 LOOKUP_RCU(快速路径)
不加任何锁,RCU 保护:
- 从根 dentry 开始
- 每个组件用
d_hash查 dcache - 验证
d_seq(dentry 序列号)保证数据一致 - mount 穿越用
mnt_seq验证
成功率 >99%(热路径命中 dcache)。
2.2 LOOKUP_LOCK(降级)
RCU 验证失败 → 降级拿 inode->i_rwsem +
dentry 引用。
2.3 LOOKUP_SLOW(磁盘)
dcache miss → 调用
inode->i_op->lookup() → 读磁盘目录。
三、mount 穿越
路径走到 mount 点时:
/ → root sb
/usr → root sb
/usr/bin → 如果 /usr 是独立挂载,穿越到新 sb 的 root dentry
follow_managed() 处理 automount + mount
穿越。
四、symlink 处理
遇到 symlink → 递归解析目标。
保护:
- 最大嵌套 40
层(
MAXSYMLINKS = 40) - 最大总长度 4096 字节
- 绝对 symlink 从根重新开始
五、openat2 与 RESOLVE_*
openat2(2) 引入安全控制:
struct open_how how = {
.flags = O_RDONLY,
.resolve = RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH,
};
int fd = openat2(dirfd, "path", &how, sizeof(how));- RESOLVE_NO_SYMLINKS:不跟 symlink
- RESOLVE_BENEATH:不能越出 dirfd(防路径穿越)
- RESOLVE_IN_ROOT:把 dirfd 当 / 看(chroot 语义)
- RESOLVE_NO_XDEV:不穿越 mount 点
- RESOLVE_NO_MAGICLINKS:不跟 /proc/self/fd/N 这类魔法链接
容器安全的基石。
六、AT_EMPTY_PATH 与 fstat 优化
openat(dirfd, "", AT_EMPTY_PATH)
// 等价于对 dirfd 本身操作配合 /proc/self/fd/N →
可以用路径操作已打开的 fd。
七、case-insensitive
ext4 5.2+ 支持 casefold 特性(UTF-8 case
folding):
mkfs.ext4 -O casefold /dev/sda1
chattr +F /mnt/mydir # 该目录及子目录大小写不敏感内核在 lookup 时用 UTF-8 NFC 正规化 + case fold 比较。
限制:不同 locale 的 case mapping 不同;不支持 Turkic “i” 问题。
八、RENAME_EXCHANGE
renameat2(olddirfd, "a", newdirfd, "b", RENAME_EXCHANGE);
// 原子交换 a 和 b比 rename+rename 安全——不会出现中间状态。
RENAME_NOREPLACE:不覆盖目标(原子
create-or-fail)。
九、dcache 调优
# 当前 dcache 状态
cat /proc/sys/fs/dentry-state
# negative dentry 限制(6.1+)
sysctl fs.negative-dentry-limit=0 # 不限(默认)大量 negative dentry 场景:编译器频繁探测不存在的头文件 → dcache 膨胀。
十、路径查找性能
# 衡量 lookup 速度
perf stat -e 'syscalls:sys_enter_openat' -- find /usr -name '*.so' > /dev/null
# dcache 命中率
perf stat -e dentry-cache-misses,dentry-cache-accesses 2>/dev/null
# 或看 /proc/slabinfo dentry 变化十一、小结
- namei 走三阶段:RCU → 加锁 → 磁盘
- mount 穿越和 symlink 是两大复杂点
- openat2 RESOLVE_* 是容器安全的路径查找利器
- case-insensitive 在 ext4 上可选启用
参考文献
Documentation/filesystems/path-lookup.rst- Neil Brown, “Pathname lookup in Linux.” LWN.net (6 篇系列)
fs/namei.cinclude/uapi/linux/openat2.hDocumentation/filesystems/ext4/casefold.rst
工具
strace -e openatperf trace/proc/sys/fs/dentry-state
上一篇:VFS 四层抽象 下一篇:fd 表与 struct file
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
EEVDF 调度器:Linux 6.6 为什么换掉了 CFS
Linux 6.6 用 EEVDF 取代了 CFS 的 SCHED_NORMAL 选取逻辑。从 1995 年原始论文的 lag、eligibility、virtual deadline,到 commit 147f3ef 只重写 placement/pick/preempt,再到本机内核 6.6 上读 sched/debug 把每个任务的 vruntime、eligible 标志、deadline 一一对上 vd=ve+r/w,外加 nice 带宽与 base_slice 抢占两组实测,讲清换的是哪一块、延迟敏感任务凭什么先跑。
【操作系统百科】Windows 内核与 Linux 的关键差异
NT Executive/Kernel/HAL 分层、Object Manager 与 handle、IRP 驱动栈、IOCP 完成端口 vs epoll/io_uring、EPROCESS/KTHREAD vs task_struct、Registry vs 文件配置,以及 WSL2 为何选择 Hyper-V 轻量 VM。
【操作系统百科】实时 OS 巡礼
硬实时与软实时的可调度性边界、VxWorks/QNX/Zephyr 机制对照、Linux PREEMPT_RT 与主线合入、DO-178C/ISO 26262 认证语境,以及选型决策树。
【操作系统百科】Unikernel
Unikernel 在云上为什么没成主流?MirageOS、IncludeOS、Unikraft、OSv 的设计取舍——库操作系统、启动时间、工具链、调试困难、POSIX 兼容。