目录项dentry

在 Linux 中,目录项(通常称为 dentry,是 "directory entry" 的缩写)是一个核心的内核内存数据结构,属于虚拟文件系统层的一部分。

磁盘上的文件系统(ext4, XFS, Btrfs, FAT, NTFS 等)有自己独特的、优化过的格式来存储目录项信息(文件名、对应的 inode 号等)。例如:

  • ext4:使用哈希树 (HTree) 或传统的线性列表+哈希表存储目录项。

  • XFS:使用 B+树存储目录项。

  • FAT:使用文件分配表簇链和目录项表。

当内核需要读取一个目录的内容时:

  • 底层文件系统驱动 (如 ext4.ko, xfs.ko) 负责从磁盘读取原始的目录块/节点。
  • VFS 层根据这些原始数据,在内存中创建对应的 dentry 结构(及其关联的 inode 结构)。
  • 这些 dentry 结构被链接起来(通过 d_parent, d_subdirs, d_children),形成内存中反映目录树结构的缓存。
  • 当创建、删除、重命名文件/目录时,修改首先发生在内存中的 dentry (和 inode) 上,然后由底层文件系统驱动负责将更改同步(写回)到磁盘上的具体数据结构中。

目录项结构(struct dentry)存储的主要信息:

名称 (d_name):

  • 存储该目录项代表的文件或目录的名称(例如,"myfile.txt")。
  • 包含名称本身和其长度(hash 和 len)。

父目录指针 (d_parent):

  • 指向父目录的 dentry 结构。
  • 这是构建完整路径名和进行路径遍历(如 ../)的关键。

子项列表 (d_subdirs) 和 子项哈希表 (d_children):

  • d_subdirs: 一个链表,链接所有直接位于该目录下的子目录项(文件或子目录)。
  • d_children: 更常用的一个哈希表(链表头数组),用于根据子项名称快速查找该目录下的特定子项。内核主要使用这个哈希表进行高效查找。

关联的索引节点 (d_inode):

  • 这是 dentry 最关键的指针!
  • 指向与该目录项关联的索引节点结构(struct inode)。
  • inode 才是真正包含文件元数据(权限、大小、时间戳、数据块位置等)和指向文件实际数据块的结构。dentry 本身不存储文件属性或内容。
  • 一个 inode 可以被多个 dentry 指向(硬链接)。

挂载点信息 (d_mounted):

  • 如果这个 dentry 代表的是一个目录,并且该目录是另一个文件系统的挂载点,这个字段会被设置(非零)。
  • 当遍历路径遇到挂载点时,内核会切换到新挂载的文件系统的根 dentry。

状态标志 (d_flags):

  • 包含一些内部状态位,例如:
    • DCACHE_DISCONNECTED: 表示该 dentry 目前未连接到父 dentry(例如 NFS 场景)。
    • DCACHE_OP_DELETE: 表示该 dentry 正在被删除。
    • DCACHE_MANAGE_TRANSIT: 用于自动挂载点管理。
    • 等等。这些标志主要用于内核内部管理。

引用计数 (d_lockref 或 d_count):

  • 内核使用引用计数来跟踪当前有多少地方正在使用这个 dentry 结构(例如,有进程打开了该文件,或者它正在路径查找中被使用)。
  • 当引用计数降为 0 时,内核不会立即销毁 dentry。它会进入一个“负”状态或放入 LRU 缓存,以便在短时间内如果再次访问同一路径可以快速重建,最终会被内存回收机制移除。

操作函数集指针 (d_op):

  • 指向一个包含该 dentry 特定操作函数的结构(struct dentry_operations)。这些函数由底层的具体文件系统(如 ext4)提供,用于处理该文件系统特有的 dentry 操作,例如:
    • d_revalidate: 检查缓存中的 dentry 是否仍然有效(对网络文件系统如 NFS 尤其重要)。
    • d_weak_revalidate: 弱有效性检查。
    • d_hash: 计算文件名哈希值的特定方法。
    • d_compare: 比较两个文件名的特定方法(例如,是否大小写敏感)。
    • d_delete: 当引用计数变为 0 时调用。
    • d_release: 当 dentry 被真正释放时调用。
    • d_prune: 通知底层文件系统 dentry 即将被释放。
    • d_iput: 当关联的 inode 被释放时调用(如果文件系统需要额外操作)。
    • d_dname: 为动态生成的 dentry 提供调试名称。

超级块指针 (d_sb):

  • 指向该 dentry 所属文件系统的超级块结构(struct super_block)。超级块代表一个已挂载的文件系统实例。

挂载点信息 (d_mounted):这个字段存在的必要性?

  1. 挂载过程 (mount 命令):
  • 当你执行 mount /dev/sdb1 /mnt/data 时:
    • /dev/sdb1 是包含文件系统(比如 ext4)的块设备。
    • /mnt/data 是宿主文件系统(比如根文件系统 ext4)中的一个空目录。
  • 内核动作:
    • 读取 /dev/sdb1 的超级块,在内存中创建一个新的 super_block 结构 (s_sdb1) 代表这个新的文件系统实例。
    • 为这个新文件系统的根目录 (/) 创建内存中的 inode 和 dentry 结构。这个 dentry 就是新文件系统的 "根 dentry" (mount->mnt_root)。
    • 找到宿主文件系统中 /mnt/data 目录对应的 dentry (dentry_mnt_data)。
    • 在 dentry_mnt_data 上设置 d_mounted = 1,标记这是一个挂载点。
    • 创建一个 struct mount 结构,它是挂载点的核心管理结构。这个结构的关键字段包括:
      • mnt_mountpoint:指向宿主文件系统中的挂载点 dentry (dentry_mnt_data)。
      • mnt_root:指向新挂载文件系统的根 dentry。
      • mnt_sb:指向新文件系统的超级块 (s_sdb1)。
    • 将这个 mount 结构加入到全局挂载树中。
  1. 路径遍历遇到挂载点 (例如访问 /mnt/data/file.txt):
  • 内核从进程的当前目录(或根目录 /)的 dentry 开始解析路径 /mnt/data/file.txt。
  • 它先找到 / 的 dentry (dentry_root)。
  • 在 / 的子项中查找 mnt,找到其 dentry (dentry_mnt)。
  • 在 dentry_mnt 的子项中查找 data,找到其 dentry (dentry_mnt_data)。
  • 关键步骤: 内核检查 dentry_mnt_data 的 d_mounted 标志。发现它被设置了 (d_mounted == 1)。
  • 内核知道 dentry_mnt_data 是一个挂载点。于是:
    • 它查找与 dentry_mnt_data 关联的 mount 结构(通过挂载树)。
    • 从这个 mount 结构中取出 mnt_root —— 这就是新挂载文件系统 (/dev/sdb1) 的根目录 (/) 的 dentry。
    • 内核进行“跳转”:接下来的路径解析将从这个新的根 dentry (mnt_root) 开始,而不是继续在宿主文件系统的 dentry_mnt_data 下查找。
  • 接下来的路径分量解析将在 dentry_newfs_root 及其子项中进行,完全处于新文件系统的上下文中。现在内核在新的根 dentry (mnt_root) 下查找 file.txt 的 dentry。
  • 找到 file.txt 的 dentry 后,最终找到其对应的 inode。

存在的必要性:

  1. 保持宿主结构不变:
  • 挂载后,宿主文件系统中 /mnt/data 目录对应的原始 dentry (dentry_host_data) 和 inode 保持不变。
  • dentry_host_data->d_inode 仍然指向宿主文件系统上代表空目录 /mnt/data 的那个 inode。
  • dentry_host_data->d_subdirs 理论上还是空的(宿主目录本身是空的)。
  • 关键一步: 内核在 dentry_host_data 上设置 d_mounted = 1。这只是一个标记,表示“此目录是通往另一个世界的门户”,并不改变其本身代表的内容。
  1. 创建挂载结构 (struct mount):
  • 这个 struct mount 对象是连接两个独立文件系统世界的“桥梁”。
  1. . 和 .. 的正确处理:
  • 在新文件系统中 (/mnt/data/ 下):
    • .:解析到 dentry_newfs_root (新文件系统的根)。
    • ..:因为新文件系统的根是顶级目录,dentry_newfs_root->d_parent 通常指向自己。但在挂载点下,VFS 有特殊处理:
      • 当在新文件系统内部解析 .. 时,如果当前目录就是该文件系统的根 (dentry == mnt_root),那么 .. 应该返回到宿主文件系统的挂载点目录 (dentry_host_data) 的父目录(即 /mnt)。这个逻辑由 follow_dotdot() 函数实现,它会检查是否在挂载点边界,并进行相应的反向“跳转”(回到宿主文件系统上下文)。
  • 在宿主文件系统中:
    • /mnt/data/. 解析到 dentry_host_data (宿主目录)。
    • /mnt/data/.. 解析到 dentry_host_data->d_parent (宿主目录 /mnt)。
posted @ 2025-08-26 09:43  pointarray  阅读(19)  评论(0)    收藏  举报