在上一篇文章中,我们实现了一个可以正确挂载 / 卸载的简单文件系统,由于没有实现任何操作存储设备的代码,所以我们的文件系统其实根本无法存储文件,现在,是时候来给我们的文件系统设计一个数据存储格式了!
# 数据存储格式
在 Linux 文件系统模型与 VFS 入门这篇文章中讲解 VFS 时我们就一再强调,要注意同名对象( superblock
、 inode
等)在 VFS 和实际文件系统中对应的不同概念。在 VFS 层,我们需要按照 VFS 规范填充 super_block
、 inode
等结构,并提供相应的回调函数,使 VFS 能够通过统一接口访问不同的文件系统。而具体文件系统的存储格式则可以自由设计,不一定非要有固定的 superblock
区或 inode
区,只要能够正确存储和管理文件,并能被文件系统解析即可。
首先,我们需要先分析下需求,即我们的文件系统需要存储什么样的文件?使用场景是什么?需要哪些特性?当然,出于简单考虑,我们暂时不支持除读 / 写外的任何特性,并对存储的文件作出如下假设:
- 只有普通文件和目录这两种类型的文件
- 文件名、目录名最多 8 个字符
- 最多只能存储 32 个文件
- 对于普通文件来说,其最多存储 32 字节的数据
如果存储的文件遵循以上限制(不遵循咱就不存了😊),我们就可以很简单的使用一片连续内存(一个大的结构体数组)来存储数据了,结构体定义如下:
// simple.h | |
#define MAX_NAME_LEN 8 | |
#define MAX_FILES 32 | |
#define MAX_DATA_LEN 32 | |
/** | |
* file 的概念中包括了普通文件和目录 | |
*/ | |
struct file_block { | |
char filename[MAX_NAME_LEN]; // 文件名 | |
uint8_t busy; // 是否被使用 | |
mode_t mode; // 类型(普通文件 / 目录) | |
uint8_t idx; // 当前文件块的索引 | |
uint8_t parent_idx; // 父目录索引 | |
char data[MAX_DATA_LEN]; // 普通文件的数据区 | |
}; |
所以,其实我们的存储格式就类似于 struct file_block blks[MAX_FILES]
这样的结构。
# 创建文件系统工具
mkfs
在创建文件系统时会初始化文件系统的元数据,例如 superblock
和根目录等。但由于我们的存储格式实在是太过简单,由于 simplefs
的存储格式极其简单,没有专门的 superblock
,所以我们在初始化过程中只创建一个 file_block
作为根目录即可,代码如下:
// mkfs.simplefs.c | |
#include "simple.h" | |
#include <unistd.h> | |
#include <stdio.h> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
int main(int argc, char *argv[]) | |
{ | |
int fd; | |
ssize_t ret; | |
struct file_block root_file; | |
if (argc != 2) { | |
printf("Usage: mkfs-simplefs <device>\n"); | |
return -1; | |
} | |
fd = open(argv[1], O_RDWR); | |
if (fd == -1) { | |
perror("Error opening the device"); | |
return -1; | |
} | |
/* 创建根目录 */ | |
root_file.busy = 1; | |
root_file.mode = S_IFDIR; | |
root_file.idx = 0; | |
root_file.parent_idx = 0; /* 根目录的父目录就是自己 */ | |
root_file.data[0] = '\0'; | |
root_file.filename[0] = '/'; | |
ret = write(fd, (char *)&root_file, sizeof(struct file_block)); | |
/* 仅仅是一个粗糙的检查,其实是不需要的 */ | |
if (ret != sizeof(struct file_block)) | |
printf | |
("bytes written [%d] are not equal to the default file_block size", | |
(int)ret); | |
else | |
printf("Super block written successfully"); | |
close(fd); | |
return 0; | |
} |
此时,我们只需要调整下 makefile 就可以编译 simplefs
专属的 mkfs
了!
obj-m := simplefs.o | |
simplefs-objs := simple.o | |
all: ko mkfs.simplefs | |
ko: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules | |
mkfs.simplefs_SOURCES: # User-space program | |
mkfs.simplefs.c simple.h | |
clean: | |
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean | |
rm mkfs.simplefs |
# 测试
本测试在上一篇文章测试的基础上进行,此时应该已经有了 /dev/loop0
这个虚拟设备。
❯ sudo ./mkfs.simplefs /dev/loop0 | |
Super block written succesfully | |
❯ sudo xxd -l 64 /dev/loop0 | |
00000000: 2f00 0000 0000 0000 0100 0000 0040 0000 /............@.. | |
00000010: 0000 0000 0000 0000 0000 0000 0000 0000 ................ | |
00000020: 0000 0000 0000 0000 0000 0000 0000 0000 ................ | |
00000030: 0000 0000 0000 0000 0000 0000 0000 0000 ................ |
创建文件系统后可以使用 xxd
来查看我们初始化的磁盘,可以发现已经正确的初始化了!
What?是不是觉得一堆问题?比如数据区的长度限制 / 浪费、文件名的长度限制、文件数的限制等,简直不可用嘛!这个文件系统确实问题重重,但这是因为它只是一个开始,当这个文件系统实现并且能跑之后,你会发现它的各种不足和问题,而弥补这些不足正好是优化的动机,带着你逐步实现一个更加不 Low 的文件系统的过程中,你会窥见并掌握 Linux 内核文件系统的全貌和细节。 完美的学习过程!