4. 索引节点对象(inode):结构体定义、inode编号、inode操作表、分配与回收
索引节点(inode)是VFS层最核心的数据结构之一,它抽象了文件系统中所有文件、目录、设备节点等对象的元数据信息。每个文件或目录在VFS中都有一个唯一的inode实例,负责存储文件的权限、大小、时间戳、数据块指针等静态属性,并通过inode_operations操作表提供对文件元数据的操作接口。
核心概念: inode不包含文件名,文件名存储在目录项(dentry)中。一个inode可以被多个dentry引用(硬链接),但每个inode在文件系统内拥有唯一的编号(i_ino)。
4.1 inode结构体定义
Linux内核中,inode结构体定义在include/linux/fs.h中,是VFS层最庞大的数据结构之一。以下列出其核心字段,按功能分组说明:
| 字段 | 类型 | 说明 |
|---|---|---|
i_ino |
unsigned long | inode编号,文件系统内唯一 |
i_count |
atomic_t | 引用计数,表示有多少内核路径引用此inode |
i_nlink |
unsigned int | 硬链接计数,当为0时inode可被回收 |
i_mode |
umode_t | 文件类型与权限位(S_IFMT掩码) |
i_uid / i_gid |
kuid_t / kgid_t | 文件所有者和所属组 |
i_size |
loff_t | 文件大小(字节) |
i_atime / i_mtime / i_ctime |
struct timespec64 | 访问时间、修改时间、状态变更时间 |
i_blocks |
blkcnt_t | 文件占用的磁盘块数(512字节为单位) |
i_op |
const struct inode_operations * | inode操作表指针 |
i_fop |
const struct file_operations * | 文件操作表指针(默认从inode继承) |
i_sb |
struct super_block * | 指向所属超级块 |
i_mapping |
struct address_space * | 页缓存映射,管理文件数据页 |
i_data |
struct address_space | 内嵌的address_space实例 |
i_dentry |
struct list_head | 引用此inode的dentry链表头 |
i_list / i_sb_list |
struct list_head | 全局inode链表和超级块inode链表 |
关键设计: inode结构体通过
i_mode的高4位(S_IFMT)区分文件类型:普通文件(S_IFREG)、目录(S_IFDIR)、字符设备(S_IFCHR)、块设备(S_IFBLK)、FIFO(S_IFIFO)、套接字(S_IFSOCK)和符号链接(S_IFLNK)。VFS层根据此字段决定调用哪些操作函数。
4.2 inode编号(i_ino)
每个inode在所属文件系统中拥有唯一的编号i_ino,该编号由底层文件系统在创建inode时分配。VFS层不保证不同文件系统间的inode编号唯一性,但同一文件系统内必须唯一。
inode编号的分配策略因文件系统而异:
- 基于位图的分配: ext2/ext3/ext4使用块位图和inode位图,从预分配的inode表中分配连续编号。
- 基于哈希的分配: tmpfs使用哈希表管理inode,编号由递增计数器生成。
- 基于对象的分配: btrfs使用B树管理inode,编号由对象ID和偏移量组合。
注意:
i_ino的类型是unsigned long,在32位系统上最大值为2^32-1,在64位系统上为2^64-1。某些文件系统(如FAT)的inode编号由目录项偏移量生成,可能不连续。
4.3 inode操作表(inode_operations)
inode_operations结构体定义了VFS层对inode元数据进行操作的回调函数集合。每个文件系统必须实现这些回调,以提供对文件元数据的访问和修改能力。
struct inode_operations {
// 目录项查找(用于目录inode)
struct dentry * (*lookup) (struct inode *dir, struct dentry *dentry, unsigned int flags);
// 创建新文件
int (*create) (struct inode *dir, struct dentry *dentry, umode_t mode, bool excl);
// 创建硬链接
int (*link) (struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry);
// 删除硬链接
int (*unlink) (struct inode *dir, struct dentry *dentry);
// 创建符号链接
int (*symlink) (struct inode *dir, struct dentry *dentry, const char *symname);
// 创建目录
int (*mkdir) (struct inode *dir, struct dentry *dentry, umode_t mode);
// 删除目录
int (*rmdir) (struct inode *dir, struct dentry *dentry);
// 重命名
int (*rename) (struct inode *old_dir, struct dentry *old_dentry,
struct inode *new_dir, struct dentry *new_dentry, unsigned int flags);
// 获取属性
int (*getattr) (struct user_namespace *mnt_userns, const struct path *path,
struct kstat *stat, u32 request_mask, unsigned int query_flags);
// 设置属性
int (*setattr) (struct user_namespace *mnt_userns, struct dentry *dentry,
struct iattr *attr);
// 获取扩展属性
ssize_t (*getxattr) (struct dentry *dentry, const char *name, void *buffer, size_t size);
// 设置扩展属性
int (*setxattr) (struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name, const void *value, size_t size, int flags);
// 列出扩展属性
ssize_t (*listxattr) (struct dentry *dentry, char *buffer, size_t size);
// 删除扩展属性
int (*removexattr) (struct user_namespace *mnt_userns, struct dentry *dentry,
const char *name);
// 更新inode时间戳
int (*update_time) (struct inode *inode, struct timespec64 *time, int flags);
// 权限检查
int (*permission) (struct user_namespace *mnt_userns, struct inode *inode, int mask);
};
设计要点: inode_operations中的大部分回调函数是可选的。例如,普通文件inode通常只需要实现
getattr、setattr和permission;目录inode则需要实现lookup、create、mkdir、unlink等目录操作。VFS层在调用前会检查函数指针是否为NULL。
下表列出了不同文件类型inode的典型操作表实现:
| 文件类型 | 必需操作 | 可选操作 | 典型实现 |
|---|---|---|---|
| 普通文件 | getattr, setattr, permission | getxattr, setxattr, update_time | ext4_file_inode_operations |
| 目录 | lookup, create, mkdir, unlink, rmdir, rename | symlink, link, getxattr | ext4_dir_inode_operations |
| 符号链接 | get_link (或 readlink) | getattr, setattr | ext4_symlink_inode_operations |
| 特殊文件(设备/FIFO/套接字) | getattr, setattr, permission | 无 | def_blk_aops / def_chr_aops |
4.4 inode的分配与回收
inode的生命周期管理涉及分配、初始化、缓存和回收四个阶段。VFS层提供了统一的分配接口alloc_inode和回收接口destroy_inode,底层文件系统可以通过超级块操作表super_operations中的回调定制这些行为。
4.4.1 inode分配流程
- 调用入口: VFS层通过
new_inode()或new_inode_pseudo()函数请求分配inode。 - 超级块回调: 调用
sb->s_op->alloc_inode(sb),文件系统在此回调中从slab缓存分配inode结构体内存。 - 初始化: VFS层调用
inode_init_always()设置inode的通用字段(i_count=1, i_nlink=1, i_mode=0等)。 - 编号分配: 文件系统在
alloc_inode回调中或之后分配inode编号(i_ino)。 - 加入链表: inode被加入超级块的
s_inodes链表和全局inode_in_use链表。
// VFS层inode分配核心路径(简化)
struct inode *new_inode(struct super_block *sb)
{
struct inode *inode;
// 1. 通过超级块回调分配inode内存
inode = alloc_inode(sb);
if (!inode)
return NULL;
// 2. 初始化通用字段
if (inode_init_always(sb, inode)) {
destroy_inode(inode);
return NULL;
}
// 3. 设置默认操作表
inode->i_op = &empty_inode_operations;
inode->i_fop = &empty_file_operations;
// 4. 加入全局链表
spin_lock(&inode_sb_list_lock);
list_add(&inode->i_sb_list, &sb->s_inodes);
spin_unlock(&inode_sb_list_lock);
return inode;
}
4.4.2 inode回收流程
inode的回收由引用计数i_count和硬链接计数i_nlink共同控制:
- i_count == 0: 表示没有内核路径引用此inode,可以加入空闲链表。
- i_nlink == 0: 表示文件已被删除,inode可以被完全销毁。
回收路径分为两个阶段:
- 清理阶段(evict): 调用
sb->s_op->evict_inode(inode),文件系统在此回调中释放磁盘块、清理页缓存、删除扩展属性等。 - 销毁阶段(destroy): 调用
sb->s_op->destroy_inode(inode),文件系统释放inode结构体内存(通常归还slab缓存)。
关键约束: 在
evict_inode回调中,文件系统必须调用truncate_inode_pages_final()清理所有页缓存,否则可能导致内存泄漏。VFS层在evict_inode返回后会自动从全局链表中移除inode。
4.5 inode缓存与slab分配器
为了提升inode分配和回收的性能,Linux内核使用slab分配器管理inode结构体的内存。每个文件系统在初始化时创建专用的slab缓存:
// ext4文件系统创建inode slab缓存示例
static int __init ext4_init_fs(void)
{
// 创建ext4_inode_info的slab缓存
ext4_inode_cachep = kmem_cache_create("ext4_inode_cache",
sizeof(struct ext4_inode_info),
0, SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD,
NULL);
if (!ext4_inode_cachep)
return -ENOMEM;
return 0;
}
// 文件系统alloc_inode回调使用slab缓存分配
static struct inode *ext4_alloc_inode(struct super_block *sb)
{
struct ext4_inode_info *ei;
ei = kmem_cache_alloc(ext4_inode_cachep, GFP_NOFS);
if (!ei)
return NULL;
// 初始化ext4私有字段
ei->vfs_inode.i_ino = 0;
// ... 其他初始化
return &ei->vfs_inode; // 返回VFS inode指针
}
性能优化: slab缓存通过对象复用减少内存分配开销,同时利用CPU缓存局部性提升访问速度。内核还维护了
inode_unused和inode_in_use两个全局链表,用于快速查找和回收不再使用的inode。
4.6 本章小结
inode对象是VFS层文件元数据管理的核心,其设计体现了以下关键原则:
- 抽象隔离: inode结构体通过
i_op和i_fop操作表将VFS层与具体文件系统实现解耦。 - 引用计数管理:
i_count和i_nlink双计数机制确保inode在文件被打开或硬链接存在时不会被提前回收。 - 生命周期回调: 通过
super_operations中的alloc_inode、evict_inode和destroy_inode回调,允许文件系统定制inode的分配与回收行为。 - 缓存友好: slab分配器和全局链表管理机制保证了inode分配的高效性和可回收性。