用户传一个字符串
/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
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】内存回收
Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。
【操作系统百科】交换
swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】用户态分配器
glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。