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 回调函数介入。

  1. 分配阶段(alloc_super):VFS 调用 alloc_super() 分配 super_block 结构体内存,初始化链表头、信号量、引用计数(s_count 初始为 1)。此时超级块处于 SB_ALLOCATED 状态。
  2. 填充阶段(fill_super):具体文件系统的 fill_super() 回调被调用。该函数从磁盘读取超级块原始数据,填充 s_fs_info、s_root、s_op 等字段。若失败则回滚释放。
  3. 激活阶段(sget / sb_set):超级块被插入全局超级块链表(super_blocks),设置 SB_ACTIVE 标志。此时文件系统可被用户进程访问。
  4. 同步阶段(sync_filesystem):内核定期或主动调用 sync_filesystem(),通过 s_op->sync_fs() 将内存中的脏元数据写回磁盘。
  5. 销毁阶段(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、块设备以及文件系统类型之间的核心关联:

super_block 核心关系架构图 super_block 核心关系架构图 super_block s_dev | s_fs_info | s_op | s_flags file_system_type s_type 指向 block_device s_bdev 指向 s_type s_bdev inode 链表 s_inodes 链表头 每个 inode->i_sb_list dentry LRU 链表 s_dentry_lru 链表头 回收候选 dentry s_inodes s_dentry_lru 根 dentry (s_root) 路径查找起点 s_root super_operations (s_op) alloc_inode | destroy_inode | sync_fs s_op 说明:实线箭头表示指针指向关系;超级块通过 s_inodes 和 s_dentry_lru 管理所有 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 实例。