本篇文章是从零实现文件系统的第三篇文章,主要内容为设计并实现磁盘上的数据存储格式以及为 simplefs 实现一个简单的文件系统创建(格式化)工具 mkfs.simplefs

⚙️ 以下是本篇文章所使用的环境:

  • Ubuntu Server 24.04
  • Linux kernel 6.8.0-51-generic
  • VSCode + GCC

⚓️对应代码版本: V0.2

在上一篇文章中,我们实现了一个可以正确挂载 / 卸载的简单文件系统,由于没有实现任何操作存储设备的代码,所以我们的文件系统其实根本无法存储文件,现在,是时候来给我们的文件系统设计一个数据存储格式了!

# 数据存储格式

Linux 文件系统模型与 VFS 入门这篇文章中讲解 VFS 时我们就一再强调,要注意同名对象( superblockinode 等)在 VFS 和实际文件系统中对应的不同概念。在 VFS 层,我们需要按照 VFS 规范填充 super_blockinode 等结构,并提供相应的回调函数,使 VFS 能够通过统一接口访问不同的文件系统。而具体文件系统的存储格式则可以自由设计,不一定非要有固定的 superblock 区或 inode 区,只要能够正确存储和管理文件,并能被文件系统解析即可。

首先,我们需要先分析下需求,即我们的文件系统需要存储什么样的文件?使用场景是什么?需要哪些特性?当然,出于简单考虑,我们暂时不支持除读 / 写外的任何特性,并对存储的文件作出如下假设:

  1. 只有普通文件和目录这两种类型的文件
  2. 文件名、目录名最多 8 个字符
  3. 最多只能存储 32 个文件
  4. 对于普通文件来说,其最多存储 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 内核文件系统的全貌和细节。 完美的学习过程


更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Gality 微信支付

微信支付

Gality 支付宝

支付宝