006 Linux文件系统数据结构详解:超级块super_block

前面几篇文章,介绍了内核虚拟文件系统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_rootsNFS备用根目录hash链表
s_mounts
s_bdevblock_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_mutexrename操作的互斥锁,这是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个想法

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注