文件系统之super_block
注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.21 super_blocksuper_block,即超级块代表的是一种文件系统类型,比如ext3、ext4都有对应的super_block结构体。一台机器可以有多块硬盘,一个硬盘可以有多个分区,每个分区都有自己的文件系统类型,可以是一样的,也可以不一样。我们来了解下超级块的主要成员变量。1.1 s
注:本文分析基于linux-4.18.0-193.14.2.el8_2内核版本,即CentOS 8.2
1 super_block
super_block,即超级块代表的是一种文件系统类型,比如ext3、ext4都有对应的super_block结构体。一台机器可以有多块硬盘,一个硬盘可以有多个分区,每个分区都有自己的文件系统类型,可以是一样的,也可以不一样。我们来了解下超级块的主要成员变量。
2 super_block主要成员变量
struct super_block {
struct list_head s_list; //通过该变量链接到超级块全局链表super_blocks上
dev_t s_dev; //该文件系统对应的块设备标识符
unsigned char s_blocksize_bits;
unsigned long s_blocksize; //该文件系统的block size
loff_t s_maxbytes; //文件系统支持的最大文件
struct file_system_type *s_type; //文件系统类型,比如ext3、ext4
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; //文件系统的mount标记
unsigned long s_iflags; /* internal SB_I_* flags */
unsigned long s_magic; //该文件系统类型的魔术字
struct dentry *s_root; //全局根目录的dentry项
...
struct block_device *s_bdev; //对应的块设备
struct backing_dev_info *s_bdi; //超级块对应的BDI设备
struct mtd_info *s_mtd;
//通过该变量,链接到file_system_type中的fs_supers链表
struct hlist_node s_instances;
...
char s_id[32]; /* Informational name */
uuid_t s_uuid; /* UUID tune2fs -l可以查看*/
void *s_fs_info; //指向具体文件系统超级块结构,如ext4_sb_info
...
const struct dentry_operations *s_d_op; //该超级块默认的目录项操作函数
...
struct shrinker s_shrink; //每个超级块注册的shrink函数,用于内存回收
...
/* AIO completions deferred from interrupt context */
struct workqueue_struct *s_dio_done_wq;
...
//该超级块对应的未在使用dentry列表
struct list_lru s_dentry_lru ____cacheline_aligned_in_smp;
//该超级块对应的未在使用inode列表
struct list_lru s_inode_lru ____cacheline_aligned_in_smp;
...
/* s_inode_list_lock protects s_inodes */
spinlock_t s_inode_list_lock ____cacheline_aligned_in_smp;
struct list_head s_inodes; //该超级块包含的所有inode
spinlock_t s_inode_wblist_lock;
struct list_head s_inodes_wb; //该超级块正在回写的inode
};
而对应的文件系统则由file_system_type结构描述,
struct file_system_type {
const char *name; //文件系统名称,如ext4,xfs
...
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *); //对应的mount函数
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next; //通过该变量将系统上所有文件系统类型链接到一起
struct hlist_head fs_supers; //该文件系统类型锁包含的超级块对象
...
};
3 注册一个文件系统
内核为了管理系统上的所有文件系统,创建了一个全局链表file_systems,系统上所有文件系统类型都会挂载在上面,我们可以通过/proc/filesystems查看系统所有的文件系统类型。
下面我们以ext4文件类型,大概了解下注册过程。
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,
};
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
//在已注册的全局文件系统链表file_systems中查找,看是否已经注册过
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
//如果查找到就返回错误,同一个文件系统不能注册两次
res = -EBUSY;
else
//没查找到,直接将该文件系统添加链表末尾
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
//在已注册的全局文件系统链表file_systems中查找
for (p = &file_systems; *p; p = &(*p)->next)
if (strncmp((*p)->name, name, len) == 0 &&
!(*p)->name[len])
break;
return p;
}
可见,同个类型的文件系统只能注册一次,因此全局文件系统链表file_systems中不可能出现相同的文件系统类型
4 创建一个超级块
我们再以ext4文件系统为例,看下超级块创建的过程。这一些要从文件系统的挂载开始说起,
static struct dentry *ext4_mount(struct file_system_type *fs_type, int flags,
const char *dev_name, void *data)
{
return mount_bdev(fs_type, flags, dev_name, data, ext4_fill_super);
}
struct dentry *mount_bdev(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data,
int (*fill_super)(struct super_block *, void *, int))
{
...
//分配新的超级块
s = sget(fs_type, test_bdev_super, set_bdev_super, flags | SB_NOSEC,
bdev);
if (s->s_root) {
...
} else {
s->s_mode = mode;
snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev);
sb_set_blocksize(s, block_size(bdev));
//对新的超级块进行信息的填充,对于ext4,就是调用ext4_fill_super
error = fill_super(s, data, flags & SB_SILENT ? 1 : 0);
...
}
...
}
主要就是两点:
- 分配超级块
- 填充超级块
我们先看分配超级块,有几点需要考虑:
- 该设备是否已经mount过,不能重复mount,就像同个类型的文件系统也不能重复注册
- 分配超级块后需要做一些初始化工作,比如各种链表初始化,以及设置缓存回收函数等
- 分配完后就要考虑如何管理这个超级块,要加入全局超级块链表,对应的文件类型也要加到全局文件系统列表,同时还要把刚才初始化设置的缓存回收函数注册到全局回收链表
struct super_block *sget(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
int flags,
void *data)
{
...
return sget_userns(type, test, set, flags, user_ns, data);
}
//sget_userns - find or create a superblock
struct super_block *sget_userns(struct file_system_type *type,
int (*test)(struct super_block *,void *),
int (*set)(struct super_block *,void *),
int flags, struct user_namespace *user_ns,
void *data)
{
struct super_block *s = NULL;
struct super_block *old;
int err;
...
//先检查对应文件系统上的fs_supers链表,查看该设备是否已经mount过
//有mount过,直接返回旧的超级块
if (test) {
hlist_for_each_entry(old, &type->fs_supers, s_instances) {
//通过传入的test_bdev_super判断挂载的超级块对应的设备是否相同
if (!test(old, data))
continue;
...
return old;
}
}
//没mount过,创建新的超级块
if (!s) {
spin_unlock(&sb_lock);
//创建并初始化超级块
s = alloc_super(type, (flags & ~SB_SUBMOUNT), user_ns);
if (!s)
return ERR_PTR(-ENOMEM);
goto retry;
}
...
//把对应文件系统类型赋值到超级块上
s->s_type = type;
strlcpy(s->s_id, type->name, sizeof(s->s_id));
//将新的超级块通过s_list挂到全局super_blocks链表上
list_add_tail(&s->s_list, &super_blocks);
//把该超级块的文件系统实例挂到对应文件系统的列表头
//比如有两个分区都是ext4文件系统,那fs_supers上就会挂上两个超级块信息
hlist_add_head(&s->s_instances, &type->fs_supers);
spin_unlock(&sb_lock);
get_filesystem(type);
//把该超级块的shrink函数挂到全局shrink链表shrinker_list
//这样系统回收内存时,只要调用shrinker_list的shrink函数就可以了
register_shrinker_prepared(&s->s_shrink);
return s;
}
static struct super_block *alloc_super(struct file_system_type *type, int flags,
struct user_namespace *user_ns)
{
...
//初始化各个链表和锁
INIT_HLIST_NODE(&s->s_instances);
INIT_HLIST_BL_HEAD(&s->s_roots);
mutex_init(&s->s_sync_lock);
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);
...
s->s_shrink.seeks = DEFAULT_SEEKS;
//超级块缓存回收函数
s->s_shrink.scan_objects = super_cache_scan;
s->s_shrink.count_objects = super_cache_count;
s->s_shrink.batch = 1024;
s->s_shrink.flags = SHRINKER_NUMA_AWARE | SHRINKER_MEMCG_AWARE;
if (prealloc_shrinker(&s->s_shrink))
goto fail;
//初始化存放未使用的dentry链表s_dentry_lru
if (list_lru_init_memcg(&s->s_dentry_lru, &s->s_shrink))
goto fail;
//初始化存放未使用的indode链表s_inode_lru
if (list_lru_init_memcg(&s->s_inode_lru, &s->s_shrink))
goto fail;
return s;
fail:
destroy_unused_super(s);
return NULL;
}
回过头,我们再看下超级块的填充,虽然初始化时设置了一些,但是还是有很多和具体文件系统相关的信息没有体现,
- 针对具体的文件系统,设置对应的超级块信息,super_block是VFS层的体现,具体到实际文件系统,还有对应的超级块
- 设置该文件系统对应的超级块操作函数,里面包含了对inode资源本身的一些操作集合,比如分配,销毁,写,脏化等操作
- 调用刚才设置的ext4_alloc_inode函数,为超级块的root分配对应的inode,同样inode也是VFS层的体现,实际ext4分配的是ext4_inode_info,而inode内嵌在该结构的vfs_inode变量中,这样就把VFS和实际文件系统关联在一起
- 同样需要为超级块分配一个目录项,并关联到对应的inode
static const struct super_operations ext4_sops = {
.alloc_inode = ext4_alloc_inode,
.destroy_inode = ext4_destroy_inode,
.write_inode = ext4_write_inode,
.dirty_inode = ext4_dirty_inode,
...
};
static int ext4_fill_super(struct super_block *sb, void *data, int silent)
{
struct ext4_sb_info *sbi = kzalloc(sizeof(*sbi), GFP_KERNEL);
...
sb->s_fs_info = sbi; //设置实际文件系统的超级块信息
sbi->s_sb = sb;
...
sb->s_op = &ext4_sops; //设置该超级块对应的操作函数,主要是对inode自身的操作
...
//调用ext4_alloc_inode分配一个用于root的inode
root = ext4_iget(sb, EXT4_ROOT_INO, EXT4_IGET_SPECIAL);
...
sb->s_root = d_make_root(root); //为root分配目录项
...
}
5 结构关系
我们用图对以上几个结构进行展示,
更多推荐
所有评论(0)