深入理解Linux文件系统的挂载过程

深入理解Linux文件系统的挂载过程

深入理解Linux文件系统的挂载过程

1. 背景介绍

在Linux的世界里,一切皆文件。无论是硬盘、鼠标还是网络连接,都可以被视为文件来处理。而文件系统则是管理这些文件的大脑,它负责组织和管理所有的文件,确保我们能够高效地找到它们。然而,仅仅有一个文件系统还不够,我们需要一种方法来打开这扇大门,这就是挂载的概念。挂载,简单来说,就是告诉操作系统:“嘿,这里有本书(或是一堆书),请把它放在这个位置,让我能够阅读。”接下来,我们就一起来探索Linux文件系统的挂载过程吧!

2. 挂载流程总览与流程图

2.1 流程图展示

2.2 简要说明

用户在终端执行 mount 命令,请求挂载特定文件系统。该请求通过系统调用 sys_mount 进入内核。内核中处理挂载逻辑,依次查找和验证合适的文件系统类型,初始化 super_block 和根 inode,完成挂载过程。

下面我们就一步步来看看吧

3. mount 命令与用户空间交互

在用户空间,mount 命令用于将设备或文件系统挂载到一个挂载点。它是与内核进行交互的主要途径。挂载是文件系统操作中至关重要的部分,允许用户访问存储设备中的数据。

例如,执行以下命令:

mount /dev/sda1 /mnt

这条命令将设备 /dev/sda1(通常是一个磁盘分区)挂载到 /mnt 目录。挂载后,用户就可以通过访问 /mnt 来访问该设备上的文件系统内容。

mount 工具根据这些参数构建一个挂载请求,包含设备、挂载点、文件系统类型、挂载选项等信息。如果用户没有指定文件系统类型,mount 工具会尝试自动检测文件系统类型。构建好的挂载请求会通过sys_mount系统调用交给内核进行处理。

4. 进入内核:sys_mount 系统调用

4.1 处理挂载参数与路径解析

当用户通过 mount 命令请求挂载一个文件系统时,该请求会被转换为 sys_mount 系统调用。这一节我们将详细介绍 sys_mount 如何处理挂载参数和路径解析的过程。

sys_mount 首先需要将用户空间的参数复制到内核空间。

路径解析是 sys_mount 的另一个重要任务。内核需要将传入的路径解析为内核中的 dentry 和 vfsmount 结构。dentry 表示文件系统中的一个目录项,而 vfsmount 表示一个挂载点。

struct path path;

err = user_path_at(AT_FDCWD, kernel_dir_name, LOOKUP_FOLLOW, &path);

if (err)

goto out_free_data;

struct dentry *mnt_point = path.dentry;

struct vfsmount *mnt = path.mnt;

挂载选项通过 flags 参数传递。sys_mount 需要解析这些选项并将其应用于挂载过程中。常见的挂载选项包括:

MS_RDONLY: 只读挂载。MS_NOSUID: 不允许设置用户ID或组ID。MS_NODEV: 不允许访问设备文件。MS_NOEXEC: 不允许执行文件。

在完成参数解析和路径解析后,sys_mount 将调用挂载核心函数 do_mount 来处理实际的挂载操作。

4.2 调用内核挂载核心函数 do_mount

do_mount 函数用于处理文件系统的挂载过程。下面是它如何一步步调用到 file_system_type 中的 mount 回调函数的过程:

do_mount 的关键步骤之一是根据文件系统类型来查找对应的 file_system_type 结构。这个结构包含了文件系统的相关操作函数,包括 mount 回调函数。

fs_type = get_fs_type(fstype);

这里的 fstype 是用户传入的文件系统类型的字符串。get_fs_type 函数会查找内核中注册的所有文件系统类型,并返回匹配的 file_system_type 结构体。file_system_type 结构体是文件系统类型的描述符,它包含了对该文件系统的操作

一旦通过 fstype 获取到对应的 file_system_type,do_mount 函数会调用该结构体中的 mount 回调函数:

sb = fs_type->mount(fs_type, flags, dev_name, data);

在这里,fs_type->mount 是注册在 file_system_type 结构中的具体挂载操作函数,它会处理文件系统的具体挂载逻辑。每个文件系统类型(如 ext4, xfs 等)都会提供自己的 mount 函数。当 do_mount 调用 fs_type->mount 时,实际执行的是该文件系统的挂载实现。不同文件系统的 mount 函数会有不同的行为,但大体上都会执行以下操作:

创建超级块(superblock):这是一个描述挂载文件系统的结构。挂载根目录:根目录(root directory)会在超级块中初始化,并设置相关参数。初始化其他资源:例如设备、日志等。

挂载完成后,mount 回调函数会返回一个指向超级块(super_block)的指针,表示挂载成功。do_mount 最终会通过返回值传递给用户空间,告诉用户挂载操作是否成功。

4.3 总结

通过上述步骤,我们可以看到 do_mount 函数是如何逐步调用 file_system_type 结构中的 mount 回调函数来完成文件系统的挂载操作的。这些回调函数负责创建超级块并初始化文件系统的具体逻辑,从而确保文件系统能够正确地挂载到指定的挂载点上。下面我们来详细讨论如何使用file_system_type注册文件系统 。

5. file_system_type 与文件系统注册

5.1 file_system_type 结构体的定义与作用

在 Linux 内核中,file_system_type 结构体用于表示不同类型的文件系统。可以将它类比为 C++ 中的类,而具体实现的文件系统(例如 ext2)则是这个类的实例。这意味着每个文件系统都会有一个对应的 file_system_type 实例,该实例包含了文件系统的基本信息和操作方法。

5.1.1 结构体定义

file_system_type 结构体的定义位于 include/linux/fs.h 文件中,其主要字段包括:

struct file_system_type {

const char *name; // 文件系统名称

int (*mount) (struct file_system_type *, int,

const char *, void *); // 挂载函数指针

struct module *owner; // 拥有该文件系统的模块

...

};

name: 文件系统名称,用于标识文件系统类型。mount: 挂载函数指针,指向一个函数,该函数负责执行特定文件系统的初始化挂载逻辑。owner: 指向拥有该文件系统的模块,通常用于模块卸载时的依赖管理。

5.1.2 mount函数指针

mount 函数指针是 file_system_type 结构体中的一个重要成员。它定义了一个函数,该函数负责初始化并挂载文件系统。典型的 mount 函数实现如下:

// 填充超级块信息

static int myfs_fill_super(struct super_block *sb, void *data, int silent) {

sb->s_magic = MYFS_MAGIC; // 设置文件系统的魔数

sb->s_op = &myfs_super_ops; // 设置超级块操作

sb->s_fs_info = NULL; // 没有额外的文件系统信息

// 创建根目录inode,并将其设置为超级块的根目录

struct inode *root_inode = myfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);

if (!root_inode)

return -ENOMEM; // 内存分配失败

sb->s_root = d_make_root(root_inode); // 创建根目录的dentry

if (!sb->s_root) {

iput(root_inode); // 如果创建失败,释放root_inode

return -ENOMEM;

}

return 0;

}

// 挂载文件系统

static struct dentry *myfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) {

return mount_nodev(fs_type, flags, data, myfs_fill_super); // 使用无设备挂载

}

函数的调用流程如下:file_system_type -> mount -> mount_nodev -> myfs_fill_super

5.1.2.1 fill_super 回调函数

sb: 指向超级块结构体的指针,超级块包含文件系统的重要元数据信息。data: 指向挂载数据的指针,通常用于传递特定于文件系统的选项。silent: 一个布尔值,指示是否在出错时保持静默。

设置文件系统的魔数:

sb->s_magic = MYFS_MAGIC;

这里设置了文件系统的魔数,用于在后续操作中验证文件系统的类型。

设置超级块操作:

sb->s_op = &myfs_super_ops;

这里设置了超级块的操作集,myfs_super_ops 包含了一系列针对超级块的操作函数。

创建根目录inode:

struct inode *root_inode = myfs_get_inode(sb, NULL, S_IFDIR | 0755, 0);

if (!root_inode)

return -ENOMEM; // 内存分配失败

这里创建了根目录的inode,作为这个文件系统的root节点。

创建根目录的dentry:

sb->s_root = d_make_root(root_inode);

if (!sb->s_root) {

iput(root_inode); // 如果创建失败,释放root_inode

return -ENOMEM;

}

这里创建了根目录的 dentry,并将其设置为超级块的根目录, 以后就可以在这个根目录下面创建子目录/文件了。

5.1.2.2 mount 回调函数

fs_type: 指向 file_system_type 结构体的指针,表示要挂载的文件系统类型。flags: 挂载标志,用于指定挂载选项。dev_name: 设备名,对于无设备文件系统(如内存文件系统),可以为空。data: 指向挂载数据的指针,通常用于传递特定于文件系统的选项。

调用 mount_nodev 函数:return mount_nodev(fs_type, flags, data, myfs_fill_super);

这里调用了 mount_nodev 函数,该函数用于挂载无设备文件系统。myfs_fill_super 是一个回调函数,用于填充超级块信息。

5.2 文件系统的注册流程:register_filesystem

在 Linux 内核中,文件系统需要通过 register_filesystem() 函数注册。只有注册后的文件系统类型才可以被挂载。

int register_filesystem(struct file_system_type *fs);

fs: 指向 file_system_type 结构体的指针,表示要注册的文件系统类型。

5.2.1 注册过程详解

内核维护了一个全局链表,其中包含了所有已注册的文件系统类型。这个链表的头节点通常是一个 struct list_head 类型的变量。register_filesystem 函数会将传入的 file_system_type 结构体添加到这个链表中。这样,当用户尝试挂载某个文件系统时,内核可以通过遍历这个链表来找到相应的文件系统类型。

5.2.2 示例代码

以下是一个简单的示例,展示了如何在一个模块中注册一个文件系统:

#include

#include

#include

static struct file_system_type myfs_type = {

.name = "myfs",

.mount = myfs_mount,

.owner = THIS_MODULE,

};

static int __init myfs_init(void) {

int ret;

ret = register_filesystem(&myfs_type);

if (ret) {

printk(KERN_ERR "Failed to register filesystem: %d\n", ret);

return ret;

}

printk(KERN_INFO "Filesystem 'myfs' registered successfully.\n");

return 0;

}

static void __exit myfs_exit(void) {

unregister_filesystem(&myfs_type);

printk(KERN_INFO "Filesystem 'myfs' unregistered.\n");

}

module_init(myfs_init);

module_exit(myfs_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("Your Name");

MODULE_DESCRIPTION("A simple example of a custom filesystem.");

myfs_type: 定义了一个 file_system_type 结构体,表示自定义的文件系统类型。myfs_init: 模块初始化函数,调用 register_filesystem 注册文件系统。myfs_exit: 模块退出函数,调用 unregister_filesystem 卸载文件系统。

5.3 文件系统的查找与匹配

当用户尝试挂载一个文件系统时,内核会通过查找全局链表中的 file_system_type 结构来匹配适合的文件系统类型。

5.3.1 查找过程详解

用户请求挂载:

用户通过 mount 命令或系统调用请求挂载一个文件系统。例如:

mount -t ext4 /dev/sda1 /mnt

内核解析请求:

内核接收到挂载请求后,会解析命令行参数,提取文件系统类型(如 ext4)、设备名(如 /dev/sda1)和挂载点(如 /mnt)。

查找文件系统类型:

内核会遍历全局链表中的 file_system_type 结构,查找与请求的文件系统类型匹配的项。这个查找过程通常通过字符串比较来完成,比较 file_system_type 结构中的 name 字段。

调用挂载函数:

一旦找到匹配的 file_system_type 结构,内核会调用该结构中的 mount 函数指针,执行具体的挂载操作。例如,对于 ext4 文件系统,内核会调用 ext4_mount 函数。

5.3.2 示例代码

以下是一个简化的示例,展示了内核如何查找并调用文件系统的挂载函数:

#include

#include

#include

// 全局链表头

static LIST_HEAD(file_systems);

// 查找并挂载文件系统

struct dentry *find_and_mount(const char *fstype, const char *dev_name, void *data) {

struct file_system_type *fs_type;

struct list_head *pos;

// 遍历全局链表

list_for_each(pos, &file_systems) {

fs_type = list_entry(pos, struct file_system_type, next);

if (strcmp(fs_type->name, fstype) == 0) {

// 找到匹配的文件系统类型

return fs_type->mount(fs_type, 0, dev_name, data);

}

}

// 没有找到匹配的文件系统类型

return ERR_PTR(-ENODEV);

}

file_systems: 全局链表头,用于存储所有已注册的文件系统类型。find_and_mount: 查找并挂载文件系统的函数。它遍历全局链表,查找与请求的文件系统类型匹配的项,并调用其 mount 函数。

5.3.3 总结

通过全局链表,内核能够高效地管理和查找已注册的文件系统类型。当用户请求挂载一个文件系统时,内核会遍历这个链表,找到匹配的文件系统类型,并调用其挂载函数来完成挂载操作。

相关内容

Bose QC35耳机充电时间与使用技巧全解析
365bet安卓

Bose QC35耳机充电时间与使用技巧全解析

📅 10-05 👁️ 5124
達摩祖師洞
365bet安卓

達摩祖師洞

📅 08-01 👁️ 5777
塞尔达树桩上有个树叶
365bet安卓

塞尔达树桩上有个树叶

📅 08-08 👁️ 3735
Rapper小老虎:活明白的中国嘻哈范儿
365体育投注账号被冻结

Rapper小老虎:活明白的中国嘻哈范儿

📅 09-21 👁️ 4793