本文探讨了 Linux 文件系统挂载的复杂机制,重点分析虚拟文件系统(VFS)层和 ext2 文件系统。本文为 IT 专业人士提供了一个详细的技术讲解,涵盖挂载过程、关键 VFS 数据结构及其交互,优化了技术受众的阅读体验。
引言
在 Linux 中,挂载文件系统是将块设备上的文件系统附着到系统目录树中的某个目录,从而允许用户访问文件的操作。这一过程由 Linux 内核协调,涉及 VFS 层与具体文件系统实现之间的复杂交互。本文深入探讨文件系统挂载的核心逻辑,以 ext2 文件系统为例,解答为何需要挂载以及挂载如何实现文件访问。
环境:
- 处理器架构:arm64
- 内核版本:Linux 5.11
- 操作系统:Ubuntu 20.04.1
- 工具:vim、ctags、cscope
本文不涉及挂载命名空间和绑定挂载等高级主题,仅聚焦于以 ext2 为例的基础挂载过程。
关键 VFS 数据结构
VFS 层抽象了各种文件系统的通用功能,为用户进程提供统一接口,并屏蔽具体文件系统的实现细节。以下是挂载过程中涉及的关键 VFS 数据结构:
1. file_system_type
- 作用:定义文件系统类型,指定挂载和卸载方法。
- 功能:由具体文件系统(如 ext2)注册,用于在挂载时创建超级块和根目录项对象。
- 文件系统分类:
- 磁盘文件系统:持久存储(如 ext2、ext4、xfs)。
- 内存文件系统:易失性存储(如 tmpfs)。
- 伪文件系统:内核接口(如 proc、sysfs、sockfs、bdev)。
- 网络文件系统:远程数据访问(如 NFS)。
- 源码路径:include/linux/fs.h(约第 2226 行)
2. super_block
- 作用:表示块设备上文件系统的元数据(如块大小、最大文件大小、魔数)。
- 特点:一个文件系统在内存中仅有一个超级块实例,即使被多次挂载(对磁盘文件系统而言)。
- 源码路径:include/linux/fs.h(约第 1414 行)
3. mount
- 作用:描述一次挂载操作,连接超级块与挂载点。
- 特点:每次挂载操作都会创建一个唯一的挂载对象。
- 源码路径:fs/mount.h(约第 39 行)
4. inode
- 作用:存储文件或目录的元数据(如属性、位置)。
- 功能:在文件首次访问时创建,通常通过读取磁盘上的索引节点数据。
- 源码路径:include/linux/fs.h(约第 610 行)
5. dentry
- 作用:表示目录项,构成文件系统的目录树。
- 功能:将文件名或目录名映射到索引节点号,在路径遍历时于内存中创建。
- 源码路径:include/linux/dcache.h(约第 90 行)
6. file
- 作用:表示进程打开的文件描述符。
- 功能:在进程打开文件时创建,加入进程的文件描述符表。
- 源码路径:include/linux/fs.h(约第 915 行)
文件系统挂载流程
挂载过程涉及一系列内核操作,将块设备的文件系统集成到系统目录树中。以下是结构化的流程概述:
1. 系统调用处理
- 入口:mount 系统调用(fs/namespace.c 中的 SYSCALL_DEFINE5)启动挂载流程。
- 参数:
- dev_name:块设备路径。
- dir_name:挂载点目录。
- type:文件系统类型(如 “ext2″)。
- flags:挂载选项(如只读)。
- data:其他挂载选项。
- 步骤:
- 将用户空间参数复制到内核空间。
- 委托给 do_mount 进行进一步处理。
2. 挂载点路径解析
- 功能:user_path_at 将挂载点目录解析为 path 结构(包含 vfsmount 和 dentry)。
- 委托:do_mount 调用 path_mount 处理挂载操作。
3. 参数验证
- 功能:path_mount 验证输入参数并委托给 do_new_mount 处理默认挂载操作。
- 检查:确保文件系统类型和挂载点有效。
4. 文件系统特定挂载
- 功能:do_new_mount 定位文件系统类型并初始化挂载上下文。
- 关键步骤:
- 获取文件系统类型(get_fs_type)。
- 分配并初始化 fs_context 结构(fs_context_for_mount)。
- 调用文件系统的挂载方法或回退到 legacy_init_fs_context。
- 解析挂载选项并检查权限(mount_capable)。
- 调用 vfs_get_tree 创建超级块及相关结构。
5. 集成到全局文件系统树
- 功能:do_new_mount_fc 创建 mount 实例并将其与挂载点和超级块关联。
- 步骤:
- 分配 vfsmount 结构(vfs_create_mount)。
- 关联超级块和根目录项。
- 解析挂载点(lock_mount)。
- 将挂载添加到全局文件系统树(do_add_mount)。
ext2 文件系统挂载详解
以 ext2 为例,我们详细分析其挂载过程,重点关注 vfs_get_tree 和 ext2_fill_super。
1. vfs_get_tree
- 作用:调用文件系统的挂载方法创建超级块、根索引节点和根目录项。
- 对于 ext2:
- 使用传统接口(fs/ext2/super.c 中的 ext2_mount)。
- 委托给 mount_bdev,调用 ext2_fill_super。
2. mount_bdev
- 作用:磁盘文件系统的通用函数,用于创建 VFS 结构。
- 步骤:
- 获取块设备描述符(blkdev_get_by_path)。
- 检查是否存在超级块(sget)或创建新超级块。
- 设置块大小(sb_set_blocksize)。
- 调用 ext2_fill_super 填充超级块。
- 返回根目录项。
3. ext2_fill_super
- 作用:用 ext2 特定的元数据填充 VFS 超级块。
- 关键操作:
- 分配 ext2 专用超级块结构(ext2_sb_info)。
- 读取磁盘超级块到缓冲区(sb_bread)。
- 验证文件系统魔数(EXT2_SUPER_MAGIC = 0xEF53)。
- 设置块大小、索引节点大小和组描述符。
- 读取根索引节点(ext2_iget)并创建根目录项(d_make_root)。
- 更新磁盘上的超级块元数据(ext2_write_super)。
4. 挂载点关联
- 功能:do_new_mount_fc 将文件系统集成到目录树中。
- 关键步骤:
- 分配 vfsmount(vfs_create_mount)。
- 解析挂载点(lock_mount):
- 如果目录不是挂载点,使用其目录项。
- 如果是挂载点,使用最后挂载的文件系统的根目录项。
- 将挂载实例链接到全局树(do_add_mount)。
路径名查找与挂载点
当用户访问文件(如 /mnt/test/file.txt)时,内核执行路径名查找:
- 功能:fs/namei.c 中的 walk_component 处理路径遍历。
- 挂载点处理:
- 检查目录项是否为挂载点(DCACHE_MOUNTED 标志)。
- 使用 lookup_mnt 查找子文件系统的 vfsmount。
- 更新路径到子文件系统的根目录项。
- 示例:
- 挂载:/dev/sda1 → /mnt、/dev/sda2 → /mnt、/dev/sda3 → /mnt。
- 查找 /mnt/test/file.txt:
- 到达 /mnt(挂载点)。
- 遍历到 /dev/sda3 的根目录项。
- 在 /dev/sda3 内继续解析 test/file.txt。
关键洞察
- 挂载必要性:文件系统必须挂载到目录以供用户访问,使路径名查找能够解析到文件系统根目录。
- 单一超级块:一个块设备的文件系统在内存中只有一个超级块,无论挂载到多个挂载点。
- 挂载点隐藏:在目录上挂载新文件系统会隐藏之前的文件系统或内容,直到卸载。
- VFS 抽象:VFS 层确保不同文件系统的无缝集成,对用户透明。
结论
Linux 中的文件系统挂载是一个复杂的过程,通过 VFS 层将块设备集成到目录树中。通过创建和关联超级块、挂载、索引节点和目录项等关键结构,内核实现了无缝的文件访问。以 ext2 为例说明了磁盘文件系统的挂载过程,突显了 VFS 与文件系统特定逻辑的交互。对于系统管理员和内核开发者来说,理解这些机制对于管理 Linux 文件系统至关重要。