3. 索引节点对象:struct inode 结构体详解、inode 唯一标识与状态

索引节点(inode)是 VFS 中用于描述文件或目录元数据的核心对象。每个文件、目录、符号链接等在 VFS 层都对应一个唯一的 struct inode 实例。它不包含文件名,也不包含文件数据内容,而是存储权限、大小、时间戳、数据块指针等元信息。

3.1 struct inode 核心字段解析

VFS 的 struct inode 定义在 include/linux/fs.h 中,字段数量众多,面试中常考察以下几类关键字段:

分类 字段名 说明
唯一标识 i_ino inode 号,在同一个文件系统内唯一
唯一标识 i_generation inode 世代号,用于 NFS 等场景避免重用冲突
文件类型与权限 i_mode 文件类型(普通/目录/链接等)及 rwx 权限位
所有者 i_uid / i_gid 文件所属用户 ID 和组 ID
时间戳 i_atime / i_mtime / i_ctime 访问时间、修改时间、状态变更时间
大小与块 i_size / i_blocks 文件字节大小和占用的磁盘块数
引用计数 i_count 内核中对该 inode 的引用计数
硬链接数 i_nlink 指向该 inode 的目录项数量
操作函数表 i_op / i_fop inode 操作集和文件操作集指针
文件系统关联 i_sb 指向所属超级块对象的指针
地址空间 i_mapping 页缓存映射,用于文件数据读写
面试高频点: i_count 与 i_nlink 的区别。前者是内核内存中的活跃引用数,后者是磁盘上的硬链接计数。当 i_nlink == 0 且 i_count == 0 时,inode 才会被真正释放。

3.2 inode 唯一标识:i_ino 与 i_generation

在 VFS 层面,inode 的全局唯一性由 (i_sb, i_ino) 二元组保证。同一个超级块(即同一个挂载的文件系统实例)内,i_ino 必须唯一。不同文件系统之间,i_ino 可能重复,因此比较 inode 是否相同时必须同时比较超级块指针。

i_generation 字段用于解决 inode 号重用带来的安全问题。当文件被删除后,其 inode 号可能被新文件复用。NFS 客户端如果缓存了旧的文件句柄,通过 i_generation 可以检测到 inode 已被重新分配,从而避免访问到错误的数据。

面试题: 如何判断两个文件描述符指向同一个文件?答案:比较 struct file 中的 f_inode 指针是否相同,或者比较 f_inode->i_sb 和 f_inode->i_ino 是否相等。

3.3 inode 状态机:从创建到释放

inode 在内核中存在多种状态,理解状态转换对分析文件系统行为至关重要。主要状态包括:

  • NEW:刚分配内存,尚未与磁盘同步,i_state 包含 I_NEW 标志。
  • DIRTY:inode 元数据已被修改,但尚未写回磁盘,i_state 包含 I_DIRTY 标志。
  • CLEAN:inode 与磁盘内容一致,无待写回数据。
  • FREEING:正在被释放,i_nlink == 0 且引用计数降为 0。
  • LOCKED:正在被 I/O 操作使用,防止并发访问。
注意: I_DIRTY 又细分为 I_DIRTY_SYNC(时间戳变更)、I_DIRTY_DATASYNC(数据相关元数据变更)和 I_DIRTY_PAGES(页缓存脏页),写回策略不同。
inode 状态转换图 NEW (I_NEW) CLEAN DIRTY FREEING 初始化完成 元数据修改 写回完成 i_nlink=0 & i_count=0 说明:DIRTY 状态包含 I_DIRTY_SYNC / I_DIRTY_DATASYNC / I_DIRTY_PAGES 子标志位

3.4 inode 操作集:i_op 与 i_fop

每个 inode 都关联两个操作函数表,这是 VFS 实现多态的关键:

  • i_op(struct inode_operations *):包含 create、lookup、link、unlink、mkdir、rmdir、rename、setattr、getattr 等操作。这些操作主要针对目录项和 inode 元数据本身。
  • i_fop(struct file_operations *):包含 read、write、open、release、llseek、mmap、fsync 等操作。这些操作针对文件内容的数据读写。
关键区别: 目录 inode 的 i_fop 通常指向 dir_file_operations,其 read 操作返回目录项列表;而普通文件 inode 的 i_fop 指向 def_blk_fops 或具体文件系统的文件操作集。

3.5 面试常见问题与避坑

问题 核心要点
inode 与 dentry 的关系 dentry 是目录项缓存,指向 inode;多个 dentry 可指向同一个 inode(硬链接)
i_count 何时增加 打开文件、获取 dentry、page cache 引用等场景会增加
i_nlink 何时变为 0 最后一个硬链接被删除时,文件并未立即释放,直到所有打开的文件描述符关闭
如何遍历 inode 的脏页 通过 i_mapping->nrpages 和 find_get_pages 遍历页缓存
inode 号在什么情况下会重复 不同文件系统之间可以重复;同一文件系统内不会重复
inode 与周边对象关系图 dentry struct inode super_block struct file address_space d_inode i_sb f_inode i_mapping

3.6 代码示例:获取 inode 信息

以下代码展示了在 VFS 层面如何通过文件描述符获取 inode 的核心元数据:

#include <linux/fs.h>
#include <linux/dcache.h>

static void show_inode_info(struct inode *inode)
{
    if (!inode)
        return;

    pr_info("inode: ino=%lu, mode=%o, nlink=%u, size=%lld\n",
            inode->i_ino,
            inode->i_mode,
            inode->i_nlink,
            inode->i_size);

    pr_info("  uid=%u, gid=%u, blocks=%lu\n",
            i_uid_read(inode),
            i_gid_read(inode),
            inode->i_blocks);

    pr_info("  atime=%ld, mtime=%ld, ctime=%ld\n",
            inode->i_atime.tv_sec,
            inode->i_mtime.tv_sec,
            inode->i_ctime.tv_sec);

    pr_info("  state=0x%lx, count=%d\n",
            inode->i_state,
            atomic_read(&inode->i_count));
}

/* 在 open 系统调用钩子中调用 */
static int my_open(struct inode *inode, struct file *file)
{
    show_inode_info(inode);
    return 0;
}
面试提示: 注意 i_uid_read 和 i_gid_read 是内核封装函数,用于处理不同架构下 uid/gid 的读写一致性。直接访问 inode->i_uid 在某些内核版本中已被废弃。