介绍

文件系统是指负责存储,组织和一般管理表示为文件和目录的数据的软件。如果您使用的是设备来阅读本文,那么您目前可能至少在使用一个文件系统。

实现文件系统不是一件容易的事,并且需要在内核级别编写一部分文件,幸运的是,我们没有编写真正的磁盘文件系统,而是想编写一些东西。最重要的是解决特定问题。

在用户空间中最常用的工具就是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。

提示:

  1. 需要注意的重要一点是,默认情况下,写入和读取操作的大小为4kb,因此,如果您的文件为399kb,则必须处理:要读取该文件,读取回调将被调用100次,具有100个不同的偏移量和99个相等的大小。若块的大小为3kb,就刚刚好。
  2. 默认情况下,不允许其他用户访问已挂载的文件系统。

其他资源

Last modification:October 10th, 2019 at 10:54 am
如果觉得我的文章对你有用,请随意赞赏