3. VFS核心数据结构(中):索引节点对象(inode)的详细结构、权限与时间戳管理,以及inode操作表(inode_operations)

索引节点(inode)是VFS中最核心的数据结构之一,它承载了文件系统中每一个文件或目录的元数据信息。与目录项(dentry)不同,inode是持久化的——它直接对应磁盘上的物理索引节点。理解inode的结构、权限模型、时间戳语义以及操作表,是深入文件系统开发的基础。

3.1 inode结构总览

在Linux内核源码 include/linux/fs.h 中,struct inode 是一个庞大的结构体。下面列出其核心字段,按功能分组说明:

分类 字段名 说明
标识 i_ino inode编号,文件系统内唯一
i_generation inode世代号,用于NFS等检测重用
类型与权限 i_mode 文件类型 + 权限位(umode_t)
i_uid / i_gid 属主UID和属组GID
时间戳 i_atime / i_mtime / i_ctime 访问、修改、状态变更时间
大小与块 i_size 文件大小(字节)
i_blocks 已分配的磁盘块数(512字节为单位)
i_blkbits 块大小的移位位数
链接计数 i_nlink 硬链接数,为0时可回收
文件系统关联 i_sb 指向所属超级块(super_block)
i_mapping 地址空间映射,管理页缓存
操作表 i_op inode操作表(inode_operations *)
i_fop 文件操作表(file_operations *)
私有数据 i_private 文件系统私有指针,用于扩展
关键理解: inode不包含文件名。文件名存储在目录项(dentry)中,一个inode可以通过多个dentry被多个硬链接指向。当 i_nlink 降为0且没有进程打开该文件时,inode才会被真正释放。

3.2 权限管理:i_mode 与 POSIX ACL

i_mode 是一个16位的无符号整数,同时编码了文件类型和访问权限。其位布局如下:

位范围      含义
15-12       文件类型(S_IFMT 掩码 0xF000)
11-9        setuid / setgid / sticky 特殊位
8-6         属主权限(rwx)
5-3         属组权限(rwx)
2-0         其他用户权限(rwx)

常见的文件类型宏定义:

  • S_IFREG (0x8000) — 普通文件
  • S_IFDIR (0x4000) — 目录
  • S_IFLNK (0xA000) — 符号链接
  • S_IFBLK (0x6000) — 块设备
  • S_IFCHR (0x2000) — 字符设备
  • S_IFIFO (0x1000) — 命名管道
  • S_IFSOCK (0xC000) — 套接字

判断文件类型的标准做法是使用宏,例如 S_ISREG(mode) 检查是否为普通文件。

权限检查流程(简化):
1. 若进程的UID等于 i_uid,使用属主权限位。
2. 否则,若进程的GID或辅助GID匹配 i_gid,使用属组权限位。
3. 否则,使用其他用户权限位。
4. 超级用户(capable(CAP_DAC_OVERRIDE))绕过所有权限检查。

3.3 时间戳管理:atime / mtime / ctime

每个inode维护三个时间戳,它们各自有不同的语义和更新时机:

时间戳 全称 更新时机 示例场景
i_atime 访问时间 读取文件内容时(read / exec) cat file 会更新atime
i_mtime 修改时间 文件内容被修改时(write) echo "x" >> file 更新mtime
i_ctime 状态变更时间 inode元数据变更时(chmod / chown / link) chmod 755 file 更新ctime
注意: ctime不是创建时间(creation time)。Linux原生VFS不记录文件创建时间(btime),但ext4在扩展属性中支持了 crtime 字段。ctime的更新意味着inode的某些元数据(权限、链接数、uid等)发生了变化。

时间戳在内核中存储为 struct timespec64,包含秒(tv_sec)和纳秒(tv_nsec)两部分。纳秒精度依赖于底层文件系统的支持。

3.4 inode操作表(inode_operations)

struct inode_operations 是一个函数指针集合,定义了在inode上可以执行的操作。这些操作主要涉及目录和符号链接的元数据管理,而不是文件内容的读写(那属于 file_operations)。

核心回调函数如下:

回调函数 用途 典型实现
lookup 在目录中查找子项,返回dentry 遍历目录条目,填充inode
create 在目录中创建新文件 分配inode,添加目录项
mkdir 创建子目录 分配inode,初始化目录结构
unlink 删除目录项(硬链接) 减少i_nlink,必要时释放inode
rmdir 删除空目录 检查目录是否为空,释放inode
rename 重命名文件或目录 更新目录项,处理跨目录移动
symlink 创建符号链接 分配inode,存储目标路径
setattr 设置inode属性(chmod / chown / truncate) 更新i_mode / i_uid / i_size
getattr 获取inode属性(stat) 填充struct kstat
permission 自定义权限检查 扩展ACL或安全模块钩子
设计模式: 每个文件系统(ext4、btrfs、xfs)都会定义自己的 inode_operations 实例。例如ext4的目录inode使用 ext4_dir_inode_operations,而普通文件inode使用 ext4_file_inode_operations。VFS通过 i_op 指针调用具体实现,实现多态。

3.5 inode生命周期与操作流程

下面的SVG图展示了从VFS路径查找开始,到inode操作表调用的完整流程:

inode操作表调用流程 VFS路径查找 → inode操作表调用流程 用户态系统调用(open / stat / mkdir) VFS路径查找(path_walk / lookup) dentry缓存命中? 未命中 调用父目录inode->i_op->lookup() 命中 获取目标inode(dentry->d_inode) 执行具体操作(create / mkdir / unlink) 返回用户态(fd / 错误码) 关键点 • lookup 由父目录inode的i_op提供 • 每个文件系统实现自己的i_op表 • dentry缓存可避免重复lookup

3.6 实战:ext4中的inode操作表注册

以ext4文件系统为例,在 fs/ext4/super.c 中,不同文件类型会绑定不同的操作表:

// ext4目录inode操作表
static const struct inode_operations ext4_dir_inode_operations = {
    .create     = ext4_create,
    .lookup     = ext4_lookup,
    .link       = ext4_link,
    .unlink     = ext4_unlink,
    .symlink    = ext4_symlink,
    .mkdir      = ext4_mkdir,
    .rmdir      = ext4_rmdir,
    .mknod      = ext4_mknod,
    .rename     = ext4_rename,
    .setattr    = ext4_setattr,
    .getattr    = ext4_getattr,
    .listxattr  = ext4_listxattr,
};

// ext4普通文件inode操作表
static const struct inode_operations ext4_file_inode_operations = {
    .setattr    = ext4_setattr,
    .getattr    = ext4_getattr,
    .listxattr  = ext4_listxattr,
    .permission = ext4_permission,
    .fiemap     = ext4_fiemap,
};
对比分析: 目录inode需要 lookup、create、mkdir 等操作,而普通文件inode不需要。符号链接inode则使用 ext4_symlink_inode_operations,其中包含 get_link 回调用于读取链接目标。这种设计使得VFS层可以统一处理所有文件类型,而具体行为由底层文件系统通过操作表定制。

3.7 本章小结

  • inode 是VFS中持久化元数据的核心,包含文件类型、权限、时间戳、链接计数等关键信息。
  • i_mode 同时编码文件类型和权限位,权限检查遵循属主→属组→其他的顺序。
  • 三个时间戳(atime/mtime/ctime)各有明确的更新语义,ctime不等于创建时间。
  • inode_operations 是VFS多态的关键接口,每个文件系统为不同文件类型注册不同的操作表。
  • 路径查找过程中,VFS通过父目录inode的 lookup 回调获取子项inode,dentry缓存用于加速。