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 链表,支持快速遍历目录内容。
dcache 架构:哈希表 + LRU 链表 + 父子关系 dentry_hashtable 桶 0: (parent, hash) → 桶 1: (parent, hash) → 桶 2: (parent, hash) → 桶 3: (parent, hash) → 桶 4: (parent, hash) → ... 更多桶 dentry: "/etc" d_inode=0x..., d_parent=... dentry: "/passwd" d_inode=0x..., d_parent=... dentry: "/secret" d_inode=NULL (负 dentry) LRU 链表(最近最少使用) 最近使用: "/etc/passwd" "/etc/hosts" "/home/user" 最久未用: "/tmp/old"(待回收) d_child/d_subdirs 说明:哈希表提供 O(1) 查找,LRU 链表管理回收顺序,父子链表支持路径遍历。三者协同构成 dcache 核心。

4.3 路径解析流程

路径解析(pathname lookup)是将用户态路径名转换为 dentry 和 inode 的过程。Linux 内核实现了多种查找模式,以适应不同场景:

查找模式 说明 性能特点
RCU-walk 无锁只读查找,依赖 RCU 机制 最快,但遇到挂载点、符号链接等需回退
REF-walk 持有引用计数和锁的常规查找 较慢但安全,处理所有复杂情况
OPEN-walk 为 open 系统调用优化的查找 结合 RCU-walk 和 REF-walk,最后一步特殊处理

路径解析的核心步骤(以绝对路径 /etc/passwd 为例):

  1. 起始点确定:绝对路径从根 dentry(current->fs->root)开始;相对路径从当前工作目录 dentry(current->fs->pwd)开始。
  2. 逐分量查找:对路径中的每个分量(如 etc、passwd),调用 __d_lookup() 在 dcache 哈希表中查找。
  3. dcache 命中:若找到正 dentry,直接返回;若找到负 dentry,返回 -ENOENT。
  4. dcache 未命中:调用底层文件系统的 lookup 方法(如 ext4 的 ext4_lookup),从磁盘读取目录项,创建新的 dentry 并插入 dcache。
  5. 挂载点穿越:若 dentry 是挂载点,则通过 __follow_mount() 切换到被挂载文件系统的根 dentry。
  6. 符号链接跟随:若 dentry 是符号链接,则递归解析目标路径(有限次,默认 40 次)。
路径解析流程:/etc/passwd 步骤 1 从根 dentry 开始 步骤 2 查找 "etc" 分量 步骤 3a dcache 命中? 是 步骤 4a 直接返回 dentry 否 步骤 3b 调用 ext4_lookup 步骤 4b 创建新 dentry 步骤 5 挂载点穿越? 步骤 6 符号链接跟随? 步骤 7 重复直到路径结束 关键优化:RCU-walk 无锁只读查找 遇到挂载点/符号链接 时回退到 REF-walk 减少锁竞争,提升性能

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() 函数阅读源码,跟踪一次完整的路径解析过程。