Linux文件系统 struct super_block 结构体解析

本文围绕Linux文件系统展开,介绍了文件系统对象间的关系,着重阐述了super_block。包括其三种形式、在VFS中的重要性,详细说明了struct super_block的字段、操作函数,还提及了xattr_handler对扩展属性的处理,最后给出LKM示例及相关函数功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、文件系统对象

Linux文件系统对象之间的关系可以概括为文件系统类型、超级块、inode、dentry和vfsmount之间的关系。文件系统类型规定了某种类型文件系统的行为,它存在的主要目的是为了构造这种类型文件系统的实例,或者被称为超级块实例。

超级块反映了文件系统整体的控制信息,超级块以多种方式存在。对于基于磁盘的文件系统,它以特定格式存在于磁盘的固定区域(取决于文件系统类型),为磁盘上的超级块。在文件系统被装载时,其内容被读入内存,构建内存中的超级块。其中某些信息为各种类型的文件系统所共有,被提炼成VFS的超级块结构。如果某些文件系统不具有磁盘上超级块和内存中超级块形式,则它们必须负责从零构造出VFS的超级块。

inode反映了某个文件系统对象的一般元信息,dentry反映了某个文件系统对象在文件系统树中的位置。

超级块和inode有磁盘上、内存中以及VFS三种形式,其中VFS super_block和VFS inode是被提炼出来,为各种类型文件系统共有的,而磁盘上、内存中super_block和inode则为具体文件系统特有,根据实际情况,也可能根本不需要。

有关inode已经在这篇文章中说明:Linux文件系统 struct inode 结构体解析

Linux有一棵全局文件系统树,反映了Linux VFS对象之间的关系。文件系统要被用户空间使用,必须先装载到这棵树上。每一次装载被称为一个装载实例,某些文件系统只在内核中使用,也需要这样一个装载实例。每个文件系统装载实例有四个必备元素:vfsmount、超级块、根inode和根dentry。

接下来介绍超级块。

一个文件系统类型可能有多个超级块实例,而每个超级块实例又可以有多个装载实例。

二、super_block简介

超级块是整个文件系统的元数据的容器。对于基于磁盘的文件系统,超级块(确切地,是磁盘上超级块)是保存在磁盘设备上固定位置的一个或多个块,在装载该磁盘上的文件系统时,磁盘上超级块被读入内存,并根据它构造内存中超级块。其中一部分是各种文件系统共有的,被提取出来,即VFS超级块。在装载时,还根据文件系统类型设置超级块操作表。

VFS super_block是各种具体文件系统的超级块对象的抽象,对于具体的文件系统,另外有两种形式的super_block,一种是内存super_block,另一种是磁盘super_block。两者之间相互转换,用在不同的场合下。

因此有三种super_block:
(1)磁盘上具体的文件系统的super_block。
(2)内存中具体的文件系统的super_block。
(3)VFS super_block,超级块对象。

比如ext4文件系统:
磁盘上ext4文件系统的super_block:

// linux-5.4.18/fs/ext4/ext4.h

/*
 * 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 */
	......
}

超级块是文件系统的核心结构,保存了文件系统所有的特征数据。内核在装载文件系统时,最先看到的就是超级快的内容。
struct ext4_super_block 描述了超级块在硬盘上的结构和布局。

内存中ext4文件系统的super_block:

/*
 * 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 */
	......	
}

VFS super_block:

struct super_block {
	.......
	void			*s_fs_info;	/* Filesystem private info */
	......	
}

super_block结构描述了Linux VFS层使用的超级块对象。对于具体的文件系统,需要定义自己的超级块对象,由super_block结构中的s_fs_info指向之。即s_fs_info指向具体文件系统的超级块信息,比如ext4:ext4_sb_info。

内存中ext4文件系统的super_block 和 VFS super_block 之间的关系:

static inline struct ext4_sb_info *EXT4_SB(struct super_block *sb)
{
	return sb->s_fs_info;
}

如下图所示:
在这里插入图片描述

static const struct super_operations ext4_sops = {
	.alloc_inode	= ext4_alloc_inode,
	.free_inode	= ext4_free_in_core_inode,
	.destroy_inode	= ext4_destroy_inode,
	.write_inode	= ext4_write_inode,
	.dirty_inode	= ext4_dirty_inode,
	.drop_inode	= ext4_drop_inode,
	.evict_inode	= ext4_evict_inode,
	.put_super	= ext4_put_super,
	.sync_fs	= ext4_sync_fs,
	.freeze_fs	= ext4_freeze,
	.unfreeze_fs	= ext4_unfreeze,
	.statfs		= ext4_statfs,
	.remount_fs	= ext4_remount,
	.show_options	= ext4_show_options,
#ifdef CONFIG_QUOTA
	.quota_read	= ext4_quota_read,
	.quota_write	= ext4_quota_write,
	.get_dquots	= ext4_get_dquots,
#endif
	.bdev_try_to_free_page = bdev_try_to_free_page,
};

三、struct super_block

虚拟文件系统(VFS)是Linux内核中的一个子系统,它提供了一个抽象层,使得不同的文件系统能够以一致的方式与内核进行交互。VFS定义了一组通用的数据结构和接口,用于处理文件系统的挂载、文件访问、路径解析、文件操作等操作。

struct super_block是VFS中一个非常重要的数据结构,它代表一个文件系统的超级块。超级块是文件系统的核心数据结构之一,存储着文件系统的元数据和配置信息。每当一个文件系统被挂载到系统上时,都会创建一个对应的struct super_block对象,用来描述该文件系统的特性和状态。
通过struct super_block结构定义了通用的超级块布局。每个文件系统在挂载过程中需要实例化一个该结构的对象,并填充特定的超级块细节。

通过将文件系统的具体实现细节封装在struct super_block中,VFS层可以统一处理不同文件系统类型的操作,使得上层应用程序和系统调用可以以一致的方式与各种文件系统进行交互,而无需关心底层文件系统的差异。

struct super_block中包含了许多成员变量,用于存储与文件系统相关的信息。例如,它可以包含文件系统的类型、挂载点、超级块的状态、文件系统的特定操作函数指针等。这些信息与具体的文件系统类型有关,每个文件系统需要根据自身的特性来填充这些成员变量。

通过维护一个struct super_block对象的列表,VFS可以跟踪管理所有已挂载的文件系统。这个列表允许内核对不同文件系统类型的挂载点进行管理,执行文件系统的操作,以及在需要卸载文件系统时进行清理和释放资源。

对于伪文件系统而言,它们没有持久的超级块结构,超级块是根据需要动态生成的。伪文件系统(如procfs或sysfs)提供对内核数据结构的虚拟接口,而不对应像传统文件系统那样的物理存储设备。

3.1 字段说明

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;

	......

	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 */

	......

	/*
	 * 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;

	/* Pending fsnotify inode refs */
	atomic_long_t s_fsnotify_inode_refs;

	/* Being remounted read-only */
	int s_readonly_remount;

	/* 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 */
} __randomize_layout;

s_list:一个链表头,用于将超级块连接到一个全局的超级块列表中。所有的超级块对象被链接到一个循环双链表。链表的第一个元素由super_blocks变量表示,而超级块结构的s_list域保存了指向链表中相邻元素的指针。

// linux-5.4.18/fs/super.c

static LIST_HEAD(super_blocks);

遍历超级块链表:

list_for_each_entry(sb, &super_blocks, s_list)

s_dev:设备号,用于标识文件系统所在的设备。
super_block的s_dev和s_bdev域是基于磁盘的文件系统所特有的。s_dev记录了存储超级块信息的块设备编号,而s_bdev则为指向块设备描述符的指针。

s_blocksize_bits、s_blocksize:用于表示文件系统的块大小。指定了文件系统的块长度(这对于硬盘上的数据组织特别有用)。本质上,这两个变量以不同的方式表示了相同信息。s_blocksize
的单位是字节,而s_blocksize_bits则是对前一个值取以2为底的对数。

s_maxbytes:保存了文件系统可以处理的最大文件长度。

s_type:指向文件系统类型的指针。指向file_system_type实例,其中保存了与文件系统有关的一般类型的信息。

static struct file_system_type ext4_fs_type = {
	.owner		= THIS_MODULE,
	.name		= "ext4",
	.mount		= ext4_mount,
	.kill_sb	= kill_block_super,
	.fs_flags	= FS_REQUIRES_DEV,
};
MODULE_ALIAS_FS("ext4");

s_op:指向超级块操作函数的指针,用于定义文件系统的行为。请参考:3.3 super_operations。

dq_op:用于指定与磁盘配额(disk quota)相关的操作。磁盘配额是一种文件系统功能,用于限制用户或组在文件系统上使用的磁盘空间量。
s_qcop:,表示配额控制(quota control)操作的函数集合。配额控制操作用于管理和操作与磁盘配额相关的信息,比如设置配额限制、获取配额使用情况等。

磁盘配额(disk quota)是一种文件系统的功能,用于限制用户或组在文件系统上使用的磁盘空间量。它可以帮助系统管理员对文件系统上的用户或组进行资源管理和配额控制。
磁盘配额的主要目的是防止某个用户或组占用过多的磁盘空间,从而保持文件系统的可用空间和性能。通过设置磁盘配额,管理员可以限制用户或组的磁盘使用量,防止他们滥用系统资源或占用过多的存储空间。
磁盘配额通常基于用户或组进行管理。管理员可以为每个用户或组设置最大的磁盘空间限制。当用户或组的磁盘使用量达到或超过配额限制时,文件系统会拒绝进一步的写入操作,防止其继续使用磁盘空间。

s_export_op:struct export_operations 是一个在 Linux 内核中定义的结构,用于实现文件系统与 NFS 服务器守护进程(nfsd)之间的通信。NFS 是一种网络文件系统协议,允许远程主机通过网络访问共享文件。
struct export_operations 结构包含了一组函数指针,这些函数用于处理 NFS 服务器对文件系统的导出操作。当 NFS 客户端请求访问共享文件时,nfsd 会调用与 struct export_operations 相关联的函数来处理这些请求。

s_flags:标志位,用于标识文件系统的特性。

s_iflags:内部标志位,用于标识文件系统的内部特性。

s_magic:魔术数,用于识别文件系统类型。

s_root:指向根目录的指针。将超级块与全局根目录的dentry项关联起来。处理文件系统对象的代码经常需要检查文件系统是否已经装载,而s_root可用于该目的。如果它为NULL,则该文件系统是一个伪文件系统,只在内核内部可见。否则,该文件系统在用户空间中是可见的。

只有通常可见的文件系统的超级块,才指向/(根)目录的dentry实例。具有特殊功能、不出现在通常的目录层次结构中的文件系统(例如,管道或套接字文件系统),指向专门的项,不能通过普通的文件命令访问。

s_umount:读写信号量,用于同步卸载操作。

s_count:超级块的引用计数。

s_active:超级块的活动引用计数。super_block的引用计数有两个,一个是s_count,一个是s_active。s_count是真正的引用计数,表示该super_block是否可以被释放。s_active表示被mount了多少次。

s_security:指向超级块安全相关数据结构的指针,与LSM有关。

s_xattr:指向超级块扩展属性结构的指针。

s_roots:用于NFS的备用根目录项的哈希链表。

s_mounts:链表头,用于存储与该超级块相关的挂载点信息。

s_bdev:指向块设备的指针。
super_block的s_dev和s_bdev域是基于磁盘的文件系统所特有的。s_dev记录了存储超级块信息的块设备编号,而s_bdev则为指向块设备描述符的指针。

s_bdi:指向块设备的后备设备信息的指针。

s_mtd:指向MTD(内存技术设备)的指针。

s_instances:哈希链表头,用于存储在多个超级块实例之间共享的实例信息。
每个超级块对象被链接到它所属的文件系统类型的超级块实例链表中。它在这个链表中的“连接件”是s_instances。

struct super_block *s;
INIT_HLIST_NODE(&s->s_instances);
struct super_block *s;
hlist_add_head(&s->s_instances, &s->s_type->fs_supers);
struct file_system_type *type;
struct super_block *sb;

hlist_for_each_entry(sb, &type->fs_supers, s_instances)

s_quota_types:支持的磁盘配额类型的位掩码。

s_dquot:磁盘配额的特定选项。

s_fs_info:文件系统私有信息的指针。
s_fs_info域指向属于具体文件系统的内存中超级块结构。例如,我们看到,如果是ext4文件系统,这个域指向ext4_sb_info结构。
一般来说,s_fs_info域所指向的数据为效率考虑而从磁盘上复制到内存中的信息。基于磁盘的文件系统在分配或释放磁盘块时会访问并更新分配位图。VFS允许这些文件系统直接在内存中,即超级块的s_fs_info域上,进行操作,而无须访问磁盘。

s_time_gran:文件系统中创建、修改和访问时间的时间粒度(纳秒)。
s_time_min、s_time_max:文件系统中创建、修改和访问时间的时间限制(秒)。

s_fsnotify_mask:文件系统通知的掩码。
s_fsnotify_marks:文件系统通知标记的哈希链表。

s_id:对于磁盘文件系统,为块设备名。否在是文件系统类型名。

s_uuid:文件系统的UUID。

s_max_links:表示超级块中允许的最大硬链接数量。硬链接是文件系统中多个路径指向同一个索引节点的链接。

s_mode:表示超级块的模式。fmode_t 是文件打开模式的类型,用于指定文件的访问权限和属性。

s_vfs_rename_mutex:这是一个互斥锁(mutex),用于在虚拟文件系统(VFS)层面上提供重命名操作的互斥访问。它是一个用于内部实现的锁定机制,用于保护重命名操作的并发访问。注释中指出,文件系统不应该直接使用或访问这个字段。

s_subtype:表示文件系统的子类型。如果非空,/proc/mounts 中的文件系统类型字段将显示为 “type.subtype” 的形式,用于进一步区分不同的文件系统类型。

s_d_op:表示默认的目录项操作(dentry operations)。目录项操作是文件系统用于操作和管理目录项(dentry)的函数集合。

cleancache_poolid:用于保存 cleancache 的池标识符。cleancache 是一种内核功能,用于在文件系统缓存中缓存文件的清洁副本,以提高文件系统性能。cleancache_poolid 用于保存与超级块关联的 cleancache 池的标识符。如果值为 -1,则表示没有关联的 cleancache 池。

s_shrink:用于表示每个超级块的收缩器(shrinker)处理程序的句柄。收缩器是 Linux 内核中的一种机制,用于在系统内存紧张时回收不再使用的资源。s_shrink 是用于超级块的收缩器处理程序的句柄,可以执行与超级块相关的资源回收操作。

s_remove_count:用于表示具有 nlink == 0(硬链接计数为零)但仍被引用的索引节点数量。索引节点是文件系统中的文件和目录的元数据表示。当 nlink 计数为零时,表示没有硬链接引用该索引节点,但仍可能存在其他引用(如打开的文件描述符或目录句柄)保持着对该索引节点的引用。s_remove_count 用于跟踪具有 nlink == 0 但仍被引用的索引节点数量。
使用原子长整型可以确保对 s_remove_count 的并发访问是线程安全的,因为它提供了原子性的读取和写入操作。跟踪具有 nlink == 0 但仍被引用的索引节点数量可以帮助在文件系统操作期间识别和处理悬挂的索引节点,以确保文件系统的一致性和正确性。

s_fsnotify_inode_refs:待处理的fsnotify索引节点引用数。

s_readonly_remount:正在进行的只读重新挂载操作。

s_dio_done_wq:指向工作队列结构体(struct workqueue_struct)的指针。在 Linux 内核中,AIO(Asynchronous I/O)操作可能会从中断上下文中延迟处理,因此需要使用工作队列来处理这些延迟的 AIO 完成事件。s_dio_done_wq 指针指向用于处理这些 AIO 完成事件的工作队列。

s_pins:是一个哈希列表头(hlist_head),用于存储与超级块相关联的固定引用(pin)对象。在 Linux 内核中,文件系统可以通过将引用固定在内存中来确保文件系统对象不被销毁。s_pins 哈希列表用于记录这些固定引用对象,以便在需要时对其进行管理和访问。

s_user_ns:指向用户命名空间(struct user_namespace)的指针。在 Linux 内核中,用户命名空间用于隔离不同进程的用户标识符(UID)和组标识符(GID),以及相关的权限和资源限制。
s_user_ns 成员用于指定超级块所属的用户命名空间。用户命名空间定义了一个特定的上下文,用于解释文件系统中的用户标识符、组标识符、配额、设备节点、扩展属性(xattrs)和安全标签等。
通过将超级块与特定的用户命名空间关联起来,可以确保在不同的用户命名空间之间进行文件系统操作时,用户和组标识符以及相关的权限和安全属性得到正确的解释和处理。这种隔离和解释上下文的机制使得不同用户命名空间中的进程能够独立地访问和操作文件系统,而不会相互干扰或产生冲突。

s_dentry_lru:用于管理文件系统中的目录项(dentry)的LRU(最近最少使用)列表。LRU列表是一种用于缓存管理的策略,其中最近最少使用的项被放置在列表的末尾,而最常使用的项则位于列表的前面。s_dentry_lru 用于存储和管理目录项的 LRU 列表,并具有自己的自旋锁(spinlock)用于同步访问。
s_inode_lru:用于管理文件系统中的索引节点(inode)的LRU列表。索引节点是文件系统中的文件和目录的元数据表示。s_inode_lru 用于存储和管理索引节点的 LRU 列表,并具有自己的自旋锁用于同步访问。

/**
 *	alloc_super	-	create new superblock
 *	@type:	filesystem type superblock should belong to
 *	@flags: the mount flags
 *	@user_ns: User namespace for the super_block
 *
 *	Allocates and initializes a new &struct super_block.  alloc_super()
 *	returns a pointer new superblock or %NULL if allocation had failed.
 */
static struct super_block *alloc_super(struct file_system_type *type, int flags,
				       struct user_namespace *user_ns)
{
	......
	list_lru_init_memcg(&s->s_dentry_lru, &s->s_shrink);
	list_lru_init_memcg(&s->s_inode_lru, &s->s_shrink);
	......
}

rcu:用于管理超级块的 RCU 机制。RCU 是一种用于读者-写者并发控制的技术,它允许在没有显式锁的情况下对共享数据进行并发读取。
destroy_work:用于在超级块销毁时执行相关的工作。destroy_work 可以包含要在超级块销毁时执行的回调函数或操作。

s_sync_lock: 是一个用于同步操作的互斥锁,用于保护超级块的同步操作,例如同步写入文件系统元数据。

s_stack_depth:表示该超级块在文件系统堆栈中的深度。文件系统堆栈是指文件系统的层次结构,其中一个文件系统可以被另一个文件系统所包含。s_stack_depth 用于指示当前超级块在文件系统堆栈中的深度,以便进行相关的操作和管理。

s_inode_list_lock:用于保护s_inodes的自旋锁。
s_inodes:是一个索引节点的链表头,用于存储与超级块相关的所有索引节点。每个索引节点都代表文件系统中的一个文件或目录,每个inode通过i_sb_list链入到这个链表。

struct inode {
	......
	struct list_head	i_sb_list;	
	......	
}

s_inode_wblist_lock:用于保护写回索引节点的自旋锁。
s_inodes_wb: 是一个writeback inode链表头,用于存储需要进行写回(writeback)的索引节点。写回是将内存中已修改的数据同步到持久存储介质的过程。

struct inode {
	......
	struct list_head	i_wb_list;
	......
}

s_inodes包括了s_inodes_wb,s_inodes_wb只是s_inodes的一部分inode,当需要操作写回索引节点时,只需要遍历s_inodes_wb链表即可,而不需要遍历s_inodes。
即在同步内存内容与底层存储介质上的数据时,使用该链表会更加高效。该链表只包含已经修改的inode,因此回写数据时并不需要扫描全部inode。

/**
 *	alloc_super	-	create new superblock
 *	@type:	filesystem type superblock should belong to
 *	@flags: the mount flags
 *	@user_ns: User namespace for the super_block
 *
 *	Allocates and initializes a new &struct super_block.  alloc_super()
 *	returns a pointer new superblock or %NULL if allocation had failed.
 */
static struct super_block *alloc_super(struct file_system_type *type, int flags,
				       struct user_namespace *user_ns)
{
	......
	INIT_LIST_HEAD(&s->s_inodes);
	spin_lock_init(&s->s_inode_list_lock);
	INIT_LIST_HEAD(&s->s_inodes_wb);
	spin_lock_init(&s->s_inode_wblist_lock);
   ......
}

3.2 super_block 链表

超级块对象存在于两个链表中:s_list 和 s_instances。

所有的超级块对象被链接到一个循环双链表。链表的第一个元素由super_blocks变量表示,而超级块结构的s_list域保存了指向链表中相邻元素的指针。

每个超级块对象还被链接到它所属的文件系统类型的超级块实例链表中。它在这个链表中的“连接件”是s_instances。

(1)

struct list_head	s_list;		/* Keep this first */

s_list:一个链表头,用于将超级块连接到一个全局的超级块列表中。所有的超级块对象被链接到一个循环双链表。链表的第一个元素由super_blocks变量表示,而超级块结构的s_list域保存了指向链表中相邻元素的指针。

// linux-5.4.18/fs/super.c

static LIST_HEAD(super_blocks);

遍历超级块链表:

list_for_each_entry(sb, &super_blocks, s_list)

(2)

struct hlist_node	s_instances;

s_instances:哈希链表头,用于存储在多个超级块实例之间共享的实例信息。
每个超级块对象被链接到它所属的文件系统类型的超级块实例链表中。它在这个链表中的“连接件”是s_instances。

struct super_block *s;
INIT_HLIST_NODE(&s->s_instances);
struct super_block *s;
hlist_add_head(&s->s_instances, &s->s_type->fs_supers);
struct file_system_type *type;
struct super_block *sb;

hlist_for_each_entry(sb, &type->fs_supers, s_instances)

(3)

struct list_head	s_inodes;	/* all inodes */

是一个索引节点的链表头,用于存储与超级块相关的所有索引节点。每个索引节点都代表文件系统中的一个文件或目录,每个inode通过i_sb_list链入到这个链表。

struct inode {
	......
	struct list_head	i_sb_list;	
	......	
}

struct super_block 是链表头,struct inode是链表节点。

(4)

struct list_head	s_inodes_wb;	/* writeback inodes */

s_inodes_wb: 是一个writeback inode链表头,用于存储需要进行写回(writeback)的索引节点。写回是将内存中已修改的数据同步到持久存储介质的过程。

struct super_block 是链表头,struct inode是链表节点。

s_inodes包括了s_inodes_wb,s_inodes_wb只是s_inodes的一部分inode,当需要操作写回索引节点时,只需要遍历s_inodes_wb链表即可,而不需要遍历s_inodes。
即在同步内存内容与底层存储介质上的数据时,使用该链表会更加高效。该链表只包含已经修改的inode,因此回写数据时并不需要扫描全部inode。

3.3 super_operations

const struct super_operations	*s_op;

s_op指向一个包含了函数指针的结构,该结构按熟悉的VFS方式,提供了一个一般性的接口,用
于处理超级块相关操作。操作的实现必须由底层文件系统的代码提供。

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
	int (*bdev_try_to_free_page)(struct super_block*, struct page*, gfp_t);
	long (*nr_cached_objects)(struct super_block *,
				  struct shrink_control *);
	long (*free_cached_objects)(struct super_block *,
				    struct shrink_control *);
};

下面是一些主要的函数指针及其功能:
alloc_inode:alloc_inode 方法由 alloc_inode() 调用,用于为 struct inode 分配内存并进行初始化。如果没有定义此函数,将只分配一个简单的 struct inode。通常,alloc_inode 用于分配一个包含嵌入的 struct inode 的较大结构。
该方法的作用是在文件系统需要创建新的 inode 时,为其分配内存并进行初始化。它允许文件系统实现者自定义 inode 结构的分配过程,以满足特定的需求。通常情况下,分配的结构会包含除了 struct inode 以外的其他额外信息和数据。
通过实现 alloc_inode 方法,可以在分配 inode 时执行一些额外的操作,例如初始化自定义结构体的字段、设置默认属性或分配其他相关资源。这样,文件系统可以根据需要进行灵活的内存分配和初始化操作,以满足特定的文件系统设计和需求。

destroy_inode:用于释放为 struct inode 分配的资源。只有在定义了 ->alloc_inode 函数并且该函数执行了任何初始化或资源分配时,才需要使用这个方法。
当调用 destroy_inode 方法时,它会撤销 ->alloc_inode 函数执行的任何操作或分配。这确保了在销毁 inode 时,与 struct inode 相关的所有资源都能得到正确释放和清理

free_inode:释放给定的 inode 结构体。

dirty_inode:dirty_inode 方法是在虚拟文件系统(VFS)中当一个索引节点(inode)被标记为脏(dirty)时被调用的方法。这个方法特指标记索引节点本身为脏,而不是它的数据。如果更新需要通过 fdatasync() 持久化到磁盘,那么在标志参数(flags)中会设置 I_DIRTY_DATASYNC。如果启用了惰性时间(lazytime),并且自上次 dirty_inode 调用以来 struct inode 的时间信息已更新,则在标志参数中会设置 I_DIRTY_TIME。
当一个索引节点被标记为脏时,表示该节点的元数据已被修改,但尚未同步到存储介质中。这可能是因为文件的权限、所有权或其他元数据发生了变化。标记索引节点为脏的目的是确保在适当的时候将修改的元数据持久化到存储介质中,以保证数据的一致性和持久性。
dirty_inode 方法的作用是处理索引节点被标记为脏时的相关操作。根据传入的标志参数(flags),可以执行相应的操作。如果标志参数中设置了 I_DIRTY_DATASYNC,表示更新需要通过 fdatasync() 进行持久化到磁盘。如果标志参数中设置了 I_DIRTY_TIME,表示启用了惰性时间,并且自上次 dirty_inode 调用以来 struct inode 的时间信息已更新。
通过调用 dirty_inode 方法,VFS 可以在索引节点被标记为脏时进行必要的处理,保证更新的元数据在适当的时候被持久化,并确保文件系统的一致性和可靠性。

write_inode:write_inode 方法在虚拟文件系统(VFS)需要将一个索引节点(inode)写入磁盘时被调用。第二个参数指示写入操作是否应该是同步的,但并非所有的文件系统都会检查这个标志。
当文件系统需要将一个索引节点的数据或元数据写回磁盘时,它会调用 write_inode 方法。这通常发生在索引节点被标记为脏(dirty)后,或者在文件系统进行同步(sync)操作时。写入操作的目的是将索引节点的更新持久化,以确保数据的一致性和持久性。
将指定的索引节点对象的内容更新一个文件系统的索引节点。索引节点对象的 i_ino 字段标识所涉及磁盘文件系统的索引节点。

struct inode {
	......
	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	......
}

drop_inode:drop_inode 方法在最后一个对索引节点的访问被释放时调用,同时保持索引节点的 i_lock 自旋锁。
这个方法的实现应该是 NULL(符合常规的 UNIX 文件系统语义),或者是 “generic_delete_inode”(适用于不希望缓存索引节点的文件系统,使得 “delete_inode” 方法无论 i_nlink 的值如何都会被调用)。
“generic_delete_inode()” 的行为类似于以前在 put_inode() 中使用 “force_delete” 的做法,但它避免了 “force_delete()” 方法所存在的竞争条件。
当最后一个访问索引节点的引用被释放时,文件系统需要对索引节点进行清理和释放相关资源。这个过程需要在 i_lock 自旋锁的保护下进行,以确保并发访问的一致性。

evict_inode:从内存和底层存储介质移除 inode,并释放与之相关的资源,包括磁盘上的文件数据和元数据。

put_super:put_super 方法在虚拟文件系统(VFS)希望释放超级块(superblock)(即卸载文件系统)时被调用。该方法在持有超级块锁的情况下被调用。
当文件系统需要被卸载时,VFS 会调用 put_super 方法来释放超级块。超级块是文件系统的关键结构之一,它包含了文件系统的元数据和状态信息。在卸载文件系统之前,需要确保超级块的资源得到正确释放。

sync_fs:sync_fs 方法在虚拟文件系统(VFS)需要写出与超级块相关的所有脏数据时被调用。第二个参数指示该方法是否应等待写出操作完成。这是一个可选的方法。
当文件系统需要将与超级块关联的所有脏数据写回到存储介质时,VFS 会调用 sync_fs 方法。这通常发生在进行文件系统的同步操作时,以确保所有脏数据都被持久化到存储介质中。
sync_fs 方法的主要作用是执行与同步文件系统相关的操作。

freeze_super:冻结超级块,阻止对文件系统的写操作。

freeze_fs:freeze_fs 方法在虚拟文件系统(VFS)锁定文件系统并强制其进入一致状态时被调用。该方法目前由逻辑卷管理器(Logical Volume Manager,LVM)使用。
当需要对文件系统进行操作(例如扩展或缩减卷组)时,LVM 可能需要确保文件系统处于一致的状态,以避免数据损坏或丢失。为此,LVM 会调用 freeze_fs 方法来锁定文件系统并将其置于一致状态。
freeze_fs 方法的主要作用是执行与锁定文件系统和强制一致状态相关的操作。

thaw_super:解除冻结状态的超级块。

unfreeze_fs:unfreeze_fs 方法在虚拟文件系统(VFS)解锁文件系统并使其再次可写时被调用。
当文件系统完成一致状态操作(如锁定或冻结)后,需要将文件系统解锁以使其恢复可写状态。在这种情况下,VFS 会调用 unfreeze_fs 方法。
unfreeze_fs 方法的主要作用是执行与解锁文件系统和恢复可写状态相关的操作。

对于ext4文件系统:

static const struct super_operations ext4_sops = {
	.alloc_inode	= ext4_alloc_inode,
	.free_inode	= ext4_free_in_core_inode,
	.destroy_inode	= ext4_destroy_inode,
	.write_inode	= ext4_write_inode,
	.dirty_inode	= ext4_dirty_inode,
	.drop_inode	= ext4_drop_inode,
	.evict_inode	= ext4_evict_inode,
	.put_super	= ext4_put_super,
	.sync_fs	= ext4_sync_fs,
	.freeze_fs	= ext4_freeze,
	.unfreeze_fs	= ext4_unfreeze,
	.statfs		= ext4_statfs,
	.remount_fs	= ext4_remount,
	.show_options	= ext4_show_options,
#ifdef CONFIG_QUOTA
	.quota_read	= ext4_quota_read,
	.quota_write	= ext4_quota_write,
	.get_dquots	= ext4_get_dquots,
#endif
	.bdev_try_to_free_page = bdev_try_to_free_page,
};
# cat /boot/config-5.4.18-74-generic | grep CONFIG_QUOTA
CONFIG_QUOTA=y

CONFIG_QUOTA文件系统配置用于配额管理的函数指针。

3.4 xattr_handler

// linux-5.4.18/include/linux\fs.h

struct super_block {
	......
	const struct xattr_handler **s_xattr;
	......
}

struct xattr_handlers 是在支持扩展属性的文件系统中用于处理扩展属性操作的结构体。扩展属性允许在文件系统中存储与文件或目录相关的附加元数据,以便存储额外的信息。

这个结构体的设计目的是为了在文件系统中提供一种灵活的方式来处理扩展属性的操作。通过定义适当的处理程序,文件系统可以根据属性的名称或名称前缀来执行不同的操作。

// linux-5.4.18/include/linux/xattr.h

/*
 * struct xattr_handler: When @name is set, match attributes with exactly that
 * name.  When @prefix is set instead, match attributes with that prefix and
 * with a non-empty suffix.
 */
struct xattr_handler {
	const char *name;
	const char *prefix;
	int flags;      /* fs private flags */
	bool (*list)(struct dentry *dentry);
	int (*get)(const struct xattr_handler *, struct dentry *dentry,
		   struct inode *inode, const char *name, void *buffer,
		   size_t size);
	int (*set)(const struct xattr_handler *, struct dentry *dentry,
		   struct inode *inode, const char *name, const void *buffer,
		   size_t size, int flags);
};

struct xattr_handlers 包含以下字段:
(1)name:指定处理程序匹配具有指定名称(例如 “system.posix_acl_access”)的属性。在这种情况下,prefix 字段必须设置为 NULL。

(2)prefix:指定处理程序匹配具有指定名称前缀(例如 “user.”)的所有属性。在这种情况下,name 字段必须设置为 NULL。

(3)list:确定是否应列出与此 xattr 处理程序匹配的特定目录项的属性。一些 listxattr 实现(如 generic_listxattr)会使用它。

(4)get:由虚拟文件系统(VFS)调用,以获取特定扩展属性的值。此方法由 getxattr() 系统调用调用。

(5)set:由虚拟文件系统调用,以设置特定扩展属性的值。当新值为 NULL 时,用于删除特定扩展属性。该方法由 setxattr() 和 removexattr() 系统调用调用。

例如,假设文件系统支持扩展属性,并且定义了以下两个处理程序:
处理程序 A:名称为 “system.posix_acl_access”,prefix 字段为 NULL。这意味着该处理程序匹配具有名称 “system.posix_acl_access” 的属性。
处理程序 B:名称为 NULL,prefix 为 “user.”。这意味着该处理程序匹配所有以 “user.” 为前缀的属性。

当应用程序调用 getxattr 系统调用来获取文件的扩展属性时,VFS 会检查文件系统的 struct xattr_handlers。根据属性的名称或名称前缀,VFS 将选择相应的处理程序来处理请求。

如果应用程序请求获取属性 “system.posix_acl_access” 的值,VFS 将调用处理程序 A 的 get 方法来获取该属性的值。如果应用程序请求获取以 “user.” 为前缀的属性的值,VFS 将调用处理程序 B 的 get 方法。

类似地,当应用程序调用 setxattr 系统调用来设置文件的扩展属性时,VFS 会根据属性的名称或名称前缀选择适当的处理程序,然后调用相应的 set 方法来设置属性的值。

如果文件系统不支持扩展属性或没有与指定的属性名称匹配的处理程序,那么相关的系统调用(如 getxattr、setxattr 等)将返回错误码 -EOPNOTSUPP,表示不支持该操作。

通过使用 struct xattr_handlers,文件系统可以实现对扩展属性的管理和操作,提供了一种灵活且可扩展的方式来处理与文件和目录关联的附加元数据。

四、LKM dmeo

(1)

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/fs_struct.h>

#include <linux/sched.h>

static int __init hello_init(void)
{
    struct fs_struct *fs;
    struct path pwd;

    struct super_block *sb;
    struct super_block *sb1;

    struct block_device	*s_bdev;
    dev_t s_dev;

    fs = current->fs;
    get_fs_pwd(fs, &pwd);
    /* The root of the dentry tree */
    //获取当前文件所在的super_block描述符信息
    sb = pwd.dentry->d_sb;
    //sb = current->fs->pwd.dentry->d_sb;
	
	//显示当前文件对应的dev number
    s_dev = sb->s_dev;
    printk("s_dev = %d\n", s_dev);

	//获取当前文件对应的block_device描述符信息
    s_bdev = sb->s_bdev;
    sb1 = get_super(s_bdev);
    //显示获取结果的dev number值
    printk("s_dev = %d\n", sb1->s_dev);

    return -1;
}
 
 
module_init(hello_init);
 
MODULE_LICENSE("GPL");
# dmesg -c
[75158.684930] s_dev = 265289733
[75158.684937] s_dev = 265289733

super_block的s_dev和s_bdev域是基于磁盘的文件系统所特有的。s_dev记录了存储超级块信息的块设备编号,而s_bdev则为指向块设备描述符的指针。

首先打印出当前目录的super_block的块设备编号,然后将这个结构体中的block_device当作参数传入了get_super函数中,应该得到的是当前目录的super_block结构体,而第二次打印出来的块设备编号与第一次相同,说明函数这两个super_block结构体表示的是同一个super_block。

其中调用了一个函数get_super:

static struct super_block *__get_super(struct block_device *bdev, bool excl)
{
	struct super_block *sb;

	if (!bdev)
		return NULL;

	spin_lock(&sb_lock);
rescan:
	list_for_each_entry(sb, &super_blocks, s_list) {
		if (hlist_unhashed(&sb->s_instances))
			continue;
		if (sb->s_bdev == bdev) {
			sb->s_count++;
			spin_unlock(&sb_lock);
			if (!excl)
				down_read(&sb->s_umount);
			else
				down_write(&sb->s_umount);
			/* still alive? */
			if (sb->s_root && (sb->s_flags & SB_BORN))
				return sb;
			if (!excl)
				up_read(&sb->s_umount);
			else
				up_write(&sb->s_umount);
			/* nope, got unmounted */
			spin_lock(&sb_lock);
			__put_super(sb);
			goto rescan;
		}
	}
	spin_unlock(&sb_lock);
	return NULL;
}

/**
 *	get_super - get the superblock of a device
 *	@bdev: device to get the superblock for
 *
 *	Scans the superblock list and finds the superblock of the file system
 *	mounted on the device given. %NULL is returned if no match is found.
 */
struct super_block *get_super(struct block_device *bdev)
{
	return __get_super(bdev, false);
}
EXPORT_SYMBOL(get_super);

__get_super() 函数用于在超级块链表中查找与给定设备 (bdev) 相关联的超级块,并返回找到的超级块指针。如果找到匹配的超级块,会对其引用计数进行增加,并根据 excl 参数确定是否采用互斥或共享方式对超级块进行保护。最后,如果超级块仍然存在且处于有效状态,则返回该超级块指针。

get_super() 函数是对 __get_super() 的封装,它使用 false 作为 excl 参数调用 __get_super(),以获取共享访问的超级块指针。该函数将供其他模块使用,因此使用 EXPORT_SYMBOL 宏将其标记为可导出的符号。

get_super的目的是实现获取与给定设备关联的超级块的功能。调用 get_super() 函数并传入相应的 block_device 结构体指针,即可获取相应的超级块指针。

参考资料

Linux 5.4.18

https://static.lwn.net/kerneldoc/filesystems/vfs.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值