2. 超级块对象:super_block 结构体详解,关键字段与生命周期
超级块(Super Block)是 Linux 虚拟文件系统(VFS)中最高层级的对象,代表一个已挂载的文件系统实例。每个挂载的文件系统(如 ext4、XFS、NFS)在内存中都有一个对应的 super_block 结构体,它承载了该文件系统的全局元数据、操作函数指针以及状态信息。理解 super_block 是掌握 inode 与 dentry 管理的前提。
2.1 super_block 核心数据结构
super_block 定义在 <linux/fs.h> 中,是一个超过 50 个字段的巨型结构体。以下按功能域分类列出最关键的字段:
| 功能域 | 字段名 | 类型 | 说明 |
|---|---|---|---|
| 设备标识 | s_dev |
dev_t |
块设备主/次设备号,唯一标识底层存储设备 |
s_bdev |
struct block_device * |
指向底层块设备描述符,用于 I/O 操作 | |
s_fs_info |
void * |
指向具体文件系统私有数据(如 ext4 的 ext4_sb_info) | |
| 文件系统类型 | s_type |
struct file_system_type * |
指向文件系统类型描述符(如 ext4_fs_type) |
s_id |
char[32] |
文件系统名称字符串(如 "ext4") | |
| 根节点与挂载 | s_root |
struct dentry * |
文件系统根目录的 dentry 对象,是路径查找的起点 |
s_mounts |
struct list_head |
挂载点链表头,链接所有属于该超级块的 mount 结构 | |
s_umount |
struct rw_semaphore |
卸载/重挂载保护读写信号量 | |
| 对象链表 | s_inodes |
struct list_head |
所有 inode 的链表头(通过 inode->i_sb_list 链接) |
s_dentry_lru |
struct list_head |
dentry LRU 回收链表头 | |
s_d_op |
struct dentry_operations * |
默认的 dentry 操作函数表 | |
| 操作函数表 | s_op |
struct super_operations * |
超级块操作函数表(alloc_inode、destroy_inode、sync_fs 等) |
s_export_op |
struct export_operations * |
NFS 导出操作函数表 | |
| 标志与状态 | s_flags |
unsigned long |
挂载标志位(MS_RDONLY、MS_NOATIME 等) |
s_iflags |
unsigned long |
内部标志位(SB_ACTIVE、SB_BORN 等) | |
| 时间戳 | s_time_min / s_time_max |
time64_t |
文件系统支持的时间戳范围 |
实战要点: 在调试文件系统问题时,
s_fs_info 是最常被强制转换访问的字段。例如 ext4 中通过 EXT4_SB(sb) 宏获取 ext4_sb_info,其中包含块组描述符、日志信息等关键数据。
2.2 super_block 生命周期:从创建到销毁
超级块的生命周期严格遵循“分配 → 填充 → 激活 → 同步 → 销毁”五个阶段。每个阶段都有对应的 VFS 回调函数介入。
- 分配阶段(alloc_super):VFS 调用
alloc_super()分配super_block结构体内存,初始化链表头、信号量、引用计数(s_count初始为 1)。此时超级块处于 SB_ALLOCATED 状态。 - 填充阶段(fill_super):具体文件系统的
fill_super()回调被调用。该函数从磁盘读取超级块原始数据,填充s_fs_info、s_root、s_op等字段。若失败则回滚释放。 - 激活阶段(sget / sb_set):超级块被插入全局超级块链表(
super_blocks),设置SB_ACTIVE标志。此时文件系统可被用户进程访问。 - 同步阶段(sync_filesystem):内核定期或主动调用
sync_filesystem(),通过s_op->sync_fs()将内存中的脏元数据写回磁盘。 - 销毁阶段(kill_sb / deactivate_super):卸载文件系统时,调用
deactivate_super()减少引用计数。当s_count降为 0 时,调用kill_sb()释放所有资源,包括 inode 缓存、dentry 缓存和s_fs_info。
关键生命周期回调函数表:
| 阶段 | VFS 函数 | 文件系统回调 | 典型操作 |
|---|---|---|---|
| 分配 | alloc_super() |
— | 初始化 s_list、s_umount、s_inodes |
| 填充 | sget_fc() |
fill_super() |
读取磁盘超级块,设置 s_fs_info |
| 激活 | grab_super() |
— | 设置 SB_ACTIVE,挂载根 dentry |
| 同步 | sync_filesystem() |
sync_fs() |
写回脏 inode、更新磁盘超级块 |
| 销毁 | deactivate_super() |
kill_sb() |
释放 inode/dentry 缓存,销毁 s_fs_info |
2.3 super_block 与 inode/dentry 的关联关系
超级块是 inode 和 dentry 的“容器”和“工厂”。理解三者之间的指针链接是阅读内核源码的基础。
- 超级块 → inode:
sb->s_inodes链表串联所有属于该文件系统的 inode。每个 inode 的i_sb_list链表节点挂在此链表上。 - 超级块 → dentry:
sb->s_dentry_lru链表用于 dentry 的 LRU 回收。此外,sb->s_root指向根 dentry,是路径解析的入口。 - inode → 超级块:每个 inode 的
i_sb指针指向所属超级块,用于 inode 操作时获取文件系统上下文。 - dentry → 超级块:每个 dentry 的
d_sb指针指向所属超级块,用于 dentry 操作时获取挂载选项。
常见陷阱: 在文件系统卸载过程中,
s_root 会被置为 NULL,但 dentry 缓存可能仍持有 d_sb 指针。此时若通过 dentry 访问超级块,必须检查 d_sb->s_root 是否有效,否则会触发空指针解引用。
2.4 super_block 核心关系 SVG 架构图
下图展示了超级块与 inode、dentry、块设备以及文件系统类型之间的核心关联:
2.5 关键操作函数表:super_operations
s_op 指向的 super_operations 结构体定义了文件系统在超级块层面的行为。以下是开发文件系统时必须实现的几个核心回调:
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *inode);
void (*dirty_inode)(struct inode *inode, int flags);
int (*write_inode)(struct inode *inode, struct writeback_control *wbc);
void (*evict_inode)(struct inode *inode);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super)(struct super_block *sb);
int (*thaw_super)(struct super_block *sb);
int (*statfs)(struct dentry *dentry, struct kstatfs *buf);
int (*remount_fs)(struct super_block *sb, int *flags, char *data);
};
回调调用时机速查表:
| 回调 | 触发场景 | 典型实现 |
|---|---|---|
alloc_inode |
创建新文件或目录时 | 从 slab 缓存分配 inode 结构体 |
destroy_inode |
inode 引用计数归零时 | 释放 inode 关联的扩展属性、ACL |
dirty_inode |
inode 元数据被修改时 | 标记 inode 为脏,加入脏链表 |
write_inode |
回写线程刷脏 inode 时 | 将 inode 写入磁盘 inode 表 |
evict_inode |
inode 从缓存中移除时 | 清理页缓存、释放磁盘块 |
sync_fs |
sync() 系统调用或周期性回写 | 刷写日志、更新磁盘超级块 |
statfs |
df / statfs 系统调用 | 返回块总数、可用块数等统计 |
2.6 超级块标志位详解
s_flags 字段存储挂载时指定的选项标志,s_iflags 存储内核内部状态标志。两者共同决定超级块的运行时行为。
| 标志 | 值 | 含义 | 设置时机 |
|---|---|---|---|
MS_RDONLY |
1 | 只读挂载,禁止写操作 | mount -o ro |
MS_NOSUID |
2 | 忽略 suid/sgid 位 | mount -o nosuid |
MS_NOEXEC |
8 | 禁止执行二进制文件 | mount -o noexec |
MS_RELATIME |
0x200000 | 相对 atime 更新策略 | mount -o relatime |
SB_ACTIVE |
0x10 | 超级块已激活,可被访问 | 挂载完成后设置 |
SB_BORN |
0x20 | 超级块已完全初始化 | fill_super 成功后设置 |
SB_DYING |
0x40 | 超级块正在被销毁 | 卸载开始时设置 |
调试技巧: 在
/proc/fs/<fsname>/sb 或通过 bpftrace 跟踪 alloc_super / kill_sb 可以观察超级块的创建和销毁。使用 cat /proc/self/mountinfo 可查看当前所有挂载点的超级块设备号。
2.7 实战:通过 /proc 观察超级块
以下命令和代码片段演示如何在用户态和内核态获取超级块信息:
# 用户态:查看所有挂载点的超级块设备号
$ cat /proc/self/mountinfo | head -5
19 31 8:1 / / rw,relatime - ext4 /dev/sda1 rw
20 31 0:20 / /proc rw,nosuid,nodev,noexec - proc proc rw
21 31 0:21 / /sys rw,nosuid,nodev,noexec - sysfs sysfs rw
# 第一列是挂载 ID,第三列是超级块设备号(主:次)
# 8:1 对应 /dev/sda1 的超级块
// 内核模块:遍历超级块链表
#include <linux/fs.h>
#include <linux/kernel.h>
static void list_super_blocks(void) {
struct super_block *sb;
spin_lock(&sb_lock);
list_for_each_entry(sb, &super_blocks, s_list) {
pr_info("SB: dev=%d:%d type=%s root=%p\n",
MAJOR(sb->s_dev), MINOR(sb->s_dev),
sb->s_type->name, sb->s_root);
}
spin_unlock(&sb_lock);
}
学习路径建议: 阅读
fs/super.c 中的 alloc_super() 和 kill_super() 函数,配合 fs/ext4/super.c 中的 ext4_fill_super(),可以完整理解超级块从分配到销毁的全流程。下一章将深入 inode 结构体,届时你会看到超级块如何通过 s_op->alloc_inode 创建 inode 实例。