本文使用fuse将其与分布式的kv项目进行融合。实现了一个简易的文件系统,目的是为了学习和理解Fuse的框架和工作原理。仍然存在许多局限性的问题等待改善,作者在最后一章节会提到,希望各位大佬多多指正
1 介绍
用户态文件系统(filesystem in userspace, 简称FUSE),它能使用户在无需编辑和编译内核代码 的情况下,创建用户自定义的文件系统。文件系统是操作系统的重要组成部分,一般在内核层面实现对于文件系统的支持,而通常内核态的代码难以调试,生产率较低。在用户态空间实现文件系统能够极大幅度的提高生产效率,简化为实现新的文件系统的工作量。FUSE主要包含两个部分,内核FUSE模块(Linux 从2.6.14版本开始支持)和用户态Libfuse库。

2 项目概述
一个基于 FUSE(Filesystem in Userspace)框架和分布式 KV 存储实现的分布式文件系统。该系统允许用户通过标准的文件系统命令(如
ls、cat、echo等)来操作分布式数据库中的数据,实现了文件系统接口 与分布式存储系统的无缝集成。
2.1 技术栈
- Fuse3:用户空间文件系统框架
- RESP协议:Redis序列化协议,用于客户端与服务器通信
- TCP/IP:网络通信协议
- 分布式kv系统:基于Raft算法的分布式kv存储系统
2.2 功能架构

简单而言,即可以在文件系统中通过标准的文件系统命令去操作分布式存储集群的这样一个功能
2.3 核心功能映射
| 文件系统操作 | FUSE 函数 | KV 操作 | 说明 |
|---|---|---|---|
ls |
jl_readdir |
GET __FILE_LIST__ |
获取文件列表 |
cat file.txt |
jl_read |
GET file.txt |
读取文件内容 |
echo "content" > file.txt |
jl_write |
SET file.txt content |
写入文件 |
touch file.txt |
jl_create |
SET file.txt "" |
创建空文件 |
rm file.txt |
jl_unlink |
DELETE file.txt |
删除文件 |
stat file.txt |
jl_getattr |
EXIST file.txt |
获取文件属性 |
3 核心实现原理
3.1 网络通信模块
3.1.1 连接管理
cpp
static int connect_to_server(const char *ip, int port) {
// 1. 创建 TCP socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 设置服务器地址
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
inet_pton(AF_INET, ip, &server_addr.sin_addr);
server_addr.sin_port = htons(port);
// 3. 建立连接
connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
return fd;
}
原理:使用标准的TCP socket编程接口建立与分布式kv存储服务器的连接。支持自动重连和Leader节点发现
3.1.2 RESP协议实现
RESP(Redis Serialization Protocol)是一种简单的二进制安全协议,用于客户端与服务器之间的通信。协议格式如下:
cpp
*<参数个数>\r\n
$<参数1长度>\r\n
<参数1内容>\r\n
$<参数2长度>\r\n
<参数2内容>\r\n
...
cpp
static char *build_resp_command(const char *cmd, int argc, char **argv) {
static char buffer[MAX_BUFFER_SIZE];
int pos = 0;
// 构建数组头
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "*%d\r\n", argc + 1);
// 添加命令
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "$%zu\r\n%s\r\n", strlen(cmd), cmd);
// 添加参数
for (int i = 0; i < argc; i++) {
pos += snprintf(buffer + pos, sizeof(buffer) - pos, "$%zu\r\n%s\r\n", strlen(argv[i]), argv[i]);
}
return buffer;
}
3.1.3 响应解析
RESP 协议支持多种响应类型:
| 类型 | 前缀 | 示例 | 说明 |
|---|---|---|---|
| 简单字符串 | + |
+OK\r\n |
成功响应 |
| 错误 | - |
-Error message\r\n |
错误响应 |
| 整数 | : |
:100\r\n |
整数值 |
| 批量字符串 | $ |
$6\r\nfoobar\r\n |
二进制安全字符串 |
| 数组 | * |
*2\r\n... |
数组 |
3.2 KV操作封装
3.2.1 基本操作
cpp
// 获取键值
static int kv_get(const char *key, char *value, size_t value_size) {
char *argv[] = {(char*)key};
char *command = build_resp_command("RCGET", 1, argv);
return send_command(command, 0, value, value_size);
}
// 设置键值
static int kv_set(const char *key, const char *value) {
char *argv[] = {(char*)key, (char*)value};
char *command = build_resp_command("RCSET", 2, argv);
char response[MAX_BUFFER_SIZE];
return send_command(command, 1, response, sizeof(response));
}
// 删除键
static int kv_delete(const char *key) {
char *argv[] = {(char*)key};
char *command = build_resp_command("RCDELETE", 1, argv);
char response[MAX_BUFFER_SIZE];
return send_command(command, 1, response, sizeof(response));
}
// 检查键是否存在
static int kv_exist(const char *key) {
char *argv[] = {(char*)key};
char *command = build_resp_command("RCEXIST", 1, argv);
char response[MAX_BUFFER_SIZE];
if (send_command(command, 0, response, sizeof(response)) == 0) {
return atoi(response);
}
return 0;
}
说明:使用RC系列命令(RCSET,RCGET,RCCOUNT,RCEXIST)与分布式kv存储交互
3.2.2 文件列表管理
由于 KV 存储只提供键值对操作,我们需要一种机制来跟踪所有文件。解决方案是使用一个特殊的键
__FILE_LIST__来存储文件列表。存储格式 :
file1|file2|file3
cpp
static int add_to_file_list(const char *filename) {
char list[MAX_BUFFER_SIZE] = {0};
kv_get(file_list_key, list, sizeof(list));
// 检查是否已存在
if (file_exists_in_list(list, filename)) {
return 0;
}
// 添加到列表
if (strlen(list) > 0) {
strcat(list, "|");
}
strcat(list, filename);
return kv_set(file_list_key, list);
}
4 Fuse文件系统操作
4.1 获取文件属性(getattr)
cpp
static int jl_getattr(const char *path, struct stat *st, struct fuse_file_info *fi) {
memset(st, 0, sizeof(struct stat));
// 根目录
if (strcmp(path, "/") == 0) {
st->st_mode = S_IFDIR | 0755;
st->st_nlink = 2;
return 0;
}
// 普通文件
const char *filename = path + 1;
if (kv_exist(filename)) {
st->st_mode = S_IFREG | 0644;
st->st_nlink = 1;
// 获取文件大小
char content[MAX_BUFFER_SIZE] = {0};
if (kv_get(filename, content, sizeof(content)) == 0) {
st->st_size = strlen(content);
}
return 0;
}
return -ENOENT;
}
原理:
- 判断路径是否为根目录
- 如果是文件,检查kv存储中是否存在
- 如果存在,设置文件属性(类型、权限、大小等)
4.2 读取目录(readdir)
cpp
static int jl_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi,
enum fuse_readdir_flags flags) {
if (strcmp(path, "/") != 0) {
return -ENOENT;
}
// 填充标准目录项
filler(buf, ".", NULL, 0, (enum fuse_fill_dir_flags)0);
filler(buf, "..", NULL, 0, (enum fuse_fill_dir_flags)0);
// 获取文件列表
FileInfo files[MAX_FILES];
int count = get_file_list(files, MAX_FILES);
// 填充文件项
for (int i = 0; i < count; i++) {
filler(buf, files[i].name, NULL, 0, (enum fuse_fill_dir_flags)0);
}
return 0;
}
原理:
- 从KV存储获取文件列表
- 使用filler函数填充目录项
- 返回给FUSE框架
4.3 读取文件(read)
cpp
static int jl_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
const char *filename = path + 1;
// 从 KV 存储获取文件内容
char content[MAX_BUFFER_SIZE] = {0};
if (kv_get(filename, content, sizeof(content)) != 0) {
return -ENOENT;
}
// 计算读取范围
size_t len = strlen(content);
if (offset >= (off_t)len) {
return 0;
}
if (offset + size > len) {
size = len - offset;
}
// 复制数据到缓冲区
memcpy(buf, content + offset, size);
return size;
}
原理:
- 从 KV 存储读取完整文件内容
- 根据 offset 和 size 返回指定范围的数据
- 支持随机读取
4.4 写入文件(write)
cpp
static int jl_write(const char *path, const char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
const char *filename = path + 1;
// 读取现有内容
char content[MAX_BUFFER_SIZE] = {0};
kv_get(filename, content, sizeof(content));
// 写入新数据
if (offset + size >= MAX_BUFFER_SIZE) {
return -ENOSPC;
}
memcpy(content + offset, buf, size);
content[offset + size] = '\0';
// 保存到 KV 存储
if (kv_set(filename, content) != 0) {
return -EIO;
}
// 更新文件列表
add_to_file_list(filename);
return size;
}
原理:
- 读取现有文件内容
- 在指定偏移量写入新的数据
- 讲更新后的内容保存到kv存储
- 更新文件列表
4.5 创建文件(create)
cpp
static int jl_create(const char *path, mode_t mode, struct fuse_file_info *fi) {
const char *filename = path + 1;
// 创建空文件
if (kv_set(filename, "") != 0) {
return -EIO;
}
// 添加到文件列表
add_to_file_list(filename);
return 0;
}
4.6 删除文件(unlink)
cpp
static int jl_unlink(const char *path) {
const char *filename = path + 1;
// 检查文件是否存在
if (!kv_exist(filename)) {
return -ENOENT;
}
// 从 KV 存储删除
if (kv_delete(filename) != 0) {
return -EIO;
}
// 从文件列表移除
remove_from_file_list(filename);
return 0;
}
5 局限性
5.1 当前局限
- 文件大小限制 :单个文件最大 64KB(
MAX_BUFFER_SIZE) - 文件数量限制 :最多 1024 个文件(
MAX_FILES) - 不支持目录:只支持单层目录结构
- 无并发控制:多个客户端同时操作可能导致数据不一致
- 无持久化缓存:每次操作都需要网络请求
5.2 改进反向
- 支持大文件:将大文件分块存储
- 支持多级目录:使用路径作为键的一部分
- 添加分布式锁:使用 Raft 实现分布式锁服务
- 实现客户端缓存:减少网络请求
- 支持文件权限:存储文件权限信息