3. VFS通用文件模型:文件系统注册与挂载流程、mount系统调用实现、命名空间管理
本章深入剖析VFS层的核心骨架——通用文件模型。理解文件系统如何注册到内核、如何通过mount系统调用挂载到目录树、以及命名空间如何隔离不同进程的视图,是掌握Linux存储栈的关键。我们将从数据结构、流程步骤和内核代码实现三个维度展开。
3.1 文件系统类型注册机制
每个文件系统在可用之前,必须先向VFS层注册其file_system_type结构体。该结构体是文件系统驱动与VFS之间的契约。
3.1.1 核心数据结构:file_system_type
| 字段 | 类型 | 说明 |
|---|---|---|
name |
const char * |
文件系统名称,如"ext4"、"xfs"、"ntfs" |
fs_flags |
int |
特性标志,如FS_REQUIRES_DEV、FS_USERNS_MOUNT |
mount |
struct dentry *(*mount)(...) |
挂载回调,返回根dentry |
kill_sb |
void (*kill_sb)(struct super_block *) |
卸载时销毁超级块 |
next |
struct file_system_type * |
链表指针,链接所有已注册的文件系统 |
3.1.2 注册流程
- 调用入口:模块初始化时调用
register_filesystem()。 - 链表插入:将
file_system_type实例加入全局链表file_systems。 - 命名冲突检查:遍历链表,若
name已存在则返回-EBUSY。 - 模块引用计数:若为可卸载模块,增加模块引用计数。
关键点:
register_filesystem() 通常在文件系统模块的 init 函数中调用。例如 ext4 在 ext4_init_fs() 中注册。卸载时调用 unregister_filesystem() 从链表移除。
3.1.3 注册与挂载关系SVG图
3.2 mount系统调用实现
mount系统调用是用户空间与VFS挂载逻辑的桥梁。其核心路径从SYSCALL_DEFINE5(mount, ...)开始,经过权限检查、路径解析、超级块创建,最终将挂载点与根dentry关联。
3.2.1 调用链与关键函数
| 函数 | 所在文件 | 职责 |
|---|---|---|
ksys_mount() |
fs/namespace.c | 用户态入口,复制参数,调用path_mount() |
path_mount() |
fs/namespace.c | 解析挂载源与目标路径,权限检查 |
do_new_mount() |
fs/namespace.c | 分配mount结构,调用文件系统mount回调 |
vfs_create_mount() |
fs/namespace.c | 创建mount实例并关联超级块 |
attach_recursive_mnt() |
fs/namespace.c | 将新挂载点插入命名空间树 |
3.2.2 核心流程步骤
- 参数复制与校验:从用户空间复制
source、target、filesystemtype、mountflags、data。 - 路径解析:通过
user_path_at()获取目标路径的path结构(含dentry和vfsmount)。 - 权限检查:调用
may_mount()检查挂载权限,ns_capable()检查命名空间能力。 - 文件系统查找:遍历
file_systems链表,匹配filesystemtype名称。 - 创建超级块:调用文件系统的
mount回调,返回根dentry和超级块。 - 挂载点关联:创建
mount结构,将根dentry挂接到目标dentry上。 - 命名空间插入:调用
attach_recursive_mnt()将新挂载点加入当前进程的命名空间树。
性能提示:
mount 系统调用在路径解析阶段会触发 path_walk(),对于深层目录树可能产生较大开销。频繁挂载/卸载操作(如容器场景)建议使用 mount --bind 或 pivot_root 减少超级块创建成本。
3.2.3 mount系统调用流程SVG图
3.3 命名空间管理
Linux命名空间(Namespace)是容器技术的核心基石之一。挂载命名空间(Mount Namespace)隔离了进程对挂载点视图的感知,使得每个容器可以拥有独立的文件系统层次结构。
3.3.1 核心数据结构:mnt_namespace
| 字段 | 类型 | 说明 |
|---|---|---|
count |
atomic_t |
引用计数,决定命名空间何时销毁 |
root |
struct mount * |
命名空间的根挂载点 |
list |
struct list_head |
挂载点链表,遍历所有挂载 |
user_ns |
struct user_namespace * |
关联的用户命名空间,用于权限检查 |
seq |
unsigned int |
序列号,用于检测挂载点变更 |
3.3.2 命名空间操作
- 创建:
clone()或unshare()时指定CLONE_NEWNS标志,内核调用create_mnt_ns()复制当前命名空间。 - 切换:
switch_mnt_ns()更新进程的nsproxy指针,指向新的命名空间。 - 传播:挂载事件通过
propagate_mnt()在共享挂载组(peer group)之间传播。 - 销毁:当最后一个进程退出或调用
umount清理所有挂载点时,命名空间被释放。
注意:挂载命名空间的传播类型(
MS_SHARED、MS_PRIVATE、MS_SLAVE、MS_UNBINDABLE)决定了挂载事件是否以及如何传播到其他命名空间。默认情况下,新命名空间中的挂载是私有的,不会影响父命名空间。
3.3.3 命名空间隔离与挂载传播SVG图
3.4 关键数据结构关系总结
VFS通用文件模型的核心数据结构之间通过指针形成网状关系,理解这些关系是掌握挂载流程的基础。
| 数据结构 | 角色 | 关键关联 |
|---|---|---|
file_system_type |
文件系统类型描述符 | 全局链表,提供mount回调 |
super_block |
已挂载文件系统的超级块 | 关联file_system_type、根dentry |
mount |
挂载实例(内核内部) | 包含mnt_mountpoint、mnt_root、mnt_parent |
vfsmount |
挂载实例(对外接口) | 指向mount,包含mnt_sb、mnt_root |
dentry |
目录项缓存 | 构成目录树,d_op指向文件系统操作 |
mnt_namespace |
挂载命名空间 | 包含root挂载点、挂载链表 |
总结:文件系统注册是“类型定义”,挂载是“实例化”,命名空间是“视图隔离”。三者共同构成了VFS通用文件模型的完整生命周期。理解
mount系统调用如何串联file_system_type、super_block、mount和mnt_namespace,是优化文件系统性能的前提。