![d52e18bab9db76f3c8ea6b7e227e0f9a.png](https://i-blog.csdnimg.cn/blog_migrate/a21c236ebca65a4613a4c2af5a991895.jpeg)
前言
该从哪里开始说文件系统呢?可能很多人有不同的见解,可能有人喜欢从一个具体文件系统的on-disk结构开始说,有人喜欢从VFS的基本元素开始说,我比较倾向从挂载一个文件系统开始。应该说各有利弊吧,上来就说on-disk结构,就像上来就背一堆公式告诉你以后用一样会一头雾水。先说VFS层inode, super block, dentry, file等概念的话,也很难理解其到底对应什么。直接从mount开始说的话,离开VFS的基本概念可能理解起来会有些困难。所以最好先对VFS层的基本数据结构有一些了解但不用太深入,那样最好了。所以阅读此文的前期要求是:
先了解dentry, inode, superblock这三个基本概念
如果你对此一无所知,那请看过后再来。我下面会用一句话简单带过这三个概念,起一个头:
Superblock: 常缩写为sb(还好老外不懂中文),是一个文件系统的头部,一般在一个文件系统的第一个block里,存储着文件系统的最主要信息,如文件系统名称,大小,块大小,inode大小,有多少inode已用和未用,有多少空闲block等等。与on-disk的superblock对应。
inode: 一个inode对应一个客体文件(或目录),携带这个文件的所有元信息,包括文件属性、占用空间等信息。其与On-disk的inode对应。
dentry: 是directory entry的缩写,是一个抽象话概念,不对应On-disk结构(或者你把路径信息理解成dentry,当然这个不严谨)。从其字面意思也可以看出它是内核为构建和访问目录树形结构服务的,是目录与其下文件的缓存,主要信息包括文件名和其parent目录名。一个dentry链表常常是可以从一个文件向上追踪其每一个parent directory,直到root。注意,在内核中可能出现多个dentries结构对应一个inode,比如硬链接就是两个不同名的文件对应一个inode。
好了,多说无用,想具体了解还是自己去看书看代码吧。我不可能用几句话就让你知道这些是什么。在讲到mount操作之前,需要先知道一个文件系统模块是怎么和内核关联起来的,好让内核可以在文件系统挂载时准确找到这个文件系统的模块。所以我们的第一个概念从一个叫file_system_type的概念开始说。
file_system_type
先来看一下定义(来自Linux v4.17-rc2):
struct file_system_type {
const char *name;
int fs_flags;
#define FS_REQUIRES_DEV 1
#define FS_BINARY_MOUNTDATA 2
#define FS_HAS_SUBTYPE 4
#define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */
#define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */
struct dentry *(*mount) (struct file_system_type *, int,
const char *, void *);
void (*kill_sb) (struct super_block *);
struct module *owner;
struct file_system_type * next;
struct hlist_head fs_supers;
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
来看一下主要域的意思(个别说明不一定完全准确):
name: 文件系统的名字,如xfs, ext2等
fs_flags: 说明文件系统的类型,下面的宏定义代表了它的几种类型:
- FS_REQUIRES_DEV: 文件系统必须在物理设备上。
- FS_BINARY_MOUNTDATA: mount此文件系统时(参见mount_fs函数 - fs/super.c)需要使用二进制数据结构的mount data(如每个位域都有固定的位置和意义),常见的nfs使用这种mount data(参见struct nfs_mount_data结构 - include/uapi/linux/nfs_mount.h)。
- FS_HAS_SUBTYPE: 文件系统含有子类型,最常见的就是FUSE,FUSE本是不是真正的文件系统,所以要通过子文件系统类型来区别通过FUSE接口实现的不同文件系统。
- FS_USERNS_MOUNT: 文件系统每次挂载都后都是不同的user namespace,如用于devpts。
- FS_RENAME_DOES_D_MOVE: 文件系统将把重命名操作reame()直接按照移动操作d_move()来处理,主要用于网络文件系统。
mount: 代替早期的get_sb(),用户挂载此文件系统时使用的回调函数。
kill_sb: 删除内存中的super block,在卸载文件系统时使用。
owner: 指向实现这个文件系统的模块,通常为THIS_MODULE宏。
next: 指向文件系统类型链表的下一个文件系统类型。
fs_supers: 此文件系统类型的文件系统超级块结构都串连在这个表头下。
对于一个文件系统,定义好自己的file_system_type结构后就需要将自己注册进内核。
register_filesystem
参考fs/filesystem.c,这个文件不长,在开头有一个重要的全局变量:
static struct file_system_type *file_systems;
这个指针将指向所有已注册的文件系统模块。
看过这个全局变量后我们接着看register_filesystem函数,其定义与解释都也很详细,如下(来自Linux 4.17-rc2, fs/filesystem.c)
/**
* register_filesystem - register a new filesystem
* @fs: the file system structure
*
* Adds the file system passed to the list of file systems the kernel
* is aware of for mount and other syscalls. Returns 0 on success,
* or a negative errno code on an error.
*
* The &struct file_system_type that is passed is linked into the kernel
* structures and must not be freed until the file system has been
* unregistered.
*/
int register_filesystem(struct file_system_type * fs)
{
int res = 0;
struct file_system_type ** p;
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;
}
里面最主要的逻辑是find_filesystem调用及指针p的处理。先来看find_filesystem的定义:
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;
}
for循环一开始的file_systems变量,就是我们上面说的存储着所有已注册文件系统的全局指针。strncmp调用也很明确,就是比较file_system_type的第一项name(文件系统名)和要注册的文件系统名字是否相同。就这样一直找,如果找到同名的,break的时候p就是指向找到的同名file_system_type结构的指针,如果没找到就是指向NULL的指针(注意p本身不是NULL)。
在返回register_filesystem函数后,判断返回值,如果找到重复的则返回EBUSY错误,如果没找到重复的,就把当前要注册的文件系统挂到尾端file_system_type的next指针上,串联进链表。至此一个文件系统模块就注册好了。
当然与register相对应的就是unregister函数,这里就不说它了。在fs/filesystem.c里还有一个很重要的函数是get_fs_type(),它接受一个文件系统的名称作为参数,然后反向找到其对应的file_system_type实例,这个在mount的时候是很常用的。
说了怎么注册一个文件系统,那一个具体的文件系统的file_systemtype长什么样呢?
我们以XFS的为例看一下(Linux 4.17-rc2 fs/xfs/xfs_super.c):
static struct file_system_type xfs_fs_type = {
.owner = THIS_MODULE,
.name = "xfs",
.mount = xfs_fs_mount,
.kill_sb = kill_block_super,
.fs_flags = FS_REQUIRES_DEV,
};
这是XFS的file_systemtype的实例,name表明其是xfs,fsflags表明其是必须用在存储设备上的文件系统,不是虚拟文件系统等。最重要的两个回调函数"mount"和"killsb"就是两个需要XFS模块来实现的带有具体细节的函数了。mount是在mount操作时会被调用的函数,kill_sb一般会在umount的时候使用。这里就不展开展开具体细节了,留到后面说到具体mount操作时再说。
至此我们了解了file_system_type,已经如何注册使用。一个file_system_type最主要告诉内核三件事:“我叫什么”,“怎么挂载我”以及“怎么卸载我”(多么纯粹的让人感动:)。
更多内容请参阅:
醉卧沙场:README - 专栏文章总索引