2. 超级块对象:struct super_block 结构体详解、关键字段与生命周期

超级块对象是 VFS 四大对象模型中的核心元数据载体,它代表一个已挂载的文件系统实例。每个文件系统在挂载时,内核都会在内存中创建一个对应的 struct super_block 实例,用于描述该文件系统的全局信息,包括块大小、操作函数表、根目录 dentry、挂载点列表等。理解超级块的结构与生命周期,是掌握 VFS 挂载流程和文件系统交互的基础。

2.1 struct super_block 核心字段解析

struct super_block 定义在 include/linux/fs.h 中,包含数十个字段。下表列出面试中最常考察的关键字段及其作用:

字段名 类型 作用描述
s_list struct list_head 全局超级块链表节点,链接所有已挂载文件系统的超级块
s_dev dev_t 该文件系统所驻留块设备的设备号
s_blocksize unsigned int 文件系统逻辑块大小(字节),通常为 1024、2048 或 4096
s_blocksize_bits unsigned char log2(s_blocksize),用于快速移位运算
s_type struct file_system_type * 指向文件系统类型描述符,如 ext4、btrfs、xfs
s_op struct super_operations * 超级块操作函数表,包含 alloc_inode、destroy_inode、write_super 等回调
s_root struct dentry * 文件系统根目录的 dentry 对象,挂载点查找的起点
s_mounts struct list_head 挂载实例链表头,链接该超级块下的所有 mount 结构
s_dirty struct list_head 脏 inode 链表,等待回写磁盘的 inode 集合
s_fs_info void * 文件系统私有数据指针,指向具体文件系统的超级块信息(如 ext4 的 ext4_sb_info)
s_user_ns struct user_namespace * 挂载命名空间的用户命名空间,用于权限检查
s_time_gran u32 文件时间戳精度(纳秒),影响 inode 时间戳更新粒度
面试高频点: 面试官常问 s_fs_info 的作用——它是 VFS 与具体文件系统之间的"适配层指针"。例如 ext4 挂载时,s_fs_info 指向 struct ext4_sb_info,其中包含 ext4 特有的日志、块组描述符等元数据。VFS 通过 s_op 回调操作超级块,而具体实现则通过 s_fs_info 访问私有数据。

2.2 超级块操作函数表 super_operations

struct super_operations 定义了 VFS 与具体文件系统之间关于超级块和 inode 管理的接口。每个文件系统必须实现该表中的核心回调:

  • alloc_inode:分配一个新的 inode 结构体,通常包含文件系统私有 inode 数据(如 ext4 的 ext4_inode_info)。
  • destroy_inode:销毁 inode 结构体,释放私有数据。
  • dirty_inode:标记 inode 为脏,触发回写。
  • write_inode:将 inode 元数据写入磁盘。
  • drop_inode:当 inode 引用计数降为 0 时调用,决定是否删除 inode。
  • put_super:卸载文件系统时释放超级块资源。
  • sync_fs:同步文件系统元数据和数据到磁盘。
  • statfs:返回文件系统统计信息(如 df 命令的数据来源)。
  • remount_fs:重新挂载文件系统时调用,处理挂载选项变更。
记忆技巧: 超级块操作函数表可以归纳为三类:inode 生命周期管理(alloc/destroy/dirty/write/drop)、超级块生命周期管理(put_super/remount_fs)、全局查询与同步(sync_fs/statfs)。面试时按这三类回答,逻辑更清晰。

2.3 超级块的生命周期

超级块对象从创建到销毁经历四个阶段,每个阶段都有特定的内核函数和状态转换:

  1. 分配与初始化:alloc_super() 分配 struct super_block 内存,初始化链表头、信号量、引用计数等。此时 s_root 为 NULL,s_fs_info 未赋值。
  2. 填充与挂载:fill_super() 回调(由具体文件系统实现)从磁盘读取超级块元数据,设置 s_blocksize、s_op、s_fs_info,并创建根目录 dentry 赋值给 s_root。完成后超级块进入"活动"状态。
  3. 运行与维护:超级块在文件系统挂载期间持续存在,通过 s_op 回调响应 VFS 请求。脏 inode 通过 s_dirty 链表管理,sync_fs 定期或按需回写。
  4. 卸载与销毁:kill_sb() 或 deactivate_super() 触发销毁流程。先调用 put_super 释放文件系统私有资源,然后清理 dentry 缓存、inode 缓存,最后释放 struct super_block 内存。
注意: 超级块的生命周期与挂载计数(s_count)和活跃引用(s_active)密切相关。当 s_active 降为 0 时,内核才会真正销毁超级块。如果文件系统被多个挂载点共享(如 bind mount),s_active 会大于 1,超级块会一直存活直到所有挂载点都被卸载。

2.4 超级块生命周期状态机

下图展示了超级块从分配到销毁的完整状态转换流程,以及关键函数调用点:

超级块生命周期状态机 分配 alloc_super() 填充 fill_super() 活动 s_active > 0 销毁 kill_sb() s_active 降为 0 填充失败 关键状态说明 • 分配后 s_root = NULL • 填充后设置 s_op / s_fs_info • 活动期通过 s_op 回调工作 • 销毁前清理 dentry/inode 缓存 引用计数 • s_count:通用引用 • s_active:活跃挂载数 • s_active=0 触发销毁 • umount 递减 s_active

2.5 超级块与文件系统类型的关联

每个超级块通过 s_type 字段关联到 struct file_system_type,后者是文件系统类型的全局描述符。这种关联关系是 VFS 实现多文件系统支持的关键:

// 文件系统类型注册示例(简化)
static struct file_system_type ext4_fs_type = {
    .owner      = THIS_MODULE,
    .name       = "ext4",
    .mount      = ext4_mount,      // 挂载入口,返回超级块
    .kill_sb    = ext4_kill_sb,    // 销毁超级块
    .fs_flags   = FS_REQUIRES_DEV,
};

// 挂载时内核调用 ext4_fs_type->mount()
// 该函数内部调用 mount_bdev() -> ext4_fill_super()
// 最终将超级块的 s_type 指向 ext4_fs_type
面试追问: "如果两个不同的文件系统类型挂载到同一个块设备上,超级块如何区分?"
答案:每个文件系统实例有独立的 struct super_block,s_type 指向不同的 file_system_type,s_fs_info 指向不同的私有数据结构。即使底层块设备相同,VFS 也通过超级块对象完全隔离两者的元数据。

2.6 超级块在 VFS 对象模型中的位置

超级块是 VFS 四大对象(超级块、inode、dentry、file)的"元数据中枢"。下图展示了超级块与其他 VFS 对象的核心关联:

超级块在 VFS 对象模型中的核心位置 struct super_block s_op / s_root / s_fs_info file_system_type s_type 指向 dentry (s_root) 根目录入口 inode 链表 s_inodes / s_dirty mount 实例 s_mounts 链表 超级块通过 s_type 关联文件系统类型,通过 s_root 提供根目录入口,通过 s_inodes 管理所有 inode,通过 s_mounts 链接挂载实例

2.7 面试常见追问与避坑

  • Q:超级块和 inode 的分配顺序?
    A:先分配超级块(alloc_super),再通过 fill_super 创建根 inode 和根 dentry。卸载时先销毁 inode 和 dentry,最后销毁超级块。
  • Q:s_dirty 链表如何工作?
    A:当 inode 被修改时,通过 mark_inode_dirty() 将其加入 s_dirty 链表。内核线程 sync_supers 或 writeback 工作队列遍历该链表,调用 s_op->write_inode 回写。
  • Q:超级块可以被多个挂载点共享吗?
    A:可以。bind mount 或同一文件系统的多次挂载(如 mount -t ext4 /dev/sda1 /mnt1 和 /mnt2)会共享同一个超级块,通过 s_active 引用计数管理。
  • 避坑: 不要混淆 s_count 和 s_active。s_count 是通用引用计数(包括内核内部引用),s_active 仅表示用户空间可见的挂载引用数。只有 s_active 降为 0 才触发销毁。
面试加分项: 提到超级块在容器场景下的特殊处理——每个容器命名空间可以有独立的挂载视图,但底层超级块可能共享。内核通过 s_user_ns 字段实现用户命名空间隔离,确保不同容器对同一超级块的访问权限受控。