4. 目录项对象:struct dentry 结构体详解、目录项缓存与路径解析
目录项对象(dentry)是 VFS 中连接文件名与 inode 的桥梁。它不负责存储文件数据或元数据,而是专门管理路径名与 inode 之间的映射关系,是路径解析和目录项缓存(dcache)的核心数据结构。
面试高频点: dentry 是 VFS 中最“轻量”的对象之一,它只包含文件名、父目录指针、关联 inode 指针以及缓存管理字段。面试官常考察 dentry 与 inode 的生命周期差异、dcache 的 LRU 回收机制,以及路径解析时 dentry 的查找顺序。
4.1 struct dentry 核心字段
dentry 结构体定义在 include/linux/dcache.h 中,其核心字段如下:
| 字段 | 类型 | 说明 |
|---|---|---|
d_name |
struct qstr | 文件名(包含 hash 值,加速比较) |
d_parent |
struct dentry * | 父目录的 dentry 指针 |
d_inode |
struct inode * | 关联的 inode(可为 NULL,表示负 dentry) |
d_sb |
struct super_block * | 所属超级块 |
d_op |
const struct dentry_operations * | dentry 操作函数表 |
d_flags |
unsigned int | 标志位(DCACHE_DISCONNECTED 等) |
d_lru |
struct list_head | LRU 链表节点,用于 dcache 回收 |
d_child |
struct list_head | 父目录的子 dentry 链表节点 |
d_subdirs |
struct list_head | 本目录的子 dentry 链表头 |
d_hash |
struct hlist_node | dcache 哈希表节点 |
// 简化版 struct dentry 定义(仅展示核心字段)
struct dentry {
struct qstr d_name; // 文件名 + hash
struct dentry *d_parent; // 父目录 dentry
struct inode *d_inode; // 关联 inode(NULL 表示负 dentry)
struct super_block *d_sb; // 所属超级块
const struct dentry_operations *d_op; // 操作函数
unsigned int d_flags; // 标志位
struct list_head d_lru; // LRU 链表
struct list_head d_child; // 父目录的子链表
struct list_head d_subdirs; // 本目录的子链表
struct hlist_node d_hash; // 哈希表节点
// ... 其他字段(d_alias、d_fsdata 等)
};
关键概念:正 dentry vs 负 dentry
当
当
当
d_inode 不为 NULL 时,称为正 dentry,表示该文件名存在且已关联到 inode。当
d_inode 为 NULL 时,称为负 dentry,表示该文件名不存在。负 dentry 可以缓存“文件不存在”的结果,避免重复的磁盘查找,提升性能。
4.2 目录项缓存(dcache)架构
dcache 是 dentry 对象的内存缓存,其核心目标是加速路径名到 inode 的转换。dcache 采用哈希表 + LRU 链表的混合结构:
- 哈希表(dentry_hashtable):以 (父 dentry 指针, 文件名 hash) 为键,实现 O(1) 查找。
- LRU 链表:所有 dentry 按最近使用时间排序,当内存压力大时,系统会回收最近最少使用的 dentry。
- 子目录链表:每个 dentry 通过
d_subdirs维护其子 dentry 链表,支持快速遍历目录内容。
4.3 路径解析流程
路径解析(pathname lookup)是将用户态路径名转换为 dentry 和 inode 的过程。Linux 内核实现了多种查找模式,以适应不同场景:
| 查找模式 | 说明 | 性能特点 |
|---|---|---|
| RCU-walk | 无锁只读查找,依赖 RCU 机制 | 最快,但遇到挂载点、符号链接等需回退 |
| REF-walk | 持有引用计数和锁的常规查找 | 较慢但安全,处理所有复杂情况 |
| OPEN-walk | 为 open 系统调用优化的查找 | 结合 RCU-walk 和 REF-walk,最后一步特殊处理 |
路径解析的核心步骤(以绝对路径 /etc/passwd 为例):
- 起始点确定:绝对路径从根 dentry(
current->fs->root)开始;相对路径从当前工作目录 dentry(current->fs->pwd)开始。 - 逐分量查找:对路径中的每个分量(如
etc、passwd),调用__d_lookup()在 dcache 哈希表中查找。 - dcache 命中:若找到正 dentry,直接返回;若找到负 dentry,返回 -ENOENT。
- dcache 未命中:调用底层文件系统的
lookup方法(如 ext4 的ext4_lookup),从磁盘读取目录项,创建新的 dentry 并插入 dcache。 - 挂载点穿越:若 dentry 是挂载点,则通过
__follow_mount()切换到被挂载文件系统的根 dentry。 - 符号链接跟随:若 dentry 是符号链接,则递归解析目标路径(有限次,默认 40 次)。
4.4 dentry 操作函数表
dentry 操作函数表 dentry_operations 允许文件系统自定义 dentry 的行为:
| 函数指针 | 用途 | 典型实现 |
|---|---|---|
d_revalidate |
验证 dentry 是否仍然有效(用于网络文件系统) | NFS 检查服务器端文件是否变更 |
d_hash |
计算 dentry 的 hash 值 | ext4 使用文件名 hash |
d_compare |
比较两个 dentry 的文件名 | case-insensitive 文件系统忽略大小写 |
d_delete |
dentry 被删除时的回调 | 清理文件系统私有数据 |
d_release |
dentry 被释放时的回调 | 释放 d_fsdata 等私有数据 |
d_prune |
dentry 从 dcache 中剪除时的回调 | 清理别名关系 |
面试避坑: dentry 的
d_inode 可能为 NULL(负 dentry),但 d_parent 永远不为 NULL。根 dentry 的 d_parent 指向自身。另外,dentry 的生命周期与 inode 不同:dentry 可以被回收而 inode 仍然存在(只要还有其它 dentry 引用该 inode)。
4.5 dcache 回收机制
当系统内存不足时,内核通过 shrink_dcache_memory() 回收 dentry 对象。回收策略如下:
- LRU 顺序:从 LRU 链表尾部(最久未使用)开始回收。
- 引用计数检查:只有
d_count为 0 的 dentry 才能被回收。 - 负 dentry 优先:负 dentry(
d_inode == NULL)优先被回收,因为它们不关联实际数据。 - 子目录递归:回收目录 dentry 时,会先回收其所有子 dentry。
性能指标: dcache 命中率是衡量文件系统性能的重要指标。在典型服务器负载下,dcache 命中率通常超过 95%。可以通过
/proc/sys/fs/dentry-state 查看当前 dcache 的使用情况。
4.6 面试常见问题
| 问题 | 考察点 | 参考答案要点 |
|---|---|---|
| dentry 和 inode 的生命周期有何不同? | 理解对象关系 | dentry 可被回收而 inode 仍存在;inode 在最后一个 dentry 释放后才可能被回收 |
| 负 dentry 的作用是什么? | dcache 优化机制 | 缓存“文件不存在”的结果,避免重复磁盘查找,提升性能 |
| RCU-walk 和 REF-walk 的区别? | 路径解析优化 | RCU-walk 无锁只读,更快;REF-walk 持有锁,处理复杂情况 |
| 挂载点穿越如何实现? | VFS 挂载机制 | 通过 __follow_mount() 检查 dentry 的挂载标志,切换到被挂载文件系统的根 dentry |
| dcache 哈希表的 key 是什么? | dcache 数据结构 | (父 dentry 指针, 文件名 hash 值) |
学习建议: 理解 dentry 的关键在于把握其“桥梁”角色——它连接了用户可见的路径名和内核内部的 inode。建议结合
fs/namei.c 中的 path_lookupat() 函数阅读源码,跟踪一次完整的路径解析过程。