本文基于6.17 Linux 内核源码,深入剖析文件系统核心数据结构
struct inode的每个字段,揭示其设计理念和实现细节。
引言
在 Linux 内核中,inode(索引节点)是文件系统的核心抽象。inode 对象保存了内核操作文件或目录所需的全部信息。无论底层文件系统如何实现(ext4、XFS、Btrfs),VFS(虚拟文件系统)都通过统一的 struct inode 来表示文件。
inode 这个术语来源于"index node"(索引节点),最早由 Unix 先驱 Dennis Ritchie 和 Ken Thompson 在 1978 年的论文中提出。每个 inode 通过一个整数编号(i-number)唯一标识,内核通过这个编号在系统表(i-list)中查找文件的元数据。
结构体布局设计哲学
struct inode 的定义位于 include/linux/fs.h,其设计遵循重要的性能优化原则:
/*
* Keep mostly read-only and often accessed (especially for
* the RCU path lookup and 'stat' data) fields at the beginning
* of the 'struct inode'
*/
struct inode {
// ... 字段定义
} __randomize_layout;
设计原则
缓存行优化:将高频访问的只读字段放在结构体开头,最大化 CPU 缓存命中率
内存对齐:合理排列字段以减少内存浪费
安全性增强:使用
__randomize_layout属性随机化结构布局,防止内存布局攻击
字段详细解析
一、高频访问字段区(Hot Fields)
这些字段位于结构体开头,是 RCU 路径查找和 stat 系统调用的核心数据。
1.1 文件类型和权限
umode_t i_mode;
作用:文件类型和权限位的组合,决定文件是目录、普通文件还是特殊文件,以及访问权限
解析:
i_mode是一个 16 位值,高 4 位表示文件类型,低 12 位表示权限文件类型包括:
S_IFREG(普通文件)、S_IFDIR(目录)、S_IFLNK(符号链接)、S_IFCHR(字符设备)、S_IFBLK(块设备)等
// 文件类型位掩码,用于从 st_mode 中提取文件类型
// 使用 S_IFMT 提取文件类型代码
// mode_t file_type = sb.st_mode & S_IFMT;
// 现在进行判断:
// if (file_type == S_IFREG) { // S_IFREG 才代表普通文件
// printf("这是普通文件\n");
// } else if (file_type == S_IFDIR) { // S_IFDIR 代表目录
// printf("这是目录\n");
// } else if (file_type == S_IFLNK) { // S_IFLNK 代表符号链接
// printf("这是符号链接\n");
// }
#define S_IFMT 00170000
// Socket(套接字)文件
#define S_IFSOCK 0140000
// 符号链接(Symbolic link)
#define S_IFLNK 0120000
// 普通文件
#define S_IFREG 0100000
// 块设备
#define S_IFBLK 0060000
// 目录文件夹
#define S_IFDIR 0040000
// 字符设备
#define S_IFCHR 0020000
// FIFO管道(Pipe),一个在保存数据内核内存的的管道,符合fifo原则
#define S_IFIFO 0010000
// Set-UID位,使可执行文件以文件所有者的权限运行
#define S_ISUID 0004000
// Set-GID位,使可执行文件以文件所属组的权限运行
#define S_ISGID 0002000
// Sticky位,在目录上设置时,只允许文件所有者或root删除目录中的文件
#define S_ISVTX 0001000权限位遵循传统 Unix 权限模型:
rwxrwxrwx(所有者、组、其他用户)
示例:
// 判断是否为目录
if (S_ISDIR(inode->i_mode))
// 这是一个目录
// 检查所有者是否有写权限
if (inode->i_mode & S_IWUSR)
// 所有者可写
// 对应的宏如下
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)1.2 操作标志
unsigned short i_opflags;作用:inode 操作标志,包含 IOP_XATTR 等标志位
解析:
// 用于权限检查的快速路径缓存标志。当 inode 没有特殊的权限检查函数时,内核会设置这个标志,表示可以使用快速的 generic_permission() 函数而不需要调用自定义的 permission 方法。这是一个性能优化,避免每次权限检查时都要检查是否存在自定义 permission 函数。
// https://elixir.bootlin.com/linux/v6.17.8/source/fs/namei.c#L533
#define IOP_FASTPERM 0x0001
// 表示 inode 具有 lookup 操作,主要用于目录 inode。这个标志指示该 inode 支持查找子目录项的操作。
#define IOP_LOOKUP 0x0002
// 表示在路径解析时不应该跟随符号链接。这个标志用于控制符号链接的跟随行为,类似于 O_NOFOLLOW 打开标志的效果。
#define IOP_NOFOLLOW 0x0004
// 表示 inode 支持扩展属性(extended attributes)。这个标志在 inode 初始化时根据超级块是否定义了 s_xattr 自动设置。当 inode 不支持扩展属性时,xattr 相关操作会返回 -EOPNOTSUPP 错误
// https://patchwork.kernel.org/project/linux-security-module/patch/[email protected]/
#define IOP_XATTR 0x0008
// 表示 inode 使用默认的 readlink 实现。这个标志用于符号链接,指示可以使用通用的 readlink 处理函数而不需要文件系统提供自定义实现。
#define IOP_DEFAULT_READLINK 0x0010
// 表示文件系统使用多粒度时间戳(multigrain timestamps)管理。这是较新的内核特性,用于解决时间戳更新的精度问题。当文件的时间戳最近被查询过时,系统会使用细粒度时间戳;否则使用粗粒度时间戳以提高性能。这个标志在 inode 初始化时根据文件系统类型的 FS_MGTIME 标志自动设置。
#define IOP_MGTIME 0x0020
// 表示符号链接的长度信息已被缓存在 inode 中。这是 Linux 6.14 引入的性能优化,通过缓存符号链接长度可以避免在 vfs_readlink() 中调用 strlen(),在 ext4 文件系统上对 /initrd.img 执行 readlink 可以获得约 1.5% 的性能提升
#define IOP_CACHED_LINK 0x00401.3 所有权信息
kuid_t i_uid;
kgid_t i_gid;作用:文件所有者的用户 ID 和组 ID
解析:
支持用户命名空间(user namespace),允许容器内的 UID/GID 映射
新建文件时,UID 继承创建进程的有效 UID,GID 可能继承父目录或创建进程的有效 GID
1.4 通用标志位
unsigned int i_flags;作用:inode 标志的组合,控制文件系统行为
// 写入操作会立即同步到磁盘
#define S_SYNC (1 << 0) /* Writes are synced at once */
// 访问文件时不更新atime时间戳
#define S_NOATIME (1 << 1) /* Do not update access times */
// 文件只能以O_APPEND模式打开,即使超级用户也受此限制
#define S_APPEND (1 << 2) /* Append-only file */
// 文件内容和元数据都不能修改、删除或重命名
#define S_IMMUTABLE (1 << 3) /* Immutable file */
// 标记已被删除但仍有进程打开的目录 (系统内部使用)
#define S_DEAD (1 << 4) /* removed, but still open directory */
// 此inode不计入磁盘配额统计
#define S_NOQUOTA (1 << 5) /* Inode is not counted to quota */
// 目录修改操作同步执行
#define S_DIRSYNC (1 << 6) /* Directory modifications are synchronous */
// 不更新ctime/mtime
#define S_NOCMTIME (1 << 7) /* Do not update file c/mtime */
// 防止截断操作,因为swapon已获取其块映射
#define S_SWAPFILE (1 << 8) /* Do not truncate: swapon got its bmaps */
// 标记文件系统内部使用的inode
#define S_PRIVATE (1 << 9) /* Inode is fs-internal */
// 表示inode有关联的IMA(完整性度量架构)结构,完整性检查的一些,附带这个flag,他就要打岔特差的了
#define S_IMA (1 << 10) /* Inode has an associated IMA struct */
// 标记自动挂载点或引用型伪目录
#define S_AUTOMOUNT (1 << 11) /* Automount/referral quasi-directory */
// 无suid或xattr安全属性
#define S_NOSEC (1 << 12) /* no suid or xattr security attributes */
#ifdef CONFIG_FS_DAX
// 用于持久内存(PMEM)设备,提供直接访问以提升性能
#define S_DAX (1 << 13) /* Direct Access, avoiding the page cache */
#else
#define S_DAX 0 /* Make all the DAX code disappear */
#endif
// 使用fs/crypto进行文件加密
#define S_ENCRYPTED (1 << 14) /* Encrypted file (using fs/crypto/) */
// 标记大小写不敏感的文件/目录
#define S_CASEFOLD (1 << 15) /* Casefolded file */
// 使用fs/verity进行文件完整性保护
#define S_VERITY (1 << 16) /* Verity file (using fs/verity/) */
// 标记内核正在使用的文件(如cachefiles)
#define S_KERNEL_FILE (1 << 17) /* File is in use by the kernel (eg. fs/cachefiles) */
// 标记匿名inode(用于eventfd、signalfd等)
#define S_ANON_INODE (1 << 19) /* Inode is an anonymous inode */1.5 POSIX ACL
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif作用:POSIX 访问控制列表,提供比传统 Unix 权限更细粒度的访问控制
解析:
i_acl:文件的访问 ACLi_default_acl:目录的默认 ACL(仅用于目录,子文件会继承)支持指定多个用户和组的不同权限级别
1.6 操作接口
const struct inode_operations *i_op;作用:指向 inode 操作结构的指针,定义了对 inode 的各种操作,如创建、删除目录项等
解析:
不同文件类型有不同的操作集:
目录:
lookup、create、mkdir、rmdir等普通文件:
setattr、getattr、listxattr等符号链接:
readlink、get_link等
示例:
// 在目录中查找文件
struct dentry *dentry = dir_inode->i_op->lookup(dir_inode, target_dentry, flags);1.7 超级块关联
struct super_block *i_sb;作用:指向该 inode 所属的超级块,每个挂载的文件系统都有一个超级块
解析:
通过超级块可以访问文件系统的全局信息
包括块大小、文件系统类型、魔数等
用于回溯到文件系统级别的操作
1.8 地址空间映射
struct address_space *i_mapping;作用:指向地址空间对象的指针,通常指向 i_data,用于管理页缓存和文件映射
深度解析:
这是文件系统中最复杂的概念之一。对于普通文件,i_mapping 通常指向同一 inode 的 i_data 字段;但对于块设备文件,i_mapping 指向块设备主 inode 的地址空间,这样所有访问同一块设备的不同设备文件可以共享页缓存。
// 典型初始化(fs/inode.c)
struct address_space *const mapping = &inode->i_data;
// ...
inode->i_mapping = mapping; // 通常指向自己的 i_data二、统计和元数据字段
2.1 Inode 编号
unsigned long i_ino;作用:inode 号,在文件系统中唯一标识该 inode
解析:
inode 号在单个文件系统内唯一,但不同文件系统可以有相同的 inode 号
可以通过
ls -i命令查看inode 0 通常不使用,是保留的
查找算法: 使用公式 bg = (inode_num - 1) / sb->s_inodes_per_group 找到 inode 所在的块组
2.2 硬链接计数
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};作用:硬链接计数,指向该 inode 的目录项数量
解析:
使用 union 是为了限制直接修改:文件系统应使用
set_nlink()、inc_nlink()、drop_nlink()等辅助函数当
i_nlink降为 0 且没有进程打开文件时,inode 会被删除目录的链接数至少为 2(
.和来自父目录的链接)
2.3 设备号
dev_t i_rdev;作用:如果 inode 代表设备文件,则记录设备的主次设备号
解析:
仅对字符设备和块设备有意义
主设备号标识驱动程序,次设备号标识具体设备实例
使用
MAJOR()和MINOR()宏提取
2.4 文件大小
loff_t i_size;作用:文件大小(字节),对于符号链接是路径名长度
解析:
loff_t是 64 位类型,支持大文件对目录,大小表示目录项占用的空间
可能有 seqcount 保护(
i_size_seqcount)以确保读取一致性
2.5 时间戳
time64_t i_atime_sec; // 访问时间
time64_t i_mtime_sec; // 修改时间
time64_t i_ctime_sec; // 状态改变时间
u32 i_atime_nsec;
u32 i_mtime_nsec;
u32 i_ctime_nsec;作用:记录访问时间、修改时间、状态改变时间,使用 64 位秒和 32 位纳秒以避免 2038 年问题
解析:
传统 Unix 时间戳是 32 位,会在 2038 年 1 月溢出。现代内核使用 64 位秒 + 30 位扩展位 + 30 位纳秒,可以支持到 2446 年 5 月。
atime(访问时间):读取文件内容时更新
mtime(修改时间):写入文件内容时更新,对目录,创建或删除文件也会更新
ctime(状态改变时间):修改 inode 元数据时更新,包括权限、所有者、链接数等
性能优化:
relatime挂载选项:只在 atime 早于 mtime/ctime 或距上次更新超过 24 小时时才更新noatime挂载选项:完全禁用 atime 更新,提升性能
2.6 其他元数据
u32 i_generation; // NFS 生成号
spinlock_t i_lock; // 保护某些字段的自旋锁
unsigned short i_bytes; // 不足一块的字节数
u8 i_blkbits; // 块大小的位数(2^i_blkbits = 块大小)
enum rw_hint i_write_hint; // 写入生命周期提示
blkcnt_t i_blocks; // 占用的块数(512 字节单位)i_blocks 详解: 文件占用的块数(所有块,不只是数据块),用于配额子系统
注意:即使文件有空洞(sparse file),i_blocks 也只计算实际分配的块。这就是为什么 stat 显示的 Blocks 可能小于 Size/512。
三、状态管理和同步
3.1 Inode 状态
u32 i_state;作用:inode 状态标志,如 I_DIRTY、I_FREEING、I_CLEAR 等
重要状态位:
I_DIRTY_SYNC:inode 元数据需要同步到磁盘I_DIRTY_DATASYNC:数据需要同步I_DIRTY_PAGES:页缓存中有脏页I_NEW:inode 正在初始化I_FREEING:inode 正在被释放I_CLEAR:inode 已清理完毕I_SYNC:inode 正在被写回
3.2 读写信号量
struct rw_semaphore i_rwsem;作用:inode 级别的读写锁,用于序列化文件操作
使用场景:
写操作(写入、truncate、hole punch)需要写锁
读操作可以并发持有读锁
防止读写、写写竞争
保护文件大小变化的一致性
3.3 脏数据追踪
unsigned long dirtied_when; // jiffies 时间
unsigned long dirtied_time_when; // 时间戳类脏数据的时间作用:inode 首次变脏的时间戳,用于写回机制
解析: 内核的 writeback 机制使用这些时间戳来决定何时将脏数据写回磁盘。通常策略是:
脏数据在内存中保留一定时间(默认 30 秒)
超时后由
flusher线程写回或在内存压力大时强制写回
四、链表管理(内核内部链表)
4.1 Inode 哈希表
struct hlist_node i_hash;作用:将 inode 链入全局哈希表,用于快速查找
解析: 内核维护一个全局的 inode 哈希表,哈希键是 (superblock, ino) 对。这样可以快速检查某个 inode 是否已在内存中。
4.2 I/O 列表
struct list_head i_io_list;作用:backing device I/O 列表,用于写回队列管理
深度解析:
该字段曾被称为 i_wb_list,后重命名为 i_io_list 以更好地反映其用途。脏 inode 会被放入以下队列之一:
b_dirty:新变脏的 inodeb_io:等待写回的 inodeb_more_io:本轮写回未完成的 inode
写回线程会遍历这些列表,调用 writepage() 将脏页写回磁盘。
4.3 Cgroup Writeback(可选)
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; // 关联的 writeback 结构
int i_wb_frn_winner; // 外部 inode 检测的胜者
u16 i_wb_frn_avg_time; // 平均时间
u16 i_wb_frn_history; // 历史记录
#endif作用:关联的 cgroup writeback 结构和外部 inode 检测信息
解析: 在启用 cgroup 的系统中,每个 cgroup 可以有独立的 I/O 带宽限制。这些字段用于追踪 inode 属于哪个 cgroup 的 writeback 结构。
4.4 LRU 列表
struct list_head i_lru;作用:inode LRU 列表,用于内存回收
解析: 不活跃的 inode(引用计数为 0 但尚未释放)会被放入 LRU 列表。当内存紧张时,shrinker 可以从 LRU 尾部回收最久未使用的 inode。
4.5 其他列表
struct list_head i_sb_list; // 超级块的 inode 列表
struct list_head i_wb_list; // writeback 列表i_wb_list 解析: 用于 sync 操作的 writeback 列表,只包含正在写回的 inode,而不是所有脏 inode
五、目录项和引用计数
5.1 Dentry 关联
union {
struct hlist_head i_dentry; // 指向该 inode 的 dentry 列表
struct rcu_head i_rcu; // RCU 延迟释放
};作用:一个 inode 可能被多个目录项(硬链接)指向,这个链表头连接所有这些 dentry
RCU 释放: 当 inode 不再使用时,通过 RCU 机制延迟释放,确保正在进行的 RCU 读操作完成后才真正释放内存。
5.2 版本和序列号
atomic64_t i_version; // 文件版本号(用于检测变化)
atomic64_t i_sequence; // futex 等使用i_version 用途:
NFS 使用
i_version来检测文件是否被修改一些文件系统(如 ext4)在每次修改时递增这个值
i_sequence 用途: 用于 futex 等同步原语,以及某些需要检测文件内容变化的场景
5.3 引用计数
atomic_t i_count; // 内核引用计数
atomic_t i_dio_count; // 直接 I/O 计数
atomic_t i_writecount; // 写者计数
atomic_t i_readcount; // 只读打开计数i_count 详解: inode 引用计数,指示有多少内核组件在使用它
i_count > 0:inode 正在被使用,不能释放i_count == 0:inode 可以放入 LRU 或直接释放
i_writecount 用途:
追踪有多少进程以写模式打开了文件
用于防止在文件正被写入时执行(execve)
写者互斥:不能同时作为可执行文件运行和被写入
i_dio_count 用途: 追踪正在进行的直接 I/O 操作数量,用于 truncate 等操作的同步。
六、文件操作和锁
6.1 文件操作表
union {
const struct file_operations *i_fop;
void (*free_inode)(struct inode *);
};作用:文件操作结构指针或释放函数指针
解析:
普通使用时,
i_fop指向文件操作表(read、write、ioctl 等)inode 释放时,union 的另一半用于存储释放回调函数
示例:
// 打开文件时(fs/open.c)
file->f_op = fops_get(inode->i_fop);6.2 文件锁上下文
struct file_lock_context *i_flctx;作用:文件锁管理结构,支持 POSIX 锁、flock、租约(lease)
解析: 这是延迟分配的结构,只有在文件实际使用锁时才创建。包含三个链表:
flc_flock:flock 锁flc_posix:POSIX 锁(fcntl)flc_lease:租约
七、地址空间和页缓存
7.1 内嵌地址空间
struct address_space i_data;作用:内嵌的地址空间对象,用于管理该文件的页缓存
深度解析:
这是最复杂的字段之一。address_space 是页缓存的核心抽象:
struct address_space {
struct inode *host; // 拥有者 inode
struct xarray i_pages; // 页缓存的基数树/XArray
struct rw_semaphore invalidate_lock; // 失效保护
gfp_t gfp_mask; // 内存分配标志
atomic_t i_mmap_writable; // 可写映射计数
struct rb_root_cached i_mmap; // 内存映射的红黑树
unsigned long nrpages; // 页面总数
pgoff_t writeback_index; // 写回起始位置
const struct address_space_operations *a_ops; // 操作表
unsigned long flags; // 状态标志
// ...
};address_space_operations: 操作表包含 readpage、writepage、write_begin、write_end 等方法,由文件系统实现
页缓存工作流程:
读取:首先在页缓存中查找,未找到则分配新页并调用 readpage() 从磁盘读取
写入:调用 prepare_write(),复制用户数据,再调用 commit_write()
写回:bdflush 内核线程定期调用 writepage() 将脏页写回磁盘
八、特殊文件类型字段
8.1 设备和链接
union {
struct list_head i_devices; // 设备列表(块/字符设备)
int i_linklen; // 快速符号链接的长度
};解析:
对于设备文件,
i_devices链接所有引用同一设备的 inode对于符号链接,
i_linklen缓存链接目标的长度
8.2 类型特定数据
union {
struct pipe_inode_info *i_pipe; // 管道信息
struct cdev *i_cdev; // 字符设备
char *i_link; // 符号链接目标
unsigned i_dir_seq; // 目录序列号
};各类型详解:
管道:
i_pipe指向管道缓冲区和等待队列管道是特殊的 inode,没有对应磁盘上的文件
字符设备:
i_cdev链接到字符设备驱动通过
cdev_add()注册
符号链接:
短链接:目标路径存储在
i_link指向的内存中长链接:存储在数据块中,需要读取
目录序列号: 用于 dcache 的并发控制,检测目录是否被修改。
九、通知和安全特性
9.1 文件系统通知
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; // 关心的事件掩码
struct fsnotify_mark_connector __rcu *i_fsnotify_marks; // 标记连接器
#endif作用:文件系统事件通知掩码和标记连接器
解析: fsnotify 是 Linux 的文件系统事件通知机制,支持:
inotify:监控单个文件或目录
fanotify:全局监控,可以拦截操作
dnotify:老旧接口,不推荐使用
工作原理:
应用程序通过
inotify_add_watch()注册监听内核在相应事件发生时检查
i_fsnotify_mask如果 inode 关心该事件,通过
i_fsnotify_marks通知所有监听者
9.2 文件加密
#ifdef CONFIG_FS_ENCRYPTION
struct fscrypt_inode_info *i_crypt_info;
#endif作用:文件系统加密信息指针(较新内核版本已移至文件系统私有结构)
解析: 支持文件系统级透明加密(fscrypt),用于 ext4、F2FS、UBIFS 等。
演进历史: 为节省内存,新内核将 i_crypt_info 移至文件系统特定的 inode 结构中,对不支持加密的文件系统节省 8 字节
加密工作流程:
每个目录可以有不同的加密策略
文件名和内容都可以加密
使用 AES-256-XTS(内容)和 AES-256-CTS(文件名)
9.3 完整性验证
#ifdef CONFIG_FS_VERITY
struct fsverity_info *i_verity_info;
#endif作用:文件系统完整性验证信息指针(较新内核版本已移至文件系统私有结构)
解析: fsverity 提供文件完整性保护,使用 Merkle 树:
只读保护:一旦启用,文件不能被修改
每次读取都验证数据完整性
用于保护系统文件、APK 等
性能优化: 与加密类似,新内核将 i_verity_info 移至文件系统特定结构,节省内存
十、私有扩展
10.1 文件系统私有指针
void *i_private;作用:文件系统或设备的私有指针,指向文件系统特定的 inode 信息结构
解析: 每个文件系统都有自己的私有 inode 结构,包含 VFS inode 和文件系统特定字段:
// ext4 示例
struct ext4_inode_info {
struct inode vfs_inode; // VFS inode(必须是第一个字段)
__le32 i_data[15]; // 块指针
__u32 i_dtime; // 删除时间
__u32 i_flags; // ext4 特定标志
// ... 更多 ext4 特定字段
};通过 container_of 宏互相转换:
// VFS inode -> ext4 inode
struct ext4_inode_info *ei = EXT4_I(inode);
// ext4 inode -> VFS inode
struct inode *inode = &ei->vfs_inode;性能优化实践
1. 缓存行考虑
热字段被分组在开头,最大化 CPU 缓存效率:
// 第一缓存行:i_mode, i_uid, i_gid, i_flags, i_op, i_sb
// 第二缓存行:i_ino, i_nlink, i_size, timestamps2. RCU 友好设计
目录查找使用 RCU,避免锁竞争:
rcu_read_lock();
dentry = d_lookup_rcu(parent, name); // 无锁查找
rcu_read_unlock();3. 减少 dentry 遍历
当目录有大量子文件时,__fsnotify_update_child_flags() 遍历所有子 dentry 可能导致软锁定。解决方案:
使用
i_rwsem保护更新,确保只执行一次避免在持有自旋锁时遍历
调试技巧
使用 crash 工具
# 查看 inode 内容
crash> struct inode ffffffc073c1f360
struct inode {
i_mode = 0100644,
i_uid = {val = 1000},
i_size = 4096,
i_ino = 123456,
// ...
}通过 /proc 接口
# 查看 inode 数量
cat /proc/sys/fs/inode-nr
# 输出:nr_inodes nr_unused
# 查看某文件的 inode
stat /path/to/file使用 BPF 跟踪
# 跟踪 inode 分配
bpftrace -e 'kprobe:new_inode { @[comm] = count(); }'
# 跟踪写回
bpftrace -e 'kprobe:writeback_single_inode {
printf("%s writing inode %lu\n", comm, arg0->i_ino);
}'常见问题和陷阱
1. Inode 耗尽
即使磁盘有空间,也可能因 inode 耗尽而无法创建文件:
$ touch newfile
touch: cannot touch 'newfile': No space left on device
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 100G 50G 50G 50% /
$ df -i # 检查 inode
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda1 6.5M 6.5M 0 100% /解决方案:
清理不需要的小文件
重新格式化时增加 inode 数量:
mkfs.ext4 -N 10000000 /dev/sdX
2. 硬链接限制
ext4 的硬链接上限是 65000(受 i_nlink 字段大小限制)
3. 符号链接循环
内核检测符号链接循环,限制最大跟随深度(通常 40):
$ ln -s a b
$ ln -s b a
$ cat a
cat: a: Too many levels of symbolic links与其他内核结构的关系
Inode vs Dentry
文件系统视图(磁盘) VFS 视图(内存) 用户视图
inode <-----> struct inode <-----> fd
↑ ↑ ↑
| | |
目录项 <-----> struct dentry <----> 路径名inode:表示文件本身,包含元数据
dentry:表示目录项,连接文件名和 inode
一个 inode 可以有多个 dentry(硬链接)
Inode vs File
struct file {
struct path f_path; // dentry + vfsmount
struct inode *f_inode; // 快速访问 inode
const struct file_operations *f_op;
loff_t f_pos; // 文件位置
unsigned int f_flags; // O_RDONLY, O_NONBLOCK 等
// ...
};struct file 代表进程打开的文件,每次 open() 都创建新的 struct file,但多个 file 可能指向同一 inode
Inode vs Superblock
Superblock (每个挂载点一个)
|
+-- s_inodes (所有 inode 列表)
+-- s_type (文件系统类型)
+-- s_op (超级块操作)
+-- ...
每个 inode->i_sb 指回超级块参考资料
Linux 内核源码:
include/linux/fs.h、fs/inode.cLinux 内核文档:https://www.kernel.org/doc/html/latest/filesystems/
Understanding the Linux Kernel (3rd Edition)
Linux Kernel Development (3rd Edition)
关于作者:本文基于 Linux 主线内核最新代码分析,结合网络搜索的官方文档和内核源码撰写。
版权声明:本文采用 CC BY-SA 4.0 协议,欢迎转载,请注明出处。
更新日期:2025-11-19