前面几篇文章,介绍了内核虚拟文件系统VFS的一些基本概念,今天开始正式详细介绍几个核心数据结构:超级块super_block、索引节点inode、目录项dentry、文件对象file、挂载点mount和vfsmount。对于文件系统来说,在谈论数据结构时,一般至少要关注三个方面:持久化的结构(磁盘上)、内存中的结构、以及VFS的结构。
Linux有一棵全局文件系统树,文件系统要被用户使用,必须先安装(挂载)到这棵树上。每一次安装被都会生成一个装载实例。每个文件系统装载后,都包含上述提到的四个对象:mount(含vfsmount)、超级块、根inode和根dentry,而文件对象file,是文件系统中文件被进程使用后产生的,可以认为是一个打开的文件。

一、超级块简介
首先来看一下超级块super_block,超级块包含了文件系统基本信息。如果是基于磁盘的文件系统,那么超级块以特定格式持久化于磁盘的固定区域(一般是第2个块,第1个是引导块)。
在文件系统被安装(挂载)时,这块内容被读入内存,然后构建出内存中的超级块,其中某些公共信息为文件系统所共有,将会被提炼成VFS的超级块结构。然而某些文件系统可能不具有磁盘上超级块或内存中的超级块,但是它们必须构造出VFS的超级块。
因此有超级块有三种形态:
1)磁盘上持久化的超级块,一般是放在文件的第2个block里(文件系统第一个block)
2)内存中的超级块。
3)VFS拥有的超级块对象
二、超级块结构举例说明
以下以ext4为例,展示3种形态的超级块。
1.持久化的超级块
结构体是ext4_super_block 。可以看到字段排列非常整齐,整个布局格式,就是盘上持久化的样子,顺序不能随意更改。主要包含的字段:inode数量、block数量、剩余block数量、剩余inode数量、block大小等等(结构体很大,未完整显示)
内核版本:__le32是小端无符号32位整型
/*
* Structure of the super block
*/
struct ext4_super_block {
/*00*/ __le32 s_inodes_count; /* Inodes count */
__le32 s_blocks_count_lo; /* Blocks count */
__le32 s_r_blocks_count_lo; /* Reserved blocks count */
__le32 s_free_blocks_count_lo; /* Free blocks count */
/*10*/ __le32 s_free_inodes_count; /* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size; /* Block size */
__le32 s_log_cluster_size; /* Allocation cluster size */
/*20*/ __le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_clusters_per_group; /* # Clusters per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime; /* Mount time */
/*30*/ __le32 s_wtime; /* Write time */
__le16 s_mnt_count; /* Mount count */
__le16 s_max_mnt_count; /* Maximal mount count */
__le16 s_magic; /* Magic signature */
__le16 s_state; /* File system state */
__le16 s_errors; /* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
......
......
__u8 s_wtime_hi;
__u8 s_mtime_hi;
__u8 s_mkfs_time_hi;
__u8 s_lastcheck_hi;
__u8 s_first_error_time_hi;
__u8 s_last_error_time_hi;
__u8 s_first_error_errcode;
__u8 s_last_error_errcode;
__le16 s_encoding; /* Filename charset encoding */
__le16 s_encoding_flags; /* Filename charset encoding flags */
__le32 s_orphan_file_inum; /* Inode for tracking orphan inodes */
__le32 s_reserved[94]; /* Padding to the end of the block */
__le32 s_checksum; /* crc32c(superblock) */
};
2.内存结构的超级块
接下来是ext4超级块内存中的结构ext4_sb_info,很多字段描述了indoe、block和group信息。当ext4文件系统被挂载到文件系统树上时,将会在内存中构造如下数据结构。查看内核代码ext4目录下super.c,当ext4文件系统挂载时,会调用ext4_fill_super()函数,来填充内存结构的超级块。
/*
* fourth extended-fs super-block data in memory
*/
struct ext4_sb_info {
unsigned long s_desc_size; /* Size of a group descriptor in bytes */
unsigned long s_inodes_per_block;/* Number of inodes per block */
unsigned long s_blocks_per_group;/* Number of blocks in a group */
unsigned long s_clusters_per_group; /* Number of clusters in a group */
unsigned long s_inodes_per_group;/* Number of inodes in a group */
unsigned long s_itb_per_group; /* Number of inode table blocks per group */
unsigned long s_gdb_count; /* Number of group descriptor blocks */
unsigned long s_desc_per_block; /* Number of group descriptors per block */
ext4_group_t s_groups_count; /* Number of groups in the fs */
ext4_group_t s_blockfile_groups;/* Groups acceptable for non-extent files */
unsigned long s_overhead; /* # of fs overhead clusters */
unsigned int s_cluster_ratio; /* Number of blocks per cluster */
unsigned int s_cluster_bits; /* log2 of s_cluster_ratio */
loff_t s_bitmap_maxbytes; /* max bytes for bitmap files */
struct buffer_head * s_sbh; /* Buffer containing the super block */
struct ext4_super_block *s_es; /* Pointer to the super block in the buffer */
struct buffer_head * __rcu *s_group_desc;
......
};
static int ext4_fill_super(struct super_block *sb, void *data, int silent)
{
struct dax_device *dax_dev = fs_dax_get_by_bdev(sb->s_bdev);
char *orig_data = kstrdup(data, GFP_KERNEL);
struct buffer_head *bh, **group_desc;
struct ext4_super_block *es = NULL;
struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
struct flex_groups **flex_groups;
ext4_fsblk_t block;
ext4_fsblk_t sb_block = get_sb_block(&data);
ext4_fsblk_t logical_sb_block;
3.VFS使用的超级块对象
VFS使用的超级块super_block,是VFS抽取所有文件系统的公共信息,是操作系统管理文件系统使用,在文件系统挂载时需要生成super_block的内存结构。每次操作系统启动时,都要重新生成VFS的超级块信息。
操作系统和VFS通过注册的超级块,就可以获取挂载点信息,进行挂载管理、进行文件系统操作、以及在卸载时释放资源。
struct super_block {
struct list_head s_list; /* Keep this first */
dev_t s_dev; /* search index; _not_ kdev_t */
unsigned char s_blocksize_bits;
unsigned long s_blocksize;
loff_t s_maxbytes; /* Max file size */
struct file_system_type *s_type;
const struct super_operations *s_op;
const struct dquot_operations *dq_op;
const struct quotactl_ops *s_qcop;
const struct export_operations *s_export_op;
unsigned long s_flags;
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic;
struct dentry *s_root;
struct rw_semaphore s_umount;
int s_count;
atomic_t s_active;
#ifdef CONFIG_SECURITY
void *s_security;
#endif
const struct xattr_handler **s_xattr;
#ifdef CONFIG_FS_ENCRYPTION
const struct fscrypt_operations *s_cop;
struct key *s_master_keys; /* master crypto keys in use */
#endif
#ifdef CONFIG_FS_VERITY
const struct fsverity_operations *s_vop;
#endif
#ifdef CONFIG_UNICODE
struct unicode_map *s_encoding;
__u16 s_encoding_flags;
#endif
struct hlist_bl_head s_roots; /* alternate root dentries for NFS */
struct list_head s_mounts; /* list of mounts; _not_ for fs use */
struct block_device *s_bdev;
struct backing_dev_info *s_bdi;
struct mtd_info *s_mtd;
struct hlist_node s_instances;
unsigned int s_quota_types; /* Bitmask of supported quota types */
struct quota_info s_dquot; /* Diskquota specific options */
struct sb_writers s_writers;
/*
* Keep s_fs_info, s_time_gran, s_fsnotify_mask, and
* s_fsnotify_marks together for cache efficiency. They are frequently
* accessed and rarely modified.
*/
void *s_fs_info; /* Filesystem private info */
/* Granularity of c/m/atime in ns (cannot be worse than a second) */
u32 s_time_gran;
/* Time limits for c/m/atime in seconds */
time64_t s_time_min;
time64_t s_time_max;
#ifdef CONFIG_FSNOTIFY
__u32 s_fsnotify_mask;
struct fsnotify_mark_connector __rcu *s_fsnotify_marks;
#endif
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID */
unsigned int s_max_links;
fmode_t s_mode;
/*
* The next field is for VFS *only*. No filesystems have any business
* even looking at it. You had been warned.
*/
struct mutex s_vfs_rename_mutex; /* Kludge */
/*
* Filesystem subtype. If non-empty the filesystem type field
* in /proc/mounts will be "type.subtype"
*/
const char *s_subtype;
const struct dentry_operations *s_d_op; /* default d_op for dentries */
/*
* Saved pool identifier for cleancache (-1 means none)
*/
int cleancache_poolid;
struct shrinker s_shrink; /* per-sb shrinker handle */
/* Number of inodes with nlink == 0 but still referenced */
atomic_long_t s_remove_count;
/*
* Number of inode/mount/sb objects that are being watched, note that
* inodes objects are currently double-accounted.
*/
atomic_long_t s_fsnotify_connectors;
/* Being remounted read-only */
int s_readonly_remount;
/* per-sb errseq_t for reporting writeback errors via syncfs */
errseq_t s_wb_err;
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
struct hlist_head s_pins;
/*
* Owning user namespace and default context in which to
* interpret filesystem uids, gids, quotas, device nodes,
* xattrs and security labels.
*/
struct user_namespace *s_user_ns;
/*
* The list_lru structure is essentially just a pointer to a table
* of per-node lru lists, each of which has its own spinlock.
* There is no need to put them into separate cachelines.
*/
struct list_lru s_dentry_lru;
struct list_lru s_inode_lru;
struct rcu_head rcu;
struct work_struct destroy_work;
struct mutex s_sync_lock; /* sync serialisation lock */
/*
* Indicates how deep in a filesystem stack this SB is
*/
int s_stack_depth;
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; /* all inodes */
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; /* writeback inodes */
}
结构体字段说明
字段 | 涵义 | 说明 |
s_list | 一个全局超级块链表 | 用于将超级块连接到一个全局的超级块列表,一个双循环链表。链表的第一个元素由super_blocks变量表示,而超级块结构的s_list域保存了指向链表中相邻元素的指针(参见fs/super.c中mount_nodev()函数) |
s_dev | 设备号,用于标识文件系统所在的设备 | 使用函数set_anon_super()生成,具体生成规则是使用id生成器ida_alloc_range()(参见fs/super.c中) |
s_blocksize_bits s_blocksize | 块大小 | 表示文件系统的块大小,指定了文件系统的块长度,一般是2的次幂,s_blocksize 的单位是字节,而s_blocksize_bits则是对前一个值取以2为底的对数 比如:s_blocksize为1024字节,那么s_blocksize_bits就是10 |
s_maxbytes | 文件系统支持文件最大大小 | |
s_type | 文件系统类型结构体 | 调用register_filesystem注册给VFS |
s_op | 超级块的操作表 | 就是超级块的操作函数集合,供VFS调用 |
dq_op | 磁盘配额disk_quota管理操作表 | 配额是文件系统的增值服务,可以限制用户对盘的使用量 |
s_qcop | 配额控制的操作函数 | |
s_export_op | 用于和NFS交互一个结构体,位于export.h | |
s_flags | 标志位,用于标识文件系统的特性 | 枚举值见fs.h SB_RDONLY SB_NOSUID SB_NODEV SB_NOEXEC SB_POSIXACL |
s_iflags | 内部标志位,用于标识文件系统的特性 | 枚举值见fs.h SB_I_CGROUPWB SB_I_NOEXEC SB_I_NODEV SB_I_STABLE_WRITES |
s_magic | 魔术数 | 枚举值见magic.h #define EROFS_SUPER_MAGIC_V1 0xE0F5E1E2 #define EXT2_SUPER_MAGIC 0xEF53 #define EXT3_SUPER_MAGIC 0xEF53 #define EXT4_SUPER_MAGIC 0xEF53 |
s_root | 根dentry | |
s_umount | 用于卸载操作的信号量 | |
s_count | 超级块的引用计数 | 引用计数,表示该super_block是否可以被释放 |
s_active | 表示被mount次数的 | |
s_xattr | 扩展属性指针 | |
s_roots | NFS备用根目录hash链表 | |
s_mounts | ||
s_bdev | block_device*类型 | 非常重要,存储了块设备信息: 在构造超级块和inode时,需要读取磁盘上超级块和inode信息,此时需要使用s_bdev |
s_bdi | 块设备的后备设备信息的指针 | |
s_mtd | 指向MTD(内存技术设备)的指针 | |
s_instances | 同一个文件系统超级块链表表头 | 在创建VFS超级块时,头插法插入到链表type->fs_supers |
s_quota_types | 枚举值:配额类型。一共有三种,用户配额,用户组配额,项目配额 | enum quota_type { USRQUOTA = 0, /* element used for user quotas */ GRPQUOTA = 1, /* element used for group quotas */ PRJQUOTA = 2, /* element used for project quotas */ }; |
s_dquot | 磁盘配额选项 | quota_info |
s_fs_info | 文件系统私有信息的指针(private_info) | 一般指向具体文件系统内存中超级块,比如ext4文件系统,指向ext4_sb_info,参见:ext4_fill_super()函数 |
s_time_gran | 文件系统中创建、修改和访问时间的时间粒度(纳秒) | |
s_time_min s_time_max | 文件系统中创建、修改和访问时间的时间限制(秒) | |
s_fsnotify_mask | ||
s_fsnotify_marks | ||
s_id | 一般是块设备名称 | fs/super.c strlcpy(s->s_id, s->s_type->name, sizeof(s->s_id)); |
s_uuid | 文件系统的UUID | |
s_max_links | 表示超级块中允许的最大硬链接数量 | |
s_mode | 超级块的模式,是文件打开模式的类型,用于指定文件的访问权限和属性 | FMODE_READ FMODE_WRITE FMODE_LSEEK FMODE_PREAD |
s_vfs_rename_mutex | rename操作的互斥锁,这是VFS层面上的互斥锁,用于保护rename操作 | The next field is for VFS *only*. |
s_subtype | 文件系统子类型 | |
s_d_op | 默认的目录项操作指针 | 链接到dentry的操作表,dentry_operations |
cleancache_poolid | 用于保存cleancache的池标识符。cleancache 是一种内核功能,用于在文件系统缓存中缓存文件的清洁副本,以提高文件系统性能 | #define CLEANCACHE_NO_POOL -1 #define CLEANCACHE_NO_BACKEND -2 #define CLEANCACHE_NO_BACKEND_SHARED -3 |
s_shrink | 指向超级块的收缩器处理程序的。收缩器是 Linux 内核中的一种机制,用于在系统内存紧张时回收不再使用的资源 | 原始声明:shrink.h 具体初始化参考: fs/super.c alloc_super()函数 |
s_remove_count | 用于表示具有 nlink == 0(硬链接计数为零)但仍被引用的索引节点数量 | |
s_fsnotify_connectors | 被监控的inode、挂载对象、超级块的数量 | |
s_readonly_remount | 只读重新挂载操作 | |
s_dio_done_wq | 异步处理队列,避免打断上下文 | |
s_pins | 用于存储与超级块相关联的固定引用(pin)对象 | 记录固定引用对象后,内存将不会被销毁 |
s_user_ns | 拥有用户命名空间和默认上下文,用于解释文件系统的uid、gid、配额、设备节点、xatrs和安全标签 | 通过将超级块与特定的用户命名空间关联起来,可以确保在不同的用户命名空间之间进行文件系统操作时,用户和组标识符以及相关的权限和安全属性得到正确的解释和处理 |
s_dentry_lru | 用于管理文件系统中的目录项(dentry)的LRU列表 | |
s_inode_lru | 用于管理文件系统中的索引节点(inode)的LRU列表 | |
rcu | 用于管理超级块的 RCU 机制,Read-Copy-Upate,通过延迟资源释放,保障快速读取 | |
destroy_work | 用于超级块销毁时,执行的工作队列 | |
s_sync_lock | 是一个用于同步操作的互斥锁,用于保护超级块的同步操作 | |
s_stack_depth | ||
s_inode_list_lock | 用于保护写回索引节点的自旋锁 | |
s_inodes | 是一个索引节点的链表头,用于存储与超级块相关的所有索引节点 | |
s_inode_wblist_lock | 用于保护写回索引节点的自旋锁 | |
s_inodes_wb | 是一个writeback inode链表头,用于存储需要进行写回(writeback)的索引节点。写回是将内存中已修改的数据同步到持久存储介质的过程 |
三、超级块相关链表
VFS超级块的相关链表域
链表字段 | 字段涵义 | 使用说明 |
s_list | 用于将超级块链接到一个全局的超级块列表中 | 1)超级块列表是一个双循环列表 2)s_list指向相邻元素指针 |
s_instances | 用于将超级块接入相同文件系统的超级块哈希列表 | 1)表头在file_system_type的fs_supers域 |
s_indoes | 用于存储与超级块相关的所有索引节点 | 每个inode通过i_sb_list域链接到这个链表 |
四、超级块操作super_operation
这里的超级块操作指的是VFS中具有公共信息的超级块操作表,这些操作存储在结构体super_operation中。
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*free_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
#ifdef CONFIG_QUOTA
ssize_t (*quota_read)(struct super_block *, int, char *, size_t, loff_t);
ssize_t (*quota_write)(struct super_block *, int, const char *, size_t, loff_t);
struct dquot **(*get_dquots)(struct inode *);
#endif
long (*nr_cached_objects)(struct super_block *,
struct shrink_control *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
函数 | 作用 | 使用说明 |
alloc_inode() | 用于分配一个inode | 通常alloc_inode分配实际使用的inode,同时包含了VFS的inode 比如:ext4_alloc_inode() |
free_inode() | 释放inode时调用 | 释放给定的 inode 资源 |
destroy_inode() | ||
dirty_inode() | 标记一个脏inode() | 1)当一个inode标记为脏时,表示当前inode数据被修改,但是还没有同步到介质 2)标记索引节点为脏的目的是记录脏的inode,确保在适当的时候将修改的元数据持久化到存储介质中,以保证数据的一致性和持久性 |
write_inode() | 当一个索引节点(inode)写入磁盘时被调用 | |
drop_inode() | 当超级块最后一个对索引节点的访问被释放时调用,同时保持索引节点的 i_lock 自旋锁 | |
evict_inode() | 从内存和底层存储介质移除 inode,并释放与之相关的资源,包括磁盘上的文件数据和元数据 | |
put_super() | 当文件系统需要被卸载时,VFS 会调用 put_super 方法来释放超级块 | |
sync_fs() | 当文件系统需要将与超级块关联的所有脏数据写回到存储介质时,VFS 会调用 sync_fs 方法 | 这通常发生在进行文件系统的同步操作时,以确保所有脏数据都被持久化到存储介质中 |
freeze_super() | 冻结超级块,阻止对文件系统的写操作 | |
thaw_super() | 解冻超级块,恢复可写状态 | |
freeze_fs() | LVM在创建只读快照时,会调用此函数 | 用于给定一个机会,将当前所有未写入日志,同步到物理介质,保持文件系统清洁 |
unfreeze_fs() | LVM调用,当快照创建完毕 | |
statfs() | 除法统计文件系统最新信息:块数、空间大小、文件数、可用空间等等 | |
remount_fs() | 当执行重新挂载时执行 | |
show_options() | 显示选项信息 |
上一篇:虚拟文件系统VFS中超级块、安装点、文件系统类型三者之间关系
参考资料:官方文档
内核版本:5.16.7
《006 Linux文件系统数据结构详解:超级块super_block》有2个想法