Linux VFS文件系统分析2(基于Linux6.6)---VFS注册与挂载介绍
一、文件系统类型相关的处理
文件系统类型的处理包括文件系统的注册、挂载、文件操作、超级块管理等。
1. 文件系统类型的基本概念
Linux 中的文件系统是通过 struct file_system_type
来定义和管理的。每种文件系统类型(例如 ext4、xfs、tmpfs 等)都需要实现该结构中的一些必要字段和函数。
struct file_system_type
这个结构体包含了 Linux 文件系统的类型信息,下面是它的一些关键字段:
- name:文件系统的名字。
- fs_flags:文件系统的标志,通常包括
FS_REQUIRES_DEV
、FS_NONSHAREABLE
等。 - mount:文件系统挂载函数,负责挂载操作。
- kill_sb:清理超级块的函数。
- fs_ops:指向文件系统操作结构体(
struct super_operations
)的指针。
struct file_system_type {
const char *name; // 文件系统类型的名称
int fs_flags; // 文件系统的标志
struct super_operations *s_op; // 超级块操作
struct dentry_operations *d_op; // 目录项操作
int (*mount)(struct file_system_type *fs_type, int flags, const char *dev_name, void *data);
void (*kill_sb)(struct super_block *sb);
};
2. 文件系统注册和注销
在内核中,每种文件系统都需要注册到 VFS 系统中。这通常通过 register_filesystem()
函数来实现,而注销则使用 unregister_filesystem()
。
注册文件系统
注册文件系统的过程主要是在 fs_type
结构中定义文件系统类型并调用 register_filesystem()
来完成注册。成功注册后,VFS 就可以识别并使用这个文件系统类型。
int register_filesystem(struct file_system_type *fs_type);
注销文件系统
当文件系统不再使用时,需要将其从 VFS 中注销。这通过 unregister_filesystem()
来完成:
void unregister_filesystem(struct file_system_type *fs_type);
3. 超级块(Superblock)
每个挂载的文件系统都有一个超级块(super_block
),它包含了文件系统的元数据和状态信息。文件系统在初始化时,VFS 会为每个挂载的文件系统创建一个超级块。文件系统操作会通过这个超级块来处理实际的文件系统操作。
struct super_block
关键字段:
- s_op:指向文件系统的超级块操作结构体,包含文件系统的特定操作。
- s_fs_info:指向文件系统特定数据的指针。
- s_flags:文件系统的标志。
struct super_block {
struct super_operations *s_op;
void *s_fs_info; // 文件系统的私有数据
unsigned long s_flags; // 文件系统的标志
struct dentry *s_root; // 根目录项
};
4. 文件系统挂载(Mounting)
挂载是将文件系统与内核的 VFS 连接的过程。内核通过挂载点把存储设备或文件系统映射到 VFS 树上,使得用户可以通过路径访问文件。
挂载过程通常通过实现 mount
函数来处理。文件系统类型通过 struct file_system_type
中的 mount
函数进行挂载操作。
挂载函数原型:
struct dentry *(*mount)(struct file_system_type *fs_type, int flags, const char *dev_name, void *data);
fs_type
:文件系统类型。flags
:挂载标志(如只读、强制挂载等)。dev_name
:设备名,指示挂载的设备(如磁盘分区、虚拟设备等)。data
:挂载时需要的额外数据。
挂载函数返回一个 dentry
指针,表示文件系统的根目录。如果挂载失败,则返回 ERR_PTR
。
5. 文件系统操作
文件系统操作通常包括文件的打开、读取、写入、关闭、删除等。这些操作由 file_operations
和 inode_operations
来实现。
file_operations
示例:
文件操作结构体定义了文件的基本操作,如 open
、read
、write
、close
等。
struct file_operations {
struct module *owner;
int (*open)(struct inode *inode, struct file *file);
ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *pos);
ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *pos);
int (*release)(struct inode *inode, struct file *file);
};
open
:文件打开操作。read
:文件读取操作。write
:文件写入操作。release
:文件关闭操作。
inode_operations
示例:
inode_operations
用于定义对 inode
(索引节点)的操作,如 create
、lookup
、unlink
等。
struct inode_operations {
int (*create)(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl);
struct dentry *(*lookup)(struct inode *dir, struct dentry *dentry, unsigned int flags);
int (*unlink)(struct inode *dir, struct dentry *dentry);
};
6. 清理超级块(Kill Superblock)
每个文件系统类型都必须实现一个清理超级块的函数。清理超级块的函数会在文件系统卸载时被调用,用于释放文件系统占用的资源。
void (*kill_sb)(struct super_block *sb);
7. 总结:文件系统类型的处理过程
在 Linux 内核中,文件系统类型的处理主要涉及以下步骤:
- 定义文件系统类型: 使用
struct file_system_type
定义文件系统类型,并提供挂载、卸载等操作。 - 注册文件系统类型: 通过
register_filesystem()
将文件系统类型注册到内核的 VFS 中。 - 挂载文件系统: 实现
mount
函数,处理挂载操作,将设备或文件系统映射到 VFS。 - 管理超级块: 每个挂载的文件系统都由一个超级块进行管理,它存储了文件系统的状态和元数据。
- 文件操作和 inode 操作: 实现文件操作(
file_operations
)和 inode 操作(inode_operations
),完成文件系统的基本文件操作。 - 清理超级块: 文件系统卸载时,通过
kill_sb()
函数释放相关资源。
二、文件系统类型注册
Linux中所有注册的文件系统类型,都会通过next指针链接在一起。文件系统类型相关的变量以及每一个文件系统类型的超级块的链表连接如下图所示。而一个文件系统类型又会通过fs_supers表头,将所有该文件系统挂载所创建的超级块节点关联,如下即为这两者间的关联。
文件系统类型的注册接口 register_filesystem,该接口主要实现以下功能:
- 判断该文件系统是否已注册,若已注册,则返回失败;
- 若该文件系统未注册,则将该文件系统指针加入到register_filesystem变量对应链表的尾部。
fs/filesystems.c
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
if (fs->parameters &&
!fs_validate_description(fs->name, fs->parameters))
return -EINVAL;
BUG_ON(strchr(fs->name, '.'));
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
EXPORT_SYMBOL(register_filesystem);
接口 find_filesystem用于根据文件系统名查找文件系统,若查找到则返回文件系统对应的指针,
若查找失败,则返回NULL;
fs/filesystems.c
static struct file_system_type **find_filesystem(const char *name, unsigned len)
{
struct file_system_type **p;
for (p = &file_systems; *p; p = &(*p)->next)
if (strncmp((*p)->name, name, len) == 0 &&
!(*p)->name[len])
break;
return p;
}
文件系统类型的注销接口为 unregister_filesystem,该接口实现的功能如下:
1.查找file_systems对应的链表中是否存在该文件系统类型,若存在,则删除该文件系统。
fs/filesystems.c
int unregister_filesystem(struct file_system_type * fs)
{
struct file_system_type ** tmp;
write_lock(&file_systems_lock);
tmp = &file_systems;
while (*tmp) {
if (fs == *tmp) {
*tmp = fs->next;
fs->next = NULL;
write_unlock(&file_systems_lock);
synchronize_rcu();
return 0;
}
tmp = &(*tmp)->next;
}
write_unlock(&file_systems_lock);
return -EINVAL;
}
EXPORT_SYMBOL(unregister_filesystem);
三、文件系统挂载
针对文件系统的挂载,在应用层可通过接口mount实现挂载操作,而在linux内核下,则是sys_mount接口,针对mount接口调用流程图如下。mount的作用即调用文件系统类型中的mount接口,用于创建superblock、根root、根dentry等内容。关于mount的系统调用如下,在mount_fs接口中,则通过type->mount调用注册文件系统的mount接口,实现创建superblock、根root、根dentry。
如在下面编写的stestfs测试文件系统而言,即为调用stest_fs_mount,并调用stestfs_fill_super最终实现根dentry、根inode的创建。
四、文件系统类型的例子
要理解 Linux 文件系统类型的注册、文件挂载、文件创建、目录创建以及涉及的 inode
、dentry
、superblock
等变量之间的关系,可以通过以下几个步骤来实现:首先,我们定义一个新的文件系统类型;然后实现文件系统挂载和文件、目录操作的基本框架。
1. 新文件系统类型的注册
在 Linux 中,文件系统类型的注册主要是通过 struct file_system_type
来实现的。这个结构体定义了文件系统的名称、挂载函数、文件系统操作等。在注册后,内核会通过虚拟文件系统(VFS)来调用该文件系统的操作。
文件系统类型的结构体 file_system_type
file_system_type
是 Linux 内核用于注册和管理文件系统类型的结构体。每种文件系统类型需要定义以下几个关键的字段:
name
:文件系统类型的名字,内核通过这个名字识别不同的文件系统。fs_flags
:文件系统的一些标志,通常包括文件系统是否需要设备、是否支持只读等。mount
:文件系统挂载函数。kill_sb
:卸载文件系统时清理超级块的函数。s_op
:指向文件系统的超级块操作结构体(super_operations
)。
2. 设计一个简单的文件系统
以下是创建一个简单的文件系统类型的例子。这个例子简化了很多实际中复杂的操作,只实现了最基本的框架,用于展示文件系统类型注册、挂载、文件操作、目录操作及其与 inode
、dentry
、superblock
等变量之间的关系。
创建一个简单的 myfs
文件系统
假设我们要创建一个名为 myfs
的文件系统类型,简化版的实现如下:
a) 定义 myfs
文件系统类型
首先,我们需要定义文件系统的基本结构,如超级块操作、文件操作、inode 操作等。
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/vfs.h>
#include <linux/mount.h>
#include <linux/errno.h>
#include <linux/dcache.h>
// 超级块操作
static struct super_operations myfs_super_ops = {
.statfs = simple_statfs, // 使用简单的 statfs 操作
.drop_inode = generic_delete_inode, // 使用内核默认的删除 inode 操作
};
// inode 操作
static struct inode_operations myfs_inode_ops = {
.create = simple_create, // 使用简单的文件创建操作
};
// 文件操作
static struct file_operations myfs_file_ops = {
.read = generic_file_read,
.write = generic_file_write,
};
// 挂载函数
static int myfs_mount(struct file_system_type *fs_type, int flags, const char *dev_name, void *data) {
struct super_block *sb;
sb = sget(fs_type, NULL, set_anon_super, flags, NULL);
if (IS_ERR(sb)) {
return PTR_ERR(sb);
}
sb->s_op = &myfs_super_ops;
sb->s_root = d_make_root(NULL); // 创建根目录项
return 0;
}
// 卸载文件系统时清理超级块
static void myfs_kill_sb(struct super_block *sb) {
// 在这里执行文件系统卸载时的资源清理
pr_info("myfs: Killing superblock\n");
kill_litter_super(sb);
}
// 文件系统类型定义
static struct file_system_type myfs_fs_type = {
.name = "myfs", // 文件系统类型名称
.mount = myfs_mount, // 挂载函数
.kill_sb = myfs_kill_sb, // 清理超级块函数
.fs_flags = FS_REQUIRES_DEV, // 文件系统需要设备
};
b) 初始化文件系统
在模块加载时,我们需要注册这个文件系统类型,并通过 register_filesystem()
启动它。
static int __init myfs_init(void) {
int ret;
pr_info("myfs: Registering file system\n");
// 注册文件系统类型
ret = register_filesystem(&myfs_fs_type);
if (ret != 0) {
pr_err("myfs: Failed to register file system\n");
}
return ret;
}
static void __exit myfs_exit(void) {
pr_info("myfs: Unregistering file system\n");
// 注销文件系统类型
unregister_filesystem(&myfs_fs_type);
}
module_init(myfs_init);
module_exit(myfs_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ChatGPT");
MODULE_DESCRIPTION("A simple example of a Linux filesystem");
在这个代码片段中,我们实现了以下功能:
- 注册文件系统类型
myfs
,并定义了挂载和卸载函数。 - 超级块操作:通过
myfs_super_ops
来定义超级块操作,包括文件系统状态的获取(statfs
)和 inode 的删除(drop_inode
)。 - 文件操作:提供了简单的文件读写操作。
- 挂载函数:通过
myfs_mount
函数挂载文件系统,并创建根目录项。 - 卸载函数:通过
myfs_kill_sb
清理超级块。
3. 超级块(superblock)、目录项(dentry)、索引节点(inode)的关系
-
superblock:每个挂载的文件系统都有一个超级块(
super_block
),它包含了文件系统的元数据和状态信息。在我们的例子中,super_block
会在挂载时由sget
函数创建,并且它的s_op
会指向myfs_super_ops
,即我们为文件系统定义的超级块操作结构。 -
inode:每个文件和目录都有一个
inode
,它代表文件的元数据。在我们的例子中,inode_operations
中的create
和lookup
等操作决定了文件或目录的创建和查找方式。 -
dentry:
dentry
是目录项,它是dentry
缓存中的元素,指向具体的inode
。在文件系统挂载时,d_make_root
创建了文件系统的根目录项,并将它作为超级块的根目录。
4. 挂载文件系统
在 myfs_mount
函数中,我们通过 sget
获取一个超级块,并将文件系统挂载到 VFS 中。此时,s_root
将指向文件系统的根目录项。
sb->s_root = d_make_root(NULL); // 创建根目录项
d_make_root
会返回一个 dentry
,它指向文件系统的根目录。根目录的 dentry
将与一个 inode
相关联,而 inode
中包含了该目录的元数据。
5. 目录创建和文件创建
-
文件创建:文件创建过程通过
create
操作来完成。我们实现的myfs_inode_ops.create
操作会调用simple_create
,它是一个简化的文件创建函数。 -
目录创建:在 Linux 中,目录本质上也是一个文件,只不过它包含了其他文件或目录的
dentry
和inode
。simple_create
同样可以用于目录的创建。
6. 卸载文件系统
文件系统卸载时,myfs_kill_sb
会被调用,来执行文件系统的资源清理工作。我们可以在这里释放 superblock
和其他与文件系统相关的资源。
static void myfs_kill_sb(struct super_block *sb) {
pr_info("myfs: Killing superblock\n");
kill_litter_super(sb); // 清理超级块
}
7. 总结
我们创建了一个简单的 Linux 文件系统类型 myfs
,并注册到内核。通过实现挂载、卸载、文件和目录操作等基本功能,我们展示了 superblock
、inode
、dentry
之间的关系:
- superblock:管理文件系统的元数据和操作。
- inode:存储文件或目录的元数据。
- dentry:目录项,指向 inode,并用于文件路径解析。