介绍
文件系统是指负责存储,组织和一般管理表示为文件和目录的数据的软件。如果您使用的是设备来阅读本文,那么您目前可能至少在使用一个文件系统。
实现文件系统不是一件容易的事,并且需要在内核级别编写一部分文件,幸运的是,我们没有编写真正的磁盘文件系统,而是想编写一些东西。最重要的是解决特定问题。
在用户空间中最常用的工具就是FUSE,即USErspace中的Filesystem。
在FUSE之上构建了许多文件系统示例,这些示例涵盖了最不同的用例,例如:
- GlusterFS:可扩展的网络文件系统
- SSHFS:允许通过SSH挂载远程文件系统
- GMailFS:允许将GMail存储用作文件系统
- LoggedFS:记录其中发生的操作的文件系统
与编写低级内核文件系统相比,FUSE 的主要优点是:
- 可供非特权用户使用;
- 简洁易用的界面可进行FS操作;
- 具有大多数可用编程语言的绑定;
- 无需高级内核开发技能;
- 带有用户隔离,更安全;
- 由于您不是在内核空间中进行,因此程序崩溃不会破坏整个系统。
但是,此方法也有一些缺点:
- 目标系统需要安装libfuse;
- 比低级别的实现慢;
- 如果您需要多个用户访问您的文件系统,则不是最佳选择;
FUSE入门
建立依赖
Linux:
GCC或C
CMake> = 3
使
FUSE 2.6或更高版本
FUSE开发文件
Fedora / CentOS:
yum install gcc fuse fuse-devel make cmake
Debian / Ubuntu
apt-get install gcc fuse libfuse-dev make cmake
libfuse库公开了一组回调,您必须实现这些回调才能告诉文件系统如何运行。
关于什么是回调及其行为的最完整文档来源是fuse.h声明文件。您可以在此处找到在线版本。
示例项目
为了向您展示创建FUSE文件系统有多简单,我编写了这个小实现,该实现在挂载时仅公开一个名为file
及其内容的文件。
您可以在我的github找到,项目为fuse-example1
因此,首先克隆示例项目:
git clone https://github.com/gogobody/fuse-sample.git
如您所见,项目结构非常简单:
.
├── CMake
│ └── FindFUSE.cmake
├── CMakeLists.txt
└── fuse-example.c
CMakeLists.txt
您可能知道CMake是用于跨平台方式管理项目构建的工具。该文件的范围是定义CMake应该为我们的项目做什么。该CMake/FindFuse.cmake是必要的,以告诉CMake的在哪里可以找到FUSE有关的东西,而编译/链接。
fuse-example.c
这就是魔术真正发生的地方!
在此示例中,我实现了FUSE API回调中的四个,即:getattr,open,read,readdir。
getattr
getattr回调负责读取给定路径的元数据,始终在对文件系统执行任何操作之前调用此回调。
static int getattr_callback(const char *path, struct stat *stbuf) {
memset(stbuf, 0, sizeof(struct stat));
if (strcmp(path, "/") == 0) {
stbuf->st_mode = S_IFDIR | 0755;
stbuf->st_nlink = 2;
return 0;
}
if (strcmp(path, filepath) == 0) {
stbuf->st_mode = S_IFREG | 0777;
stbuf->st_nlink = 1;
stbuf->st_size = strlen(filecontent);
return 0;
}
return -ENOENT;
}
我们在这里所做的很简单:
- 如果path的值等于root
/
,则将其声明为目录并返回。 - 如果path的值等于filepath
/file
,则将其声明为文件并显式指定其大小,然后返回。 - 否则,给定路径上将不存在任何内容,我们将返回
-ENOENT
。
如您所见,我们正在使用struct stat
告诉FUSE当前条目是文件还是目录。
通常,如果条目是目录,st_mode则必须将设置为S_IFDIR和st_nlink,如果是文件,则st_mode必须将其设置为S_IFREG(代表常规文件)和st_nlink1。文件还要求st_size(完整文件尺寸)。
在这里您可以找到有关的更多信息<sys/stat.h>
打开 open
当系统请求打开文件时,将调用open回调。由于我们没有真实的文件,而只有内存中的表示形式,因此我们将实现此回调只是因为FUSE正常工作需要它,因此返回0。
读 read
当FUSE从打开的文件中读取数据时,将调用此回调。它应准确返回所请求的字节数size,并用这些字节的内容填充第二个参数buf
。就像在getattr回调中所做的一样,在这里我要检查给定的路径是否等于已知路径,然后将其复制filecontent到中buf,然后返回请求的字节数。
static int read_callback(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi) {
if (strcmp(path, filepath) == 0) {
size_t len = strlen(filecontent);
if (offset >= len) {
return 0;
}
if (offset + size > len) {
memcpy(buf, filecontent + offset, len - offset);
return len - offset;
}
memcpy(buf, filecontent + offset, size);
return size;
}
return -ENOENT;
}
读目录 readdir
readdir回调的任务是告诉FUSE被访问目录的确切结构。由于目前唯一可用的目录是/
,所以该函数始终返回其表示形式。因此我们通过填充buf
上层目录..
和当前目录的两个链接.
以及唯一的文件来完成此操作file。
static int readdir_callback(const char *path, void *buf, fuse_fill_dir_t filler,
off_t offset, struct fuse_file_info *fi) {
(void) offset;
(void) fi;
filler(buf, ".", NULL, 0);
filler(buf, "..", NULL, 0);
filler(buf, filename, NULL, 0);
return 0;
}
main函数
最后但并非最不重要的一点,main此处的函数充当fuse_main通过它传递的参数的代理,并通过变量通过已实现的FUSE操作回调对fuse_example_operations
进行配置。
static struct fuse_operations fuse_example_operations = {
.getattr = getattr_callback,
.open = open_callback,
.read = read_callback,
.readdir = readdir_callback,
};
int main(int argc, char *argv[])
{
return fuse_main(argc, argv, &fuse_example_operations, NULL);
}
构建并运行
您还记得您安装了CMake,make,gcc和libfuse吗?是时候使用它们了!
我们使用的第一个工具是CMake,用于检查依赖关系,设置环境并生成Makefiles。
cmake -DCMAKE_BUILD_TYPE=Debug .
如果您不希望使用Debug标志和其他与开发相关的功能,只需更改Debug为Release
我们正在使用的第二个工具是make,使用CMake生成的Makefile现在可以构建我们的项目。
make -j
-j 表示用多少颗cpu跑
RUN~!!!!
在执行任何操作之前,我们需要一个挂载点,因此让我们创建将挂载文件系统的目录:
mkdir /tmp/example
然后挂载文件系统:
./bin/fuse-example1 -d -s -f /tmp/example
现在检查它是否已安装:
$ ls -la
total 0
drwxr-xr-x. 2 root root 0 Jan 1 1970 .
drwxrwxrwt. 14 root root 320 Jan 10 16:03 ..
-rwxrwxrwx. 1 root root 49 Jan 1 1970 file
$ mount | grep fuse-example
fuse-example on /tmp/example type fuse.fuse-example (rw,nosuid,nodev,relatime,user_id=1000,group_id=1000)
您可能会注意到,我们使用三个参数挂载了文件系统:
- d:启用调试
- s:运行单线程
- f:留在前台
您可以使用查看所有安装选项的列表-h。
提示:
- 需要注意的重要一点是,默认情况下,写入和读取操作的大小为4kb,因此,如果您的文件为399kb,则必须处理:要读取该文件,读取回调将被调用100次,具有100个不同的偏移量和99个相等的大小。若块的大小为3kb,就刚刚好。
- 默认情况下,不允许其他用户访问已挂载的文件系统。