最简单文件系统的升级,增加文件创建和读写功能

2024-04-20 17:20

本文主要是介绍最简单文件系统的升级,增加文件创建和读写功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基于前文,我们继续我们的文件系统的开发。在《基于Fuse的最简单的文件系统》一文中我们实现了一个假文件系统。虽然呈现了目录结构,但是不能创建新文件,也不能删除文件,更加不能对文件进行读写。

接下来我们就增加更多的功能,使该文件系统具备创建文件和读写的功能。但是,我们本着循序渐进的原则,这里需要注意的是本文件系统还有诸多限制,具体如下:

  • 目前的文件系统还是基于内存的实现,数据并不会持久化到存储介质上
  • 只支持在根目录创建和删除文件
  • 文件的大小限制在1024字节以内
  • 不支持在根目录创建子目录
  • 只支持一次性读写,不支持指定偏移读写
  • 最大可以创建256个文件

好吧,限制好多! 增加这么多限制的原因很简单,就是为了让降低大家的学习门槛。如果一下子实现一个支持所有功能的文件系统,估计大家就没有学习的兴趣和动力了。所以我们每一个例子会增加一点点功能,逐渐实现一个功能完善的文件系统。至于本系列文章中涉及的技术细节,大家可以参考本人拙著《文件系统技术内幕》一书。
在这里插入图片描述

接下来我们回归正题。虽然本地文件系统的基本原理是将线性的硬盘空间抽象为树型层级结构,但是底层存储并不一定要是线性的地址空间。为了实现简单,本文我们会借助STL的容器实现文件系统的一些基本功能。当然我们最终实现的文件系统肯定是基于硬盘的,目前的设计思路是为了降低学习的门槛和坡度,达到循序渐进的目的。

为了支持256个最大为1KB的内存文件,我们首先需要分配256KB的内存空间(用如下代码中的data_space表示)。同时,为了管理这些空间,也就是记录哪些空间已经被使用,哪些空间还是可用状态,我们通过创建了一个位图类型的变量(data_bitmap)。
image.png

另外,为了记录根目录中文件名称与文件数据的对应关系,我们创建了一个map类型的变量files。通过这个变量,我们可以根据文件名称获取文件的描述信息,比如文件数据的位置和大小等。这里文件的描述信息是通过一个名称为inode的结构体表示的,这也是模仿的Linux文件系统中的概念。

#define DATA_SPACE_LEN (256)
static map<string, inode*> files;
static vector<char*> data_space;
static bitset<DATA_SPACE_LEN> data_bitmap;

如下是结构体inode的具体定义,这里一共包含4项内容,都是最为基本的属性描述。本文我们主要使用了data_index和size两个成员变量,分别表示文件数据的位置和大小。

struct inode
{unsigned int inode_id;unsigned int mode;unsigned short data_index;unsigned short size;
};

如下代码是初始化的代码,本例中我们主要将内存区域清零,并将位图清零。这表示目前我们有一个干净的,没有任何文件的文件系统。后续我们在实现基于硬盘的文件系统时可以在这里实现硬盘格式化的工作。需要注意的是,在init函数中我们调用了syslog函数,该函数用于向系统日志中记录一条日志,方便我们了解程序的运行情况。

/* 初始化的时候被调用 */
void* MemoryFS::init(struct fuse_conn_info *conn, fuse_config*)
{syslog(LOG_NOTICE, "init\n");for (int i = 0; i < DATA_SPACE_LEN; i++) {char* block = new char[1024];memset(block, 0, 1024);data_space.push_back(block);data_bitmap.reset(i);}return 0;
}

然后回到本文的核心内容,实现文件的创建和读写。文件的创建和读写一共涉及4个函数,分别是create、open、write和read。其中create是创建文件时被调用,open是打开文件时被调用,write和read分别数据写读的时候被调用。接下来我们分别看看代码实现。

首先要看的自然是创建文件的实现,也就是create函数的实现,具体代码如下所示。本例实现原理是在创建一个文件的时候将该文件存储在一个map数据结构中,这样后续读写文件的时候就可以找到该文件。在本例中,我们只是创建一个inode实例,并构建文件系统与inode的映射关系。我们这里实现的非常简单,相当于在根目录中增加了一个文件。

int MemoryFS::create(const char *name, mode_t mode, struct fuse_file_info *)
{syslog(LOG_NOTICE, "Create: %s\n", name);inode *i = new inode();memset(i, 0, sizeof(inode));files[name] = i;  // 存储到map中return 0;
}

打开文件的函数实现也是非常简单的,我们只需要查询一下map,看看要打开的文件是否存在。如果不存在需要返回ENOENT。

int MemoryFS::open(const char *name, struct fuse_file_info *)
{int status = 0;auto inode = files.find(name);if (inode == files.end()) {status = -ENOENT;}syslog(LOG_NOTICE, "Open: %s %d\n", name, status);return 0;
}

接下来到正题了,也就是文件写数据的功能。首先是写数据的函数原型,可以看出与Linux的write函数类似。不同的地方是Linux API第一个参数是句柄,而本函数是文件名称。当我们在根目录中向某个文件写入数据的时候就会触发给函数。该函数的核心功能是找到前面创建的文件,并且找到一个内存空间用于存储用户要写入的数据。找到具体的信息后,会将数据拷贝到内存空间,并更新inode的记录。

int MemoryFS::write(const char *name, const char *buf, size_t buf_size, off_t offset, struct fuse_file_info *)
{auto inode = files.find(name); // 查找要写入数据的文件char* content = nullptr;short index = 0;int status = 0;if ( inode == files.end() ) {  // 如果没有找到文件,返回相应的错误码status = -ENOENT;goto OUT;}if ( inode->second->size ) {  // 判断文件是否已经有数据index = inode->second->data_index;} else {for (int i = 0; i < DATA_SPACE_LEN; i++) {  // 如果没有数据,根据位图查找一个可用的空间if (!data_bitmap[i]) {index = i;data_bitmap.set(i);break;}}}content = data_space[index]; // 获取空间的地址memcpy(content, buf, buf_size); // 将数据写入内存空间inode->second->data_index = index; // 更新数据的位置inode->second->size = buf_size;       // 更新文件大小OUT:syslog(LOG_NOTICE, "Write: %s %s %d\n", name, buf, buf_size);return buf_size;
}

读数据的实现逻辑与写数据类似,首先我们需要从map中查找到对应的文件。如果该文件存在,则会返回文件对应的inode实例。前文已述,inode中包含着文件数据的位置和大小等信息。根据inode中保存的信息,我们可以将数据拷贝到读数据的缓冲区,这样在调用者就可以看到数据了。

int MemoryFS::read(const char *name, char *buf, size_t buf_size, off_t offset, struct fuse_file_info *)
{auto inode = files.find(name); // 查找文件的inode信息char* content = nullptr;short index = 0;short file_size = 0;int status = 0;if ( inode == files.end() ) {status = -ENOENT;goto OUT;}index = inode->second->data_index; // 根据inode信息确定文件数据的位置和大小file_size = inode->second->size; content = data_space[index];memcpy(buf, content, file_size);  // 将数据拷贝到读缓冲区OUT:syslog(LOG_NOTICE, "Read: %s C %s B %s %d %d %d\n", name, content, buf, index, file_size, buf_size);return file_size;
}

虽然上述函数实现后就可以实现我们的目标功能了。但是如果我们用ls命令查看文件,则需要实现目录遍历的功能,这部分功能我们在前面文章介绍过。首先是需要实现readdir功能,用于遍历目录项。本例中是遍历map类型的变量。

int MemoryFS::readdir(const char *path, void *buf, fuse_fill_dir_t filler,off_t, struct fuse_file_info *, enum fuse_readdir_flags)
{syslog(LOG_NOTICE, "readdir: %s\n", path);filler(buf, ".", NULL, 0, FUSE_FILL_DIR_PLUS);filler(buf, "..", NULL, 0, FUSE_FILL_DIR_PLUS);for (const auto& [key, value] : files) { // 实现目录项的遍历filler(buf, key.c_str() + 1, NULL, 0, FUSE_FILL_DIR_PLUS);}return 0;
}

然后是实现getattr,该函数用于获取每个文件/目录的详细属性。这里我们也是偷懒了,很多属性是写死的。这个函数的实现并不复杂,大家可以自行阅读一下相关代码。

int MemoryFS::getattr(const char *path, struct stat *stbuf, struct fuse_file_info *)
{int res = 0;memset(stbuf, 0, sizeof(struct stat));if (path == root_path) {stbuf->st_mode = S_IFDIR | 0755;stbuf->st_nlink = 2;} else if (files.find(path) != files.end()) {auto inode = files.find(path);stbuf->st_mode = S_IFREG | 0755; // 我们这里模式是写死的,stbuf->st_nlink = 1;stbuf->st_size = inode->second->size;syslog(LOG_NOTICE, "getattr file: %s %d\n", path);} else {res = -ENOENT;syslog(LOG_NOTICE, "getattr error: %s\n", path);}syslog(LOG_NOTICE, "getattr: %s %d\n", path, res);return res;
}

至此,我们完成了所有函数的介绍。相关代码已经更新到作者的github空间,大家可以自行下载编译实验一下。接下来我们将进一步丰富功能,实现一个可以持久化的文件系统。

注: 本文配套的源代码可以在github的SunnyZhang-IT/fs-from-zero库中找到。

这篇关于最简单文件系统的升级,增加文件创建和读写功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920865

相关文章

10. 文件的读写

10.1 文本文件 操作文件三大类: ofstream:写操作ifstream:读操作fstream:读写操作 打开方式解释ios::in为了读文件而打开文件ios::out为了写文件而打开文件,如果当前文件存在则清空当前文件在写入ios::app追加方式写文件ios::trunc如果文件存在先删除,在创建ios::ate打开文件之后令读写位置移至文件尾端ios::binary二进制方式

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

usaco 1.3 Prime Cryptarithm(简单哈希表暴搜剪枝)

思路: 1. 用一个 hash[ ] 数组存放输入的数字,令 hash[ tmp ]=1 。 2. 一个自定义函数 check( ) ,检查各位是否为输入的数字。 3. 暴搜。第一行数从 100到999,第二行数从 10到99。 4. 剪枝。 代码: /*ID: who jayLANG: C++TASK: crypt1*/#include<stdio.h>bool h

uva 10387 Billiard(简单几何)

题意是一个球从矩形的中点出发,告诉你小球与矩形两条边的碰撞次数与小球回到原点的时间,求小球出发时的角度和小球的速度。 简单的几何问题,小球每与竖边碰撞一次,向右扩展一个相同的矩形;每与横边碰撞一次,向上扩展一个相同的矩形。 可以发现,扩展矩形的路径和在当前矩形中的每一段路径相同,当小球回到出发点时,一条直线的路径刚好经过最后一个扩展矩形的中心点。 最后扩展的路径和横边竖边恰好组成一个直

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu

uva 10130 简单背包

题意: 背包和 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2