对于Linux延续着Unix的一个哲学思想:一切皆文件,应用看到的所有对象都是文件。应用程序通过系统调用陷入内核中,访问内核的资源,VFS作为通用文件系统接口,通过各种不同文件系统的实现,主要是以块设备为基础的物理文件系统,例如EXT2/EXT3等,对于VFS层通过不断的演变支持了更多的文件系统,其主要是中间层,对上提供POSIX API,对下对接不同的文件系统驱动
- 向上主要是提供file_operations抽象接口,包括文件的打开,读写,mmap映射,ioctl等,用户空间无须考虑具体的文件系统和实际的存储介质。
- 向下则要兼容个各种不同的文件系统
如果想要自己实现一个文件系统的话只要实现一个满足 VFS 层的文件系统就能加入到内核当中,在新的文件系统只需要遵照VFS规范即可。
下面我们先来简单看一下典型的基于块的文件系统是如何组织的,VFS作为这些块设备文件系统在内存中的代表,抽象出大部分文件系统类型的共性。
首先,我们需要来回顾一下典型的基于inode的文件系统是如何组织的(例如windows是基于table的组织形式)
当分区被挂载时,会将硬盘上的信息读取到内存中,并且抽象成通用的对象,主要有super_block和inode对象。
下面我们基于simplefs学习,VFS是如何兼容不同的文件系统,有何方法和步骤。
1 simplefs文件系统架构
simplefs中是以一个block(4K)为存储单位,下面是simplefs的partition layout,以及每个partition中能存放的block数量。
simplefs partition layout
+---------------+
| superblock | 1 block
+---------------+
| inode store | sb->nr_istore_blocks blocks
+---------------+
| ifree bitmap | sb->nr_ifree_blocks blocks
+---------------+
| bfree bitmap | sb->nr_bfree_blocks blocks
+---------------+
| data |
| blocks | rest of the blocks
+---------------+
這樣的 partition layout 是在檔案系統格式化時建立的,也就是 mkfs.c
中所執行的內容,以下逐一說明各個 partition 的細節
1.1 超级块
功能跟一般的文件系统中的superblock一样,存放关于整个文件系统的metadata信息。其信息如下:
struct simplefs_sb_info {
uint32_t magic; /* Magic number */
uint32_t nr_blocks; /* Total number of blocks (incl sb & inodes) */
uint32_t nr_inodes; /* Total number of inodes */
uint32_t nr_istore_blocks; /* Number of inode store blocks */
uint32_t nr_ifree_blocks; /* Number of inode free bitmap blocks */
uint32_t nr_bfree_blocks; /* Number of block free bitmap blocks */
uint32_t nr_free_inodes; /* Number of free inodes */
uint32_t nr_free_blocks; /* Number of free blocks */
#ifdef __KERNEL__
unsigned long *ifree_bitmap; /* In-memory free inodes bitmap */
unsigned long *bfree_bitmap; /* In-memory free blocks bitmap */
#endif
};
struct superblock {
struct simplefs_sb_info info;
char padding[4064]; /* Padding to match block size */
};
原本struct simplefs_sb_info只有8*4个byte的大小,但为了使super block对齐刚好一个block大小,struct superblock结构中外加了4064个byte的padding。
1.2 索引节点
在simplefs中的inode定义如下,每个大小为72byte:
struct simplefs_inode {
uint32_t i_mode; /* File mode */
uint32_t i_uid; /* Owner id */
uint32_t i_gid; /* Group id */
uint32_t i_size; /* Size in bytes */
uint32_t i_ctime; /* Inode change time */
uint32_t i_atime; /* Access time */
uint32_t i_mtime; /* Modification time */
uint32_t i_blocks; /* Block count */
uint32_t i_nlink; /* Hard links count */
union {
uint32_t ei_block; /* Block with list of extents for this file */
uint32_t dir_block; /* Block with list of files for this directory */
};
char i_data[32]; /* store symlink content */
};
每个block都必须要要有一个对应的inode,因此一个Block最多可以存储4096/72=56个inode
1.3 inode位图
表示一个Inode的使用情况,每个inode都用一个Bit表示,使用中的inode在表中标记为0,反之则标记为1
1.4 数据位图
同Inode位图一样,但这个是记录的block data的使用情况
1.5 数据块
前面提到的superblock,inode位图等,都是用来存放数据的Metadata信息,而这个数据块是用来存放data的区域,整个data blocks partition中被切割成4KB一个的block为单位,每个block可以分为以下几种情况
-
存放目录inode的内容信息
当block被某个inode中dir_block,这个block将用来存储整个目录下的文件信息,其结构定义如下:
#define SIMPLEFS_FILENAME_LEN 28
#define SIMPLEFS_MAX_SUBFILES 128
struct simplefs_dir_block {
struct simplefs_file {
uint32_t inode; // 檔案的 inode 編號
char filename[SIMPLEFS_FILENAME_LEN]; // 檔案名稱
} files[SIMPLEFS_MAX_SUBFILES]; // 目錄下的所有檔案
};
其中的struct simplefs_file中的filename长度是28 byte,加上一个inode 4 byte,乘上有128个struct simplefs_file(28+4)*128=4096,刚好会有一个block的大小
# inode 與 directory data block 對應示意圖
inode
+-----------------------+
| i_mode = IFDIR | 0755 | block 123
| dir_block = 123 ----|--------> +-----------+
| i_size = 4 KiB | 0 | 24 (foo) |
| i_blocks = 1 | |-----------|
+-----------------------+ 1 | 45 (bar) |
|-----------|
| ... |
|-----------|
127 | 0 |
+-----------+
-
存储inode的内容信息
当block被某个inode中simplefs_inode位ei_block这个置位,这个Block将用来存储extent block的编号以及长度,其结构定义如下:
struct simplefs_extent {
uint32_t ee_block; /* first logical block extent covers */
uint32_t ee_len; /* number of blocks covered by extent */
uint32_t ee_start; /* first physical block extent covers */
};
struct simplefs_file_ei_block {
uint32_t nr_files; /* Number of files in directory */
struct simplefs_extent extents[SIMPLEFS_MAX_EXTENTS];
};
其中的一个simplefs\_extent 大小是3个uint32\_t共有12byte,乘上有341个struct simplefs\_extent共12\*341=4092个byte,差不多是一个Block的大小
# inode 與 file data block 對應示意圖
inode
+-----------------------+
| i_mode = IFDIR | 0644 | block 93
| ei_block = 93 ----|------> +----------------+
| i_size = 10 KiB | 0 | ee_block = 0 |
| i_blocks = 25 | | ee_len = 8 |
+-----------------------+ | ee_start = 94 |
|----------------|
1 | ee_block = 8 |
| ee_len = 8 |
| ee_start = 99 |
|----------------|
2 | ee_block = 16 |
| ee_len = 8 |
| ee_start = 66 |
|----------------|
| ... |
|----------------|
341 | ee_block = 0 |
| ee_len = 0 |
| ee_start = 0 |
+----------------+
- 存储文件内容的extent block
struct simplefs_extent {
uint32_t ee_block; /* first logical block extent covers */
uint32_t ee_len; /* number of blocks covered by extent */
uint32_t ee_start; /* first physical block extent covers */
};
struct simplefs_extent
+----------------+
| ee_block = 0 |
| ee_len = 200| extent
| ee_start = 12 |-----------> +---------+
+----------------+ block 12 | |
+---------+
13 | |
+---------+
| ... |
+---------+
211 | |
+---------+
2 注册文件系统到VFS
对于文件系统的注册,可以使用以下的接口完成注册和删去
#include <linux/fs.h>
extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);
其中 file_system_type详细的见
文件系统(五) — 图解虚拟文件系统结构中说明,对于simplefs其注册接口为:
static int __init simplefs_init(void)
{
int ret = simplefs_init_inode_cache();
if (ret) {
pr_err("inode cache creation failed\n");
goto end;
}
ret = register_filesystem(&simplefs_file_system_type);
if (ret) {
pr_err("register_filesystem() failed\n");
goto end;
}
pr_info("module loaded\n");
end:
return ret;
}
其提供了fs_type类型,此时内核就支持了simplefs的文件系统
static struct file_system_type simplefs_file_system_type = {
.owner = THIS_MODULE,
.name = "simplefs",
.mount = simplefs_mount,
.kill_sb = simplefs_kill_sb,
.fs_flags = FS_REQUIRES_DEV,
.next = NULL,
};
3. 文件系统的创建
一般需要将需要存储的分区使用格式化的工具格式化成指定的文件系统,例如我们使用的ext4,使用mkfs.ext4工具,而对于simplefs,也提供了mkfs-simplefs的工具
一般格式化文件系统很简单,无外乎如下图所示的两行命令
先dd一个内存文件400KB,其中bs=4KB,一共100个,再执行命令如下图所示
linux系统里一切皆文件,磁盘也不例外。照样可以使用文件操作函数write、open等系统调用进读写访问,通过这些操作,可以充分理解linux一切皆文件。 所以格式化磁盘文件系统的本质就是在每一个块中填充超级块,inode块等结构体信息。在simplefs文件系统中只填充了一次sb、inode。
4 文件系统的挂载
当执行装载操作时,结构体之间的关联关系如下图所示,其中,vfsmount结构体只存在于内存中,其他三个结构体不仅存在于内存中,也存在于磁盘中。首先,当格式化一个文件系统时,磁盘中已经建立有sb、inode、dentry,装载过程就是在内存中创建vfsmount结构体,并让vfsmount结构体和sb、inode和dentry三个结构体之间建立关联的过程。
当我们使用mount命令时候,就会调用
/* Mount a simplefs partition */
struct dentry *simplefs_mount(struct file_system_type *fs_type,
int flags,
const char *dev_name,
void *data)
{
struct dentry *dentry =
mount_bdev(fs_type, flags, dev_name, data, simplefs_fill_super);
if (IS_ERR(dentry))
pr_err("'%s' mount failure\n", dev_name);
else
pr_info("'%s' mount success\n", dev_name);
return dentry;
}
关于在文件系统中创建一个文件,从数据结构角度看,是创建了一个inode,则要做的事情是如何在super block、inode以及dentry等几个结构体中管理这个inode。从这个流程来看,一共有如下几个步骤
-
首先获取vfs层sb结构体,通过vsb找到simplefs文件系统sb结构体,最重要的是simplefs_sops
-
在vfs sb结构体里创建一个inode对象,文件系统元素的创建,不管是inode、dentry、sb、还是vfsmount,都会填充该元素内容
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q7JXP6kx-1653227147359)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/e3dcb21d-31c7-49c1-9f6e-6a57cc906fa8/Untitled.png)]
inode的操作函数
static const struct inode_operations simplefs_inode_ops = {
.lookup = simplefs_lookup,
.create = simplefs_create,
.unlink = simplefs_unlink,
.mkdir = simplefs_mkdir,
.rmdir = simplefs_rmdir,
.rename = simplefs_rename,
.link = simplefs_link,
.symlink = simplefs_symlink,
};
file的操作函数
const struct address_space_operations simplefs_aops = {
.readpage = simplefs_readpage,
.writepage = simplefs_writepage,
.write_begin = simplefs_write_begin,
.write_end = simplefs_write_end,
};
const struct file_operations simplefs_file_ops = {
.llseek = generic_file_llseek,
.owner = THIS_MODULE,
.read_iter = generic_file_read_iter,
.write_iter = generic_file_write_iter,
.fsync = generic_file_fsync,
};
5总结
VFS定义了超级块,inode,dentry,文件及其操作的通用模型。我们注册的钩子定义了实际文件系统的功能。
- 首先,我们需要定义文件系统的格式,并写工具用于生成对应的分区信息
- 定义file_system_type,super_block(文件系统的总体信息),inode结构,主要是提供i_ops:inode_operations,dentry的信息和最重要的file_operations:i_fops
- 我们需要定义注册文件系统到VFS,然后我们通过Mount就可以使用对应的文件系统了