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等于
2. 否则,若进程的GID或辅助GID匹配
3. 否则,使用其他用户权限位。
4. 超级用户(capable(CAP_DAC_OVERRIDE))绕过所有权限检查。
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操作表调用的完整流程:
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缓存用于加速。