Linux中挂载文件系统函数的实现
挂载文件系统的步骤总结及流程图
在 Linux 内核中,挂载文件系统是一个复杂的过程,涉及超级块(super_block
)、挂载点(vfsmount
)、dentry
和 inode
的初始化与管理。以下是挂载文件系统的核心步骤及流程图:
挂载文件系统的核心步骤
获取文件系统类型 (get_fs_type
)
- 目的:根据文件系统名称(如
ext4
、bdev
)查找对应的file_system_type
结构 - 流程
- 调用
get_fs_type(name)
查找已注册的文件系统 - 如果未找到,尝试动态加载模块(
request_module
) - 增加文件系统模块的引用计数(
try_module_get
)
- 调用
分配并初始化 vfsmount
结构 (alloc_vfsmnt
)
- 目的:为挂载点分配内存并初始化链表和引用计数。
- 流程
- 调用
kmem_cache_alloc
从mnt_cache
分配内存 - 初始化
vfsmount
的链表头(mnt_hash
、mnt_child
等) - 设置引用计数(
mnt_count
)和设备名称(mnt_devname
)
- 调用
获取或创建超级块 (sget
)
- 目的:查找或创建超级块,并初始化其基本属性
- 流程
- 调用
sget
查找匹配的超级块(通过test
函数比较) - 如果未找到,调用
alloc_super
分配新的超级块 - 初始化超级块的操作函数(
s_op
)、同步机制(信号量)和引用计数
- 调用
调用文件系统特定的 get_sb
方法
- 目的:由文件系统实现(如
ext4_get_sb
)初始化超级块的具体内容 - 流程
- 调用
type->get_sb
(如bd_get_sb
)创建伪文件系统超级块 - 设置超级块的块大小、魔数、操作函数等
- 调用
初始化根 dentry
和 inode
- 目的:为挂载点创建根目录的
dentry
和inode
- 流程
- 调用
new_inode
创建根inode
,设置权限(S_IFDIR
)和时间戳 - 调用
d_alloc
创建根dentry
,并将其与inode
关联(d_instantiate
) - 设置超级块的根
dentry
(s->s_root
)
- 调用
安全检查和激活超级块
- 目的:确保挂载操作符合安全策略,并标记超级块为活跃状态
- 流程
- 调用
security_sb_kern_mount
进行安全检查 - 设置超级块标志(
MS_ACTIVE
) - 释放写锁(
up_write
)并返回vfsmount
- 调用
错误处理和资源释放
- 目的:在失败时清理已分配的资源
- 流程
- 释放
vfsmount
(free_vfsmnt
) - 停用超级块(
deactivate_super
) - 减少文件系统模块的引用计数(
put_filesystem
)
- 释放
挂载文件系统的流程图
总结
挂载文件系统的核心流程包括:
- 查找文件系统类型(
get_fs_type
)。 - 分配挂载点结构(
alloc_vfsmnt
)。 - 获取或创建超级块(
sget
+alloc_super
)。 - 初始化根目录的
inode
和dentry
。 - 安全检查并激活超级块。
- 错误处理和资源释放。
在内核内部挂载文件系统kern_mount
struct vfsmount *kern_mount(struct file_system_type *type)
{return do_kern_mount(type->name, 0, type->name, NULL);
}
struct vfsmount *
do_kern_mount(const char *fstype, int flags, const char *name, void *data)
{struct file_system_type *type = get_fs_type(fstype);struct super_block *sb = ERR_PTR(-ENOMEM);struct vfsmount *mnt;int error;char *secdata = NULL;if (!type)return ERR_PTR(-ENODEV);mnt = alloc_vfsmnt(name);if (!mnt)goto out;if (data) {secdata = alloc_secdata();if (!secdata) {sb = ERR_PTR(-ENOMEM);goto out_mnt;}error = security_sb_copy_data(type, data, secdata);if (error) {sb = ERR_PTR(error);goto out_free_secdata;}}sb = type->get_sb(type, flags, name, data);if (IS_ERR(sb))goto out_free_secdata;error = security_sb_kern_mount(sb, secdata);if (error)goto out_sb;mnt->mnt_sb = sb;mnt->mnt_root = dget(sb->s_root);mnt->mnt_mountpoint = sb->s_root;mnt->mnt_parent = mnt;mnt->mnt_namespace = current->namespace;up_write(&sb->s_umount);put_filesystem(type);return mnt;
out_sb:up_write(&sb->s_umount);deactivate_super(sb);sb = ERR_PTR(error);
out_free_secdata:free_secdata(secdata);
out_mnt:free_vfsmnt(mnt);
out:put_filesystem(type);return (struct vfsmount *)sb;
}
函数功能概述
这两个函数用于在内核内部挂载文件系统,不涉及用户空间。它们创建并初始化一个vfsmount
结构,为文件系统提供挂载点基础设施
kern_mount 函数
struct vfsmount *kern_mount(struct file_system_type *type)
{return do_kern_mount(type->name, 0, type->name, NULL);
}
- 参数: 接收文件系统类型指针
- 调用do_kern_mount:
type->name
: 文件系统名称作为fstype
参数0
: 挂载标志位为0type->name
: 再次作为名称参数NULL
: 没有额外的挂载数据
do_kern_mount 函数详解
1. 变量声明和初始化
struct file_system_type *type = get_fs_type(fstype);
struct super_block *sb = ERR_PTR(-ENOMEM);
struct vfsmount *mnt;
int error;
char *secdata = NULL;
- type: 通过名称获取文件系统类型结构
- sb: 初始化超级块指针为内存不足错误
mnt
: 将要分配的vfsmount
结构- error: 错误码存储
secdata
: 安全数据指针,初始为NULL
2. 文件系统类型检查
if (!type)return ERR_PTR(-ENODEV);
- 如果
get_fs_type
返回NULL,表示找不到该文件系统类型 - 返回
-ENODEV
(设备不存在)错误
3. 分配vfsmount
结构
mnt = alloc_vfsmnt(name);
if (!mnt)goto out;
alloc_vfsmnt
: 分配并初始化vfsmount
结构- 如果分配失败,跳转到out标签进行清理
4. 安全数据处理
if (data) {secdata = alloc_secdata();if (!secdata) {sb = ERR_PTR(-ENOMEM);goto out_mnt;}error = security_sb_copy_data(type, data, secdata);if (error) {sb = ERR_PTR(error);goto out_free_secdata;}
}
- data存在检查: 如果有挂载数据需要处理
alloc_secdata
: 分配安全数据内存- 内存分配失败: 跳转到
out_mnt
清理vfsmount
- security_sb_copy_data: 复制并验证安全数据
- 安全数据错误: 跳转到
out_free_secdata
清理安全数据
5. 获取超级块
sb = type->get_sb(type, flags, name, data);
if (IS_ERR(sb))goto out_free_secdata;
- type->get_sb: 调用文件系统特定的获取超级块方法
- IS_ERR检查: 如果获取超级块失败,跳转到
out_free_secdata
6. 安全挂载检查
error = security_sb_kern_mount(sb, secdata);
if (error)goto out_sb;
- security_sb_kern_mount: 安全检查,确认允许内核挂载
- 检查失败: 跳转到out_sb清理超级块
7. 初始化vfsmount
结构
mnt->mnt_sb = sb;
mnt->mnt_root = dget(sb->s_root);
mnt->mnt_mountpoint = sb->s_root;
mnt->mnt_parent = mnt;
mnt->mnt_namespace = current->namespace;
mnt_sb
: 设置vfsmount
的超级块指针mnt_root
: 获取并增加根目录dentry
的引用计数mnt_mountpoint
: 设置挂载点为超级块的根目录mnt_parent
: 设置父挂载点为自身(这是根挂载点)mnt_namespace
: 设置命名空间为当前进程的命名空间
8. 完成挂载
up_write(&sb->s_umount);
put_filesystem(type);
return mnt;
- up_write: 释放超级块的写锁
- put_filesystem: 减少文件系统类型的引用计数
return mnt
: 返回成功创建的vfsmount
结构
9. 错误处理路径
out_sb 标签:
out_sb:up_write(&sb->s_umount);deactivate_super(sb);sb = ERR_PTR(error);
- 释放超级块锁
- 停用超级块
- 设置错误指针
out_free_secdata
标签:
out_free_secdata:free_secdata(secdata);
- 释放安全数据内存
out_mnt
标签:
out_mnt:free_vfsmnt(mnt);
- 释放
vfsmount
结构
out 标签:
out:put_filesystem(type);return (struct vfsmount *)sb;
- 减少文件系统类型引用计数
- 返回错误指针
根据名称获取文件系统类型结构get_fs_type
struct file_system_type *get_fs_type(const char *name)
{struct file_system_type *fs;read_lock(&file_systems_lock);fs = *(find_filesystem(name));if (fs && !try_module_get(fs->owner))fs = NULL;read_unlock(&file_systems_lock);if (!fs && (request_module("%s", name) == 0)) {read_lock(&file_systems_lock);fs = *(find_filesystem(name));if (fs && !try_module_get(fs->owner))fs = NULL;read_unlock(&file_systems_lock);}return fs;
}
函数功能概述
get_fs_type
函数用于根据名称获取文件系统类型结构,并增加其模块引用计数。如果文件系统未加载,它会尝试动态加载该文件系统模块
代码逐段详解
1. 变量声明和第一次查找
struct file_system_type *fs;read_lock(&file_systems_lock);
fs = *(find_filesystem(name));
- fs: 文件系统类型指针,用于存储查找结果
- read_lock: 获取文件系统列表的读锁,允许并发读取
- find_filesystem(name): 调用查找函数,返回指向指针的指针
- *(find_filesystem(name)): 解引用,获取实际的文件系统类型指针
2. 模块引用计数管理
if (fs && !try_module_get(fs->owner))fs = NULL;
- fs检查: 如果找到了文件系统类型(fs不为NULL)
- try_module_get(fs->owner): 尝试增加文件系统模块的引用计数
- 成功:返回非零,保持fs不变
- 失败:返回0,表示模块正在被卸载或其他问题
- fs = NULL: 如果增加引用计数失败,将fs设为NULL
3. 释放锁和第一次查找结果检查
read_unlock(&file_systems_lock);
if (!fs && (request_module("%s", name) == 0)) {
- read_unlock: 释放读锁
- !fs检查: 如果第一次没有找到文件系统(fs为NULL)
- request_module(“%s”, name): 请求加载名为name的内核模块
- 返回0表示模块加载请求成功(不一定立即加载完成)
- 条件成立: 如果文件系统不存在但模块加载请求成功
4. 第二次查找(模块加载后)
read_lock(&file_systems_lock);fs = *(find_filesystem(name));if (fs && !try_module_get(fs->owner))fs = NULL;read_unlock(&file_systems_lock);
- 重新加锁: 再次获取读锁
- 重新查找: 再次在文件系统列表中查找同名文件系统
- 再次尝试引用: 如果找到,再次尝试增加模块引用计数
- 释放锁: 完成操作后释放读锁
5. 返回结果
return fs;
- 返回找到的文件系统类型指针,或者NULL(如果未找到或引用失败)
vfsmount
分配并初始化 alloc_vfsmnt
struct vfsmount *alloc_vfsmnt(const char *name)
{struct vfsmount *mnt = kmem_cache_alloc(mnt_cache, GFP_KERNEL);if (mnt) {memset(mnt, 0, sizeof(struct vfsmount));atomic_set(&mnt->mnt_count,1);INIT_LIST_HEAD(&mnt->mnt_hash);INIT_LIST_HEAD(&mnt->mnt_child);INIT_LIST_HEAD(&mnt->mnt_mounts);INIT_LIST_HEAD(&mnt->mnt_list);INIT_LIST_HEAD(&mnt->mnt_fslink);if (name) {int size = strlen(name)+1;char *newname = kmalloc(size, GFP_KERNEL);if (newname) {memcpy(newname, name, size);mnt->mnt_devname = newname;}}}return mnt;
}
函数功能
alloc_vfsmnt
函数用于分配并初始化一个虚拟文件系统挂载点结构(vfsmount
)。它是Linux内核文件系统挂载机制的基础函数,负责创建和管理文件系统挂载点的内核数据结构
代码逐段解释
第1行:函数声明
struct vfsmount *alloc_vfsmnt(const char *name)
- 返回类型:
struct vfsmount *
- 指向vfsmount
结构的指针 - 参数:
const char *name
- 挂载设备名称(可为NULL) - 功能: 分配并初始化一个新的
vfsmount
结构
第2-3行:内存分配
struct vfsmount *mnt = kmem_cache_alloc(mnt_cache, GFP_KERNEL);if (mnt) {
kmem_cache_alloc(mnt_cache, GFP_KERNEL)
:- 从专用的slab缓存
mnt_cache
中分配内存 mnt_cache
: 预创建的vfsmount
对象缓存,提高分配效率GFP_KERNEL
: 标准内核内存分配标志
- 从专用的slab缓存
if (mnt)
: 检查内存分配是否成功- 如果分配失败,
mnt
为NULL,直接返回NULL
- 如果分配失败,
第4行:内存清零
memset(mnt, 0, sizeof(struct vfsmount));
memset(mnt, 0, sizeof(struct vfsmount))
:- 将整个
vfsmount
结构体清零 - 确保所有字段都处于已知的初始状态
- 避免未初始化内存导致的问题
- 将整个
第5行:设置引用计数
atomic_set(&mnt->mnt_count,1);
atomic_set(&mnt->mnt_count,1)
:- 原子性地设置引用计数为1
mnt_count
: 记录该挂载点被引用的次数- 初始值为1表示创建者持有第一个引用
第6-10行:初始化链表头
INIT_LIST_HEAD(&mnt->mnt_hash);INIT_LIST_HEAD(&mnt->mnt_child);INIT_LIST_HEAD(&mnt->mnt_mounts);INIT_LIST_HEAD(&mnt->mnt_list);INIT_LIST_HEAD(&mnt->mnt_fslink);
各个链表的用途:
mnt_hash
: 用于哈希表,快速查找挂载点mnt_child
: 在同一父挂载点下的兄弟挂载点链表mnt_mounts
: 该挂载点的子挂载点链表mnt_list
: 全局挂载点链表mnt_fslink
: 文件系统链接链表
INIT_LIST_HEAD
宏:
// 将链表头的prev和next指针都指向自己
// 创建空的双向循环链表
第11-18行:设置设备名称
if (name) {int size = strlen(name)+1;char *newname = kmalloc(size, GFP_KERNEL);if (newname) {memcpy(newname, name, size);mnt->mnt_devname = newname;}}
if (name)
: 检查是否提供了设备名称int size = strlen(name)+1
: 计算名称长度(包含终止符)char *newname = kmalloc(size, GFP_KERNEL)
: 为名称分配内存if (newname)
: 检查名称内存分配是否成功memcpy(newname, name, size)
: 复制设备名称(包括’\0’)mnt->mnt_devname = newname
: 设置挂载点的设备名称
第19-20行:返回结果
}return mnt;
return mnt
: 返回分配好的vfsmount
指针- 成功: 返回有效的
vfsmount
指针 - 失败: 返回NULL
- 成功: 返回有效的
停用和销毁一个超级块deactivate_super
void deactivate_super(struct super_block *s)
{struct file_system_type *fs = s->s_type;if (atomic_dec_and_lock(&s->s_active, &sb_lock)) {s->s_count -= S_BIAS-1;spin_unlock(&sb_lock);down_write(&s->s_umount);fs->kill_sb(s);put_filesystem(fs);put_super(s);}
}
函数功能
deactivate_super
函数用于停用和销毁一个超级块(super_block),当文件系统不再需要时清理相关资源。它是文件系统卸载过程的核心函数
代码逐段解释
第1行:函数声明
void deactivate_super(struct super_block *s)
- 返回类型:
void
- 无返回值 - 参数:
struct super_block *s
- 指向要停用的超级块的指针 - 功能: 停用超级块并释放相关资源
第2行:获取文件系统类型
struct file_system_type *fs = s->s_type;
s->s_type
: 超级块对应的文件系统类型(如ext4等)fs
: 保存文件系统类型指针,后续使用
第3行:原子递减和锁定检查
if (atomic_dec_and_lock(&s->s_active, &sb_lock)) {
这是函数的核心条件检查:
atomic_dec_and_lock
操作:
// 原子性地执行两个操作:
1. atomic_dec(&s->s_active): 将活动引用计数减1
2. 如果减1后计数为0,获取sb_lock自旋锁// 返回值:
// true: 计数减到0且成功获取锁
// false: 计数不为0,不获取锁
&s->s_active
: 超级块的活动引用计数&sb_lock
: 超级块全局自旋锁,保护超级块状态
第4行:调整超级块计数
s->s_count -= S_BIAS-1;
s->s_count
: 超级块的另一个引用计数S_BIAS
: 一个偏置常量,用于引用计数管理- 操作: 从
s_count
中减去S_BIAS-1
- 目的: 调整引用计数,为后续清理做准备
第5行:释放超级块锁
spin_unlock(&sb_lock);
spin_unlock(&sb_lock)
: 释放之前获取的超级块全局锁
第6行:获取卸载写锁
down_write(&s->s_umount);
down_write(&s->s_umount)
: 获取超级块的卸载读写信号量的写锁- 目的: 确保在卸载过程中没有其他线程可以挂载或访问该超级块
- 阻塞可能: 如果其他线程持有读锁,会阻塞等待
第7行:调用文件系统的kill_sb方法
fs->kill_sb(s);
fs->kill_sb
: 文件系统类型中定义的"kill superblock"函数指针- 作用: 执行文件系统特定的清理操作
- 不同文件系统的实现:
- 释放文件系统特定资源
- 同步脏数据到磁盘
- 关闭底层设备
第8行:释放文件系统类型引用
put_filesystem(fs);
put_filesystem(fs)
: 减少文件系统类型的引用计数- 作用: 如果引用计数为0,可能卸载文件系统模块
- 对应操作: 与
get_filesystem
配对使用
第9行:释放超级块
put_super(s);
put_super(s)
: 释放超级块结构本身- 内部操作:
- 释放超级块占用的内存
- 清理相关数据结构
- 从全局超级块列表中移除
减少文件系统类型的引用计数put_filesystem
void put_filesystem(struct file_system_type *fs)
{module_put(fs->owner);
}
static inline void module_put(struct module *module)
{if (module) {unsigned int cpu = get_cpu();local_dec(&module->ref[cpu].count);/* Maybe they're waiting for us to drop reference? */if (unlikely(!module_is_live(module)))wake_up_process(module->waiter);put_cpu();}
}
函数功能
put_filesystem
函数用于减少文件系统类型的引用计数,当引用计数降为0时可能触发文件系统模块的卸载。module_put
是内核模块引用计数管理的核心函数
代码逐段解释
put_filesystem 函数
第1-3行:put_filesystem实现
void put_filesystem(struct file_system_type *fs)
{module_put(fs->owner);
}
- 参数:
struct file_system_type *fs
- 文件系统类型指针 fs->owner
: 指向拥有该文件系统的内核模块module_put(fs->owner)
: 调用模块引用计数减少函数- 作用: 减少文件系统对应内核模块的引用计数
module_put 函数
第1行:函数声明
static inline void module_put(struct module *module)
static inline
: 静态内联函数,减少函数调用开销- 参数:
struct module *module
- 内核模块指针
第2-3行:空指针检查
if (module) {
- 检查模块指针是否有效
- 如果module为NULL,直接跳过所有操作
第4行:获取当前CPU编号
unsigned int cpu = get_cpu();
get_cpu()
: 获取当前CPU编号并禁用内核抢占- 作用:
- 防止在操作过程中被调度到其他CPU
- 确保引用计数操作的原子性和一致性
第5行:减少每CPU引用计数
local_dec(&module->ref[cpu].count);
module->ref[cpu].count
: 当前CPU对应的模块引用计数local_dec()
: 原子性地减少本地CPU的引用计数- 每CPU变量优势:
- 避免CPU间的缓存行竞争
- 提高并发性能
第6-8行:检查模块状态并唤醒等待者
/* Maybe they're waiting for us to drop reference? */if (unlikely(!module_is_live(module)))wake_up_process(module->waiter);
unlikely(!module_is_live(module))
:module_is_live(module)
: 检查模块是否处于活动状态(非MODULE_STATE_GOING)unlikely()
: 提示编译器这个条件很少成立,优化分支预测
wake_up_process(module->waiter)
:- 如果模块不在活动状态,唤醒等待进程
module->waiter
: 等待模块卸载完成的进程
第9行:恢复内核抢占
put_cpu();}
put_cpu()
: 恢复内核抢占状态- 与
get_cpu()
配对使用,恢复正常的调度行为
创建和管理块设备的伪文件系统超级块get_sb
static struct super_block *bd_get_sb(struct file_system_type *fs_type,int flags, const char *dev_name, void *data)
{return get_sb_pseudo(fs_type, "bdev:", &bdev_sops, 0x62646576);
}static struct file_system_type bd_type = {.name = "bdev",.get_sb = bd_get_sb,.kill_sb = kill_anon_super,
};
struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,struct super_operations *ops, unsigned long magic)
{struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);static struct super_operations default_ops = {.statfs = simple_statfs};struct dentry *dentry;struct inode *root;struct qstr d_name = {.name = name, .len = strlen(name)};if (IS_ERR(s))return s;s->s_flags = MS_NOUSER;s->s_maxbytes = ~0ULL;s->s_blocksize = 1024;s->s_blocksize_bits = 10;s->s_magic = magic;s->s_op = ops ? ops : &default_ops;root = new_inode(s);if (!root)goto Enomem;root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;root->i_uid = root->i_gid = 0;root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;dentry = d_alloc(NULL, &d_name);if (!dentry) {iput(root);goto Enomem;}dentry->d_sb = s;dentry->d_parent = dentry;d_instantiate(dentry, root);s->s_root = dentry;s->s_flags |= MS_ACTIVE;return s;Enomem:up_write(&s->s_umount);deactivate_super(s);return ERR_PTR(-ENOMEM);
}
整体功能
这些函数用于创建和管理块设备的伪文件系统超级块,为内核中的块设备提供统一的文件系统接口
代码逐段解释
bd_get_sb 函数
第1-4行:bd_get_sb实现
static struct super_block *bd_get_sb(struct file_system_type *fs_type,int flags, const char *dev_name, void *data)
{return get_sb_pseudo(fs_type, "bdev:", &bdev_sops, 0x62646576);
}
- 参数: 标准的文件系统挂载参数
get_sb_pseudo
: 创建伪文件系统超级块- 参数说明:
fs_type
: 文件系统类型"bdev:"
: 伪文件系统名称前缀&bdev_sops
: 块设备超级块操作函数集0x62646576
: 魔数,ASCII对应"bdev
"
bd_type 文件系统类型定义
第6-10行:块设备文件系统类型
static struct file_system_type bd_type = {.name = "bdev",.get_sb = bd_get_sb,.kill_sb = kill_anon_super,
};
.name = "bdev"
: 文件系统名称为"bdev
".get_sb = bd_get_sb
: 挂载时调用bd_get_sb创建超级块.kill_sb = kill_anon_super
: 卸载时调用匿名超级块清理函数
get_sb_pseudo 函数
第13-15行:函数声明和变量定义
struct super_block *
get_sb_pseudo(struct file_system_type *fs_type, char *name,struct super_operations *ops, unsigned long magic)
{struct super_block *s = sget(fs_type, NULL, set_anon_super, NULL);static struct super_operations default_ops = {.statfs = simple_statfs};struct dentry *dentry;struct inode *root;struct qstr d_name = {.name = name, .len = strlen(name)};
- 参数: 文件系统类型、名称、操作函数集、魔数
sget()
: 获取或创建超级块default_ops
: 默认操作函数集,只包含statfs
d_name
: 目录名称结构,初始化名称和长度
第17-18行:错误检查
if (IS_ERR(s))return s;
IS_ERR(s)
: 检查超级块创建是否失败- 如果失败直接返回错误指针
第20-25行:超级块基本设置
s->s_flags = MS_NOUSER;s->s_maxbytes = ~0ULL;s->s_blocksize = 1024;s->s_blocksize_bits = 10;s->s_magic = magic;s->s_op = ops ? ops : &default_ops;
详细设置:
MS_NOUSER
: 标记为用户不可挂载的文件系统s_maxbytes = ~0ULL
: 最大文件大小为无符号长整型的最大值s_blocksize = 1024
: 块大小1KBs_blocksize_bits = 10
: 块大小位数(2^10=1024)s_magic = magic
: 设置文件系统魔数s_op
: 设置超级块操作函数集,如果ops为空使用默认
第26-32行:创建根inode
root = new_inode(s);if (!root)goto Enomem;root->i_mode = S_IFDIR | S_IRUSR | S_IWUSR;root->i_uid = root->i_gid = 0;root->i_atime = root->i_mtime = root->i_ctime = CURRENT_TIME;
new_inode(s)
: 为超级块创建新的inode
- 权限设置:
S_IFDIR
: 目录类型S_IRUSR | S_IWUSR
: 用户读写权限
- 所有权:
uid
和gid
都设为0(root) - 时间戳: 所有时间设为当前时间
第33-39行:创建根dentry
dentry = d_alloc(NULL, &d_name);if (!dentry) {iput(root);goto Enomem;}dentry->d_sb = s;dentry->d_parent = dentry;d_instantiate(dentry, root);s->s_root = dentry;
d_alloc(NULL, &d_name)
: 分配dentry
结构- 错误处理: 如果
dentry
分配失败,释放inode
并跳转到错误处理 dentry
设置:d_sb = s
: 指向所属超级块d_parent = dentry
: 父目录指向自己(根目录)
d_instantiate
: 将dentry
与inode
关联s->s_root = dentry
: 设置超级块的根dentry
第40-41行:激活超级块并返回
s->s_flags |= MS_ACTIVE;return s;
MS_ACTIVE
: 标记超级块为活跃状态- 返回创建好的超级块指针
第43-46行:错误处理
Enomem:up_write(&s->s_umount);deactivate_super(s);return ERR_PTR(-ENOMEM);
up_write(&s->s_umount)
: 释放超级块写锁deactivate_super(s)
: 停用超级块ERR_PTR(-ENOMEM)
: 返回内存不足错误
分配并初始化一个超级块alloc_super
static struct super_block *alloc_super(void)
{struct super_block *s = kmalloc(sizeof(struct super_block), GFP_USER);static struct super_operations default_op;if (s) {memset(s, 0, sizeof(struct super_block));if (security_sb_alloc(s)) {kfree(s);s = NULL;goto out;}INIT_LIST_HEAD(&s->s_dirty);INIT_LIST_HEAD(&s->s_io);INIT_LIST_HEAD(&s->s_files);INIT_LIST_HEAD(&s->s_instances);INIT_HLIST_HEAD(&s->s_anon);init_rwsem(&s->s_umount);sema_init(&s->s_lock, 1);down_write(&s->s_umount);s->s_count = S_BIAS;atomic_set(&s->s_active, 1);sema_init(&s->s_vfs_rename_sem,1);sema_init(&s->s_dquot.dqio_sem, 1);sema_init(&s->s_dquot.dqonoff_sem, 1);init_rwsem(&s->s_dquot.dqptr_sem);init_waitqueue_head(&s->s_wait_unfrozen);s->s_maxbytes = MAX_NON_LFS;s->dq_op = sb_dquot_ops;s->s_qcop = sb_quotactl_ops;s->s_op = &default_op;}
out:return s;
}
函数功能
alloc_super
函数用于分配并初始化一个超级块(super_block)结构体。它是文件系统挂载时创建超级块的核心函数,负责设置超级块的所有基本属性和同步机制
代码逐段解释
第1-3行:函数声明和内存分配
static struct super_block *alloc_super(void)
{struct super_block *s = kmalloc(sizeof(struct super_block), GFP_USER);static struct super_operations default_op;
- 返回类型:
struct super_block *
- 超级块指针 kmalloc(sizeof(struct super_block), GFP_USER)
:- 分配超级块结构体大小的内存
GFP_USER
: 用户空间触发的分配标志
static struct super_operations default_op
:- 静态默认超级块操作结构
- 静态变量在首次使用时初始化为零
第4-10行:内存初始化和安全检查
if (s) {memset(s, 0, sizeof(struct super_block));if (security_sb_alloc(s)) {kfree(s);s = NULL;goto out;}
if (s)
: 检查内存分配是否成功memset(s, 0, sizeof(struct super_block))
: 将整个超级块结构清零security_sb_alloc(s)
: Linux安全模块(LSM)钩子函数- 进行安全相关的初始化
- 如果安全检查失败,释放内存并跳转到out标签
第11-15行:初始化链表头
INIT_LIST_HEAD(&s->s_dirty);INIT_LIST_HEAD(&s->s_io);INIT_LIST_HEAD(&s->s_files);INIT_LIST_HEAD(&s->s_instances);INIT_HLIST_HEAD(&s->s_anon);
各个链表的用途:
s_dirty
: 脏inode
链表s_io
: 需要写入磁盘的inode
链表s_files
: 该文件系统打开的文件链表s_instances
: 同类型文件系统实例链表s_anon
: 匿名dentry
的哈希链表
第16-19行:初始化同步原语
init_rwsem(&s->s_umount);sema_init(&s->s_lock, 1);down_write(&s->s_umount);
init_rwsem(&s->s_umount)
: 初始化卸载读写信号量sema_init(&s->s_lock, 1)
: 初始化超级块锁为1(可用状态)down_write(&s->s_umount)
: 获取卸载写锁,防止并发卸载
第20-21行:设置引用计数
s->s_count = S_BIAS;atomic_set(&s->s_active, 1);
s->s_count = S_BIAS
: 设置超级块引用计数偏置值S_BIAS
是一个大数,防止过早释放
atomic_set(&s->s_active, 1)
: 原子性地设置活动引用计数为1
第22-26行:初始化更多同步机制
sema_init(&s->s_vfs_rename_sem,1);sema_init(&s->s_dquot.dqio_sem, 1);sema_init(&s->s_dquot.dqonoff_sem, 1);init_rwsem(&s->s_dquot.dqptr_sem);init_waitqueue_head(&s->s_wait_unfrozen);
同步原语详解:
s_vfs_rename_sem
: VFS重命名操作信号量dqio_sem
: 磁盘配额I/O信号量dqonoff_sem
: 配额启用/禁用信号量dqptr_sem
: 配额指针读写信号量s_wait_unfrozen
: 等待文件系统解冻的等待队列
第27-30行:设置操作函数和限制
s->s_maxbytes = MAX_NON_LFS;s->dq_op = sb_dquot_ops;s->s_qcop = sb_quotactl_ops;s->s_op = &default_op;}
s->s_maxbytes = MAX_NON_LFS
: 设置最大文件大小限制MAX_NON_LFS
通常为2GB,避免大文件支持问题
dq_op = sb_dquot_ops
: 设置磁盘配额操作函数s_qcop = sb_quotactl_ops
: 设置配额控制操作函数s_op = &default_op
: 设置默认的超级块操作函数
第31-33行:返回结果
out:return s;
}
out:
: 标签,用于错误处理跳转return s
: 返回超级块指针(成功或NULL)
获取或创建超级块sget
struct super_block *sget(struct file_system_type *type,int (*test)(struct super_block *,void *),int (*set)(struct super_block *,void *),void *data)
{struct super_block *s = NULL;struct list_head *p;int err;retry:spin_lock(&sb_lock);if (test) list_for_each(p, &type->fs_supers) {struct super_block *old;old = list_entry(p, struct super_block, s_instances);if (!test(old, data))continue;if (!grab_super(old))goto retry;if (s)destroy_super(s);return old;}if (!s) {spin_unlock(&sb_lock);s = alloc_super();if (!s)return ERR_PTR(-ENOMEM);goto retry;}err = set(s, data);if (err) {spin_unlock(&sb_lock);destroy_super(s);return ERR_PTR(err);}s->s_type = type;strlcpy(s->s_id, type->name, sizeof(s->s_id));list_add_tail(&s->s_list, &super_blocks);list_add(&s->s_instances, &type->fs_supers);spin_unlock(&sb_lock);get_filesystem(type);return s;
}
函数功能
sget
函数用于获取或创建超级块,它是VFS层中超级块管理的核心函数。它实现了超级块的重用机制,避免相同文件系统的重复挂载,同时支持超级块的查找、创建和初始化
代码逐段解释
第1-6行:函数声明和变量定义
struct super_block *sget(struct file_system_type *type,int (*test)(struct super_block *,void *),int (*set)(struct super_block *,void *),void *data)
{struct super_block *s = NULL;struct list_head *p;int err;
- 参数:
type
: 文件系统类型指针test
: 测试函数指针,用于检查现有超级块是否匹配set
: 设置函数指针,用于初始化新超级块data
: 传递给test和set函数的用户数据
- 局部变量:
s
: 新超级块指针,初始为NULLp
: 链表遍历指针err
: 错误码
第7行:重试标签
retry:
- 用于在获取超级块失败时重新尝试的标签
第8-19行:查找现有超级块
spin_lock(&sb_lock);if (test) list_for_each(p, &type->fs_supers) {struct super_block *old;old = list_entry(p, struct super_block, s_instances);if (!test(old, data))continue;if (!grab_super(old))goto retry;if (s)destroy_super(s);return old;}
spin_lock(&sb_lock)
: 获取超级块全局自旋锁if (test) list_for_each(p, &type->fs_supers)
:- 如果提供了test函数,遍历该文件系统类型的所有超级块实例
old = list_entry(p, struct super_block, s_instances)
:- 从链表节点获取超级块结构体指针
if (!test(old, data)) continue
:- 调用test函数检查超级块是否匹配,不匹配则继续遍历
if (!grab_super(old)) goto retry
:- 尝试获取现有超级块的引用,如果失败则重试
if (s) destroy_super(s)
:- 如果之前创建了新超级块s,现在销毁它
return old
: 返回找到的现有超级块
第20-26行:创建新超级块
if (!s) {spin_unlock(&sb_lock);s = alloc_super();if (!s)return ERR_PTR(-ENOMEM);goto retry;}
if (!s)
: 如果没有创建新超级块且没找到现有超级块spin_unlock(&sb_lock)
: 释放锁,因为alloc_super
可能睡眠s = alloc_super()
: 分配新超级块if (!s) return ERR_PTR(-ENOMEM)
: 分配失败返回内存不足错误goto retry
: 跳转到retry,重新尝试查找(可能其他线程已创建)
第28-33行:初始化新超级块
err = set(s, data);if (err) {spin_unlock(&sb_lock);destroy_super(s);return ERR_PTR(err);}
err = set(s, data)
: 调用set函数初始化新超级块- 错误处理: 如果set失败,释放锁,销毁超级块,返回错误
第34-39行:注册新超级块
s->s_type = type;strlcpy(s->s_id, type->name, sizeof(s->s_id));list_add_tail(&s->s_list, &super_blocks);list_add(&s->s_instances, &type->fs_supers);spin_unlock(&sb_lock);
s->s_type = type
: 设置超级块的文件系统类型strlcpy(s->s_id, type->name, sizeof(s->s_id))
: 复制文件系统名称到s_idlist_add_tail(&s->s_list, &super_blocks)
: 添加到全局超级块链表list_add(&s->s_instances, &type->fs_supers)
: 添加到文件系统类型的实例链表spin_unlock(&sb_lock)
: 释放超级块全局锁
第40-41行:增加引用并返回
get_filesystem(type);return s;
get_filesystem(type)
: 增加文件系统类型的引用计数return s
: 返回新创建的超级块
设计模式分析
模板方法模式
// sget提供算法框架:
1. 查找现有实例
2. 创建新实例
3. 初始化新实例// test和set函数由具体文件系统实现:
- 不同的匹配逻辑
- 不同的初始化方式
重试机制解决竞态条件
// 经典的Linux内核并发模式:
retry:spin_lock();// 检查条件if (条件失败) {spin_unlock();// 可能睡眠的操作goto retry;}// 执行操作spin_unlock();
性能优化
避免重复挂载
// 通过重用机制:
// 相同参数的挂载请求返回同一超级块
// 减少内存使用和初始化开销
锁粒度优化
// 精细的锁控制:
- 链表操作时持有锁
- 内存分配时释放锁
- 平衡并发性和安全性
安全地获取一个超级块的引用grab_super
static int grab_super(struct super_block *s)
{s->s_count++;spin_unlock(&sb_lock);down_write(&s->s_umount);if (s->s_root) {spin_lock(&sb_lock);if (s->s_count > S_BIAS) {atomic_inc(&s->s_active);s->s_count--;spin_unlock(&sb_lock);return 1;}spin_unlock(&sb_lock);}up_write(&s->s_umount);put_super(s);yield();return 0;
}
函数功能
grab_super
函数用于安全地获取一个超级块的引用。它实现了超级块引用获取的并发安全机制,确保在获取引用时超级块不会被意外卸载
代码逐段解释
第1行:函数声明
static int grab_super(struct super_block *s)
static
: 静态函数,只在当前文件内可见- 返回类型:
int
- 1表示成功,0表示失败 - 参数:
struct super_block *s
- 要获取引用的超级块指针
第2-3行:增加引用计数并释放锁
s->s_count++;spin_unlock(&sb_lock);
s->s_count++
: 增加超级块的引用计数spin_unlock(&sb_lock)
: 释放超级块全局自旋锁- 为什么立即释放锁?: 因为后续的
down_write
操作可能睡眠,不能在持有自旋锁时调用
第4行:获取卸载写锁
down_write(&s->s_umount);
down_write(&s->s_umount)
: 获取超级块卸载读写信号量的写锁- 作用: 防止在引用获取过程中超级块被卸载
- 可能阻塞: 如果其他线程正在卸载超级块,会等待直到卸载完成
第5行:检查根目录是否存在
if (s->s_root) {
s->s_root
: 检查超级块是否有有效的根目录- 意义: 如果根目录存在,说明超级块是完整初始化的,可以安全使用
第6-12行:成功获取引用的路径
spin_lock(&sb_lock);if (s->s_count > S_BIAS) {atomic_inc(&s->s_active);s->s_count--;spin_unlock(&sb_lock);return 1;}spin_unlock(&sb_lock);
spin_lock(&sb_lock)
: 重新获取超级块全局锁if (s->s_count > S_BIAS)
:- 检查引用计数是否大于偏置值
S_BIAS
是一个大数,用于防止过早释放- 这个条件确保超级块没有被标记为待释放
atomic_inc(&s->s_active)
: 原子性地增加活动引用计数s->s_count--
: 减少之前增加的s_count
(因为要用s_active
)spin_unlock(&sb_lock)
: 释放全局锁return 1
: 返回成功
第13-17行:失败处理路径
}up_write(&s->s_umount);put_super(s);yield();return 0;
up_write(&s->s_umount)
: 释放卸载写锁put_super(s)
: 减少超级块引用计数,可能触发释放yield()
: 让出CPU,给其他线程运行机会grab_super
失败通常意味着另一个线程正在释放这个超级块- 如果没有yield(),释放超级块的线程可能还没有得到CPU时间,该线程就继续重试
return 0
: 返回失败
详细机制分析
引用计数管理策略
锁的使用顺序和目的
// 锁序列分析:
1. sb_lock (自旋锁): - 保护超级块链表和引用计数- 不能长时间持有(可能睡眠)2. s_umount (读写信号量):- 保护超级块卸载过程- 可以长时间持有,允许睡眠
设计原理总结
双重检查锁定模式
// grab_super使用的模式:
1. 快速检查: s_count++ (持有sb_lock)
2. 详细检查: down_write(s_umount) + 检查s_root
3. 最终确认: 重新检查s_count > S_BIAS (持有sb_lock)// 确保在所有关键阶段状态一致