使用 Fuse 和 java 17 编写一个简单的文件系统

使用 Fuse 和 java 17 编写一个简单的文件系统

Photo by 扬·安东宁·科拉尔 on 不飞溅

目标是探索 Project Panama 的外部链接器功能并创建我们的简单文件系统。我们将使用 Java 17 和 FUSE 来做到这一点。我们将研究如何进行向上调用、向下调用和使用内存地址来创建我们的内存文件系统。

文件系统能做什么?

它将完成您对文件系统的期望的基础知识。我们可以挂载它、创建/读/写文件、创建目录和卸载它。重点是外部链接器功能。为了使实现易于理解,我们不会实现子目录。您只需要创建一个跟踪文件创建位置的 Java 类;如果您想添加该功能。

什么是 Fuse 和 Project Panama

FUSE(用户空间中的文件系统)允许您在实现其接口时创建用户空间文件系统。 FUSE 项目由两个组件组成:FUSE 内核模块和 libfuse 用户空间库。我们的实现将使用 libfuse 的高级 API。它提供了挂载文件系统、卸载文件系统、从内核读取请求以及发回响应的功能。

Project Panama 是 Java 语言改进的集合。该项目的目标是丰富和改进 Java 和本地(外部)接口之间的连接,这些接口通常由用 C 编写的应用程序使用。

巴拿马由以下 JEP(JDK 增强提案)组成:

  • Foreign-Memory Access API JEP:JEP-370、JEP-383
  • 外部链接器 API JEP:JEP-389
  • 矢量 API JEP:JEP-338

我们将专注于 Foreign Linker API,因为它提供对本机代码的纯 Java 访问。使用外部链接器的另一个好处是它应该具有可比的性能或比 JNI 更好。

设置

在开始之前,请确保您在 Linux/Mac 系统上安装了 FUSE(如果您使用的是 Windows,则可以使用 WSL1 或 WSL2 来跟随或任何 Linux VM)。我使用 libfuse 3.10.5 作为示例。如果您使用的是旧版本或新版本,可能会有细微差别。跑步 ldconfig -p | grep libfuse 在终端中将显示安装的 Libfuse 版本。如果未安装 Libfuse,您将不会得到任何输出。

我们还需要 Jextract,它是一个从 C 头文件生成 Java 文件的工具,并且仅在 Panama 早期访问版本中可用。去 https://jdk.java.net/panama/ 并为您的系统下载最新版本并解压缩。我们只需要这个特定版本的 Java 来生成 Java 文件。我们要构建的项目可以使用任何 Java 17 GA 版本。

此外,在 ( https://github.com/libfuse/libfuse/releases ) 我们将使用它作为 Jextract 的输入。

设置为运行和编译应用程序。

由于外部链接器仍处于孵化阶段,我们必须添加一些参数来运行和编译代码。如果您也在使用 IntelliJ,则需要添加 --add-modules jdk.incubator.foreign 到设置里面的Java编译器选项。并添加 --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign 到运行配置中的 VM 选项。

开始吧!

首先,我们将从 Libfuse 源代码生成 Java 文件。我们需要先设置我们的 Java 版本才能做到这一点。在终端内运行:

 导出 JAVA_HOME={JAVA_DOWNLOAD_LOCATION}/jdk-71  
 导出 PATH=$JAVA_HOME/bin:$PATH

来测试一下 提取物 正在运行:

 提取 -h

这应该向您显示所有可用的命令行选项:

 -C<String> - 指定要传递给底层 Clang 解析器的参数  
 -我<String>- 指定包含文件路径  
 -l<String> - 指定加载生成的 API 时应链接的库(名称或完整绝对路径)  
 -d<String> - 指定放置生成文件的位置  
 -t<String>为生成的类指定目标包  
 --include-function - 要包含的函数名称  
 --include-macro - 要包含的常量宏的名称  
 --include-struct - 要包含的结构定义的名称  
 --include-typedef - 要包含的类型定义的名称  
 --include-union - 要包含的联合定义的名称  
 --include-var - 要包含的全局变量的名称  
 --source - 生成 java 源代码而不是类文件

使用 Jextract 创建 Java 类

一切都设置好后,我们可以从 FUSE 源创建 Java 文件。在撰写本文时,我找不到让 Jextract 包含 FUSE_USE_VERSION 宏。为了解决这个问题,我添加了 #define FUSE_USE_VERSION 35 到 libfuse-fuse-3.10.5/include/ 目录中 fuse.h 的顶部。

完成后,您可以填写“LIBFUSE_SOURCE_DOWNLOAD_LOCATION”、“LIBFUSE_SOURCE_DOWNLOAD_LOCATION”并运行命令生成 java 文件。

 jextract -C "-D_FILE_OFFSET_BITS=64" --source -d generated/src -t org.linux -I {LIBFUSE_SOURCE_DOWNLOAD_LOCATION}/libfuse-fuse-3.10.5/include/ {LIBFUSE_SOURCE_DOWNLOAD_LOCATION}/libfuse-fuse-3.10.5/包含/fuse.h
  • -C "-D_FILE_OFFSET_BITS=64" 将参数传递给 c 语言解析器
  • --source -d 生成/src 我们希望 Jextract 输出文件的位置。
  • -t org.linux 生成的 java 文件将具有的类路径。
  • -I {LIBFUSE_SOURCE_DOWNLOAD_LOCATION}/libfuse-fuse-3.10.5/include/ 包含文件路径
  • {LIBFUSE_SOURCE_DOWNLOAD_LOCATION}/libfuse-fuse-3.10.5/include/fuse.h 我们要使用的头文件

实施 FUSE

我们现在已经有了所有的建筑部件,让我们开始吧!正如我们之前看到的,FUSE 只是我们需要实现的一个接口。

它不像实现一个 Java 接口。它是通过调用一个 C 函数并向其传递一个包含指向 Java 方法的指针的结构(类似于 Java Records)来完成的。您可以在 fuse.h 文件中找到结构;它看起来像这样:

 结构熔断器操作{  
 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);  
 int (*readlink) (const char *, char *, size_t);  
 int (*mknod) (const char *, mode_t, dev_t);  
 int (*mkdir) (const char *, mode_t);  
 int (*unlink) (const char *);  
 int (*rmdir) (const char *);  
 int (*symlink) (const char *, const char *);  
 int (*rename) (const char *, const char *, unsigned int flags);  
 int (*link) (const char *, const char *);  
 int (*chmod) (const char *, mode_t, struct fuse_file_info *fi);  
 int (*chown) (const char *, uid_t, gid_t, struct fuse_file_info *fi);  
 int (*truncate) (const char *, off_t, struct fuse_file_info *fi);  
 int (*open) (const char *, struct fuse_file_info *);  
 int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);  
 int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);  
 int (*statfs) (const char *, struct statvfs *);  
 int (*flush) (const char *, struct fuse_file_info *);  
 int (*release) (const char *, struct fuse_file_info *);  
 int (*fsync) (const char *, int, struct fuse_file_info *);  
 int (*setxattr) (const char *, const char *, const char *, size_t, int);  
 int (*getxattr) (const char *, const char *, char *, size_t);  
 int (*listxattr) (const char *, char *, size_t);  
 int (*removexattr) (const char *, const char *);  
 int (*opendir) (const char *, struct fuse_file_info *);  
 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t,  
 结构 fuse_file_info *, 枚举 f​​use_readdir_flags);  
 int (*releasedir) (const char *, struct fuse_file_info *);  
 int (*fsyncdir) (const char *, int, struct fuse_file_info *);  
 void *(*init) (struct fuse_conn_info *conn,struct fuse_config *cfg);  
 无效(*销毁)(无效*private_data);  
 int (*access) (const char *, int);  
 int (*create) (const char *, mode_t, struct fuse_file_info *);  
 int (*lock) (const char *, struct fuse_file_info *, int cmd,struct flock *);  
 int (*utimens) (const char *, const struct timespec tv[2],  
 结构 fuse_file_info *fi);  
 int (*bmap) (const char *, size_t blocksize, uint64_t *idx); #if FUSE_USE_VERSION < 35  
 int (*ioctl) (const char *, int cmd, void *arg,  
 struct fuse_file_info *, unsigned int flags, void *data);  
 #别的  
 int (*ioctl) (const char *, unsigned int cmd, void *arg,  
 struct fuse_file_info *, unsigned int flags, void *data);  
 #万一 int (*poll) (const char *, struct fuse_file_info *,  
 结构 fuse_pollhandle *ph, 无符号 *reventsp);  
 int (*write_buf) (const char *, struct fuse_bufvec *buf, off_t off,  
 结构 fuse_file_info *);  
 int (*read_buf) (const char *, struct fuse_bufvec **bufp,  
 size_t 大小,off_t 关闭,结构 fuse_file_info *);  
 int (*flock) (const char *, struct fuse_file_info *, int op);  
 int (*fallocate) (const char *, int, off_t, off_t,  
 结构 fuse_file_info *);  
 ssize_t (*copy_file_range) (const char *path_in,  
 结构 fuse_file_info *fi_in,  
 off_t offset_in, const char *path_out,  
 结构 fuse_file_info *fi_out,  
 off_t offset_out、size_t 大小、int 标志);  
 off_t (*lseek) (const char *, off_t off, int wherece, struct fuse_file_info *);  
 };

不要让长长的清单吓到你。我们只实施:

  • 获取属性 当您读取文件的属性时调用
  • 读目录 读取目录时调用
  • 从文件中读取时调用
  • mkdir 创建目录时调用
  • 诺德 创建文件时调用
  • 写入文件时调用

当文件系统内部发生某些事情时,例如创建目录,FUSE 将调用 mkdir 指着。读取文件时也会发生同样的情况。该方法 指向将被调用。

辅助方法

有一些代码我们会更频繁地使用。因此,将其放入几个方法中将使其余代码更清晰。

在类级别,我们添加了两个列表和一个地图。我们使用列表来跟踪我们创建的目录和文件。该映射用于检索文件的内容。

 静态列表<String>目录 = 新的 ArrayList<>();  
 静态列表<String>文件 = 新的 ArrayList<>();  
 静态地图<String, String>文件内容 = 新的 HashMap<>();

这是添加文件或检查它是已知目录还是文件的三种小方法。

 静态布尔 isDir(字符串路径){  
 返回目录.包含(路径);  
 } 静态无效添加文件(字符串文件名){  
 文件。添加(文件名);  
 文件内容.put(文件名,"");  
 } 静态布尔 isFile(字符串路径){  
 返回文件.包含(路径);  
 }

在 Java 中创建 fuse_operations

我们从这个类开始:

 导入 jdk.incubator.foreign.*;  
 导入 org.linux.*; // 一个 导入 java.util.Arrays; 公共类SecondMain { 静态资源范围 rsScope = null; 公共静态无效主(字符串...参数){ System.load("/usr/lib64/libfuse3.so.3.10.5"); // 乙 args = new String[]{"-f", "-d", "/mnt/test/"}; // C 尝试 (var scope = ResourceScope.newSharedScope()) { // D  
 rsScope = 范围;  
 var arguments = Arrays.stream(args).m​​ap(s -> CLinker.toCString(s, scope)).toArray(MemorySegment[]::new); // E  
 var allocator = SegmentAllocator.ofScope(scope); // F  
 var argumentCount = args.length;  
 var argumentSpace = allocator.allocateArray(CLinker.C_POINTER, arguments); // G MemorySegment 操作MemorySegment = fuse_operations.allocate(scope); // H  
 } }  
 }

在“A”行,我们导入我们在前一步中生成的所有类。在“B”行,我们加载我们想要使用的 libfuse 库。您可以使用 ldconfig -p | grep libfuse 找到它在您的系统上的位置。

在“C”处,我们创建了一个数组,其中包含我们想要传递给 FUSE 的参数。 -F 就是将其保持在前台,这样我们就可以在控制台中看到任何输出。 -d 将使 FUSE 还将任何调试信息打印到控制台。 /mnt/测试/ 是挂载点。

资源范围管理一个或多个资源的生命周期,例如内存段。我们在“D”处创建了一个 SharedScope,因为 Fuse 默认运行多线程。您可以使用 -s 如果你愿意,让它运行单线程。

在“E”行,我们获取一个 Java 字符串并将其转换为 C 函数可用的 C 字符串。结果是一个数组 内存段 .然后在“F”行,我们创建一个 段分配器 我们可以在“G”行使用来为我们的数组分配内存 内存段 .

“H”行向我们展示了我们如何分配熔断器操作。 fuse_operations 是 Jextract 为我们生成的类的名称。它有一个方法 分配 在共享范围内为自己分配内存。

实现 getAttr

这是我们所知道的签名 fuse_operations .

 int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi);

这是来自 Jextract 生成的 Java 类的签名。它是功能接口的一部分,所以我们可以提供一个实现。

 int apply(MemoryAddress x0, MemoryAddress x1, MemoryAddress x2);

对于我们的实现,我们不会添加 fuse_file_info fi 在签名中。因为我们不会使用它。

 公共静态int getAttr(MemoryAddress路径,MemoryAddress mStat){  
 String jPath = CLinker.toJavaString(path); // 一个  
 MemorySegment statMemorySegment = stat.ofAddress(mStat, rsScope); // 乙 int S_IFDIR = 0040000; /* 目录 */  
 int S_IFREG = 0100000; /* 常规的 */ // 设置统计时间(最后访问时间)  
 现在瞬间 = Instant.now();  
 timespec.tv_sec$set(stat.st_atim$slice(statMemorySegment), now.getEpochSecond()); // C  
 timespec.tv_nsec$set(stat.st_atim$slice(statMemorySegment), now.getNano()); // 设置 stat mtim(最后修改时间)  
 现在 = Instant.now();  
 timespec.tv_sec$set(stat.st_mtim$slice(statMemorySegment), now.getEpochSecond());  
 timespec.tv_nsec$set(stat.st_mtim$slice(statMemorySegment), now.getNano()); stat.st_uid$set(statMemorySegment, 1000); // D  
 stat.st_gid$set(statMemorySegment,1000); if ("/".equals(jPath) || isDir(jPath.substring(1))) {  
 stat.st_mode$set(statMemorySegment, (short) (S_IFDIR | 0755)); // E  
 stat.st_nlink$set(statMemorySegment, 2); // F  
 } else if (isFile(jPath.substring(1))) {  
 stat.st_mode$set(statMemorySegment, (int)(S_IFREG | 0644));  
 stat.st_nlink$set(statMemorySegment, 1);  
 stat.st_size$set(statMemorySegment, filesContent.get(jPath.substring(1)).getBytes().length); // G  
 } 别的 {  
 返回-2; // H  
 } 返回0; // 我  
 }

在“A”行,我们将 C 字符串转换为 Java 字符串。我们从 Fuse 签名中知道,第一个参数是我们想要属性的文件路径。第二 内存地址 参数中是我们需要填充请求的文件或目录的属性的stat结构。要访问 stat 结构,我们需要在“B”行获得的 MemorySegment。

在“C”行,我们设置了最后一次访问时间。要设置时间,我们需要调用 timespec.tv_sec$set 并在我们通过调用获得的 stat 内存段的特定部分设置秒数 tat.st_mtim$slice(statMemorySegment) .

用户 ID 和组 ID 设置在 D 行。为方便起见,我们现在将它们设置为 1000。在C中你会打电话 见证() 获取吉德() 获得真正的价值。

在 - 的里面 如果 在“E”行,我们设置 st_mode what 指定它是目录还是普通文件,我们设置权限位。我们也对文件执行此操作。一个区别是我们设置 set_nlink 到两个目录。你可以在这里阅读为什么这样做。 ( https://unix.stackexchange.com/questions/101515/why-does-a-new-directory-have-a-hard-link-count-of-2-before-anything-is-added-to/101536# 101536) .

对于文件,我们还需要设置大小。这里我们只是将 String 转换为字节数组并使用它的大小。

在“H”行,我们返回 2,它等于 ENOENT 在 C 中,这意味着没有这样的文件或目录。指定路径名的组件不存在,或路径名是空字符串。

我们最后返回 0 让 FUSE 知道我们已经完成并且一切正常。

为什么我们需要做 Path.substring(1)?

Fuse 将为我们传递一条以 a 开头的路径 / .当我们稍后创建实现时 mkdir 和 mknod,我们将只存储名称而不是它们的路径。所以,我们不需要 / .

实现 readDir

我们要实现的下一件事是 readDir。当您想知道给定目录中有哪些文件和目录可用时,将调用此方法。

FUSE 签名如下所示:

 int (*readdir) (const char *, void *, fuse_fill_dir_t, off_t, struct fuse_file_info *, enum fuse_readdir_flags);

Jextract 创建了这个。就像 闲话 它是功能接口的一部分,我们必须提供实现。

 int apply(MemoryAddress x0, MemoryAddress x1, long x2, long x3, MemoryAddress x4);

我们这里有五个参数,我们只会使用前三个。填充物有点特别。是 FUSE 提供的一个辅助方法来填充 缓冲 .

 public static int readDir(MemoryAddress 路径,MemoryAddress 缓冲区,MemoryAddress 填充,长偏移,MemoryAddress fileInfo) { String jPath = CLinker.toJavaString(path);  
 fuse_fill_dir_t fuse_fill_dir_t = org.linux.fuse_fill_dir_t.ofAddress(filler); // 一个  
 fuse_fill_dir_t.apply(buffer, CLinker.toCString(".", rsScope).address(), MemoryAddress.NULL, 0, 0); // 乙  
 fuse_fill_dir_t.apply(buffer, CLinker.toCString("..", rsScope).address(), MemoryAddress.NULL, 0, 0);  
 if ("/".equals(jPath)) { // C  
 对于(字符串 p:目录){  
 fuse_fill_dir_t.apply(buffer, CLinker.toCString(p, rsScope).address(), MemoryAddress.NULL, 0, 0);  
 } 对于(字符串 p:文件){  
 fuse_fill_dir_t.apply(buffer, CLinker.toCString(p, rsScope).address(), MemoryAddress.NULL, 0, 0);  
 }  
 } 返回0;  
 }

调用方法 填料 我们需要它的一个实例;这是在“A”行完成的。正如我们之前谈到的,目录在基于 Unix 的文件系统中有两个链接。在“B”,我们确保每个目录都有这两个链接。

在 C 行,我们填充我们循环遍历两个列表并使用添加创建的文件和目录 填料 .

实现读取

当我们要读取文件的内容时调用此方法。 Fuse 签名如下:

 int (*read) (const char *, char *, size_t, off_t, struct fuse_file_info *);

Jextract 为我们创建了它作为功能接口的一部分:

 int apply(MemoryAddress x0, MemoryAddress x1, long x2, long x3, MemoryAddress x4);

该方法传递了一个缓冲区,我们需要用所请求文件的内容填充该缓冲区。

 public static int read(MemoryAddress path, MemoryAddress buffer, long size, long offset, MemoryAddress fileInfo) {  
 String jPath = CLinker.toJavaString(path).substring(1); 如果(!isFile(jPath)){  
 返回-1;  
 } byte[] selected = filesContent.get(jPath).getBytes(); ByteBuffer byteBuffer = buffer.asSegment(size, rsScope).asByteBuffer(); // 一个 byte[] src = Arrays.copyOfRange(selected, Math.toIntExact(offset), Math.toIntExact(size)); // 乙  
 byteBuffer.put(src); // C 返回 src.length; // D  
 }

在方法的第一部分,我们将 C 字符串转换为 Java 字符串,并检查我们是否知道文件。在“A”行,我们做一个 字节缓冲区 缓冲 首先获取它的内存段。接下来在“B”行,我们复制用户请求的部分。接下来,我们填充 字节缓冲区 使用复制的范围并返回长度;所以 FUSE 知道它有多长。

实现 doMkdir

这个方法在我们创建目录的时候被调用。

这是 FUSE 签名。

 int (*mkdir) (const char *, mode_t);

Jextract 为我们创建了它作为功能接口的一部分:

 int apply(MemoryAddress x0, int x1);

当 FUSE 调用该方法时,我们只是将 C 字符串转换为 Java 并将其添加到目录列表中。

 static int doMkdir(MemoryAddress path, int mode) {  
 String jPath = CLinker.toJavaString(path);  
 目录.add(jPath.substring(1));  
 返回0;  
 }

实现 doMknod

这是我们要实现的保险丝签名。

 int (*mknod) (const char *, mode_t, dev_t);

Jextract 生成方法:

 int apply(MemoryAddress x0, int x1, long x2);

当 FUSE 调用这个方法时,我们调用 helper 方法 添加文件 将文件添加到文件列表并在文件内容映射中创建键值对。

 static int doMknod(MemoryAddress path, int mode, long rdev) {  
 String jPath = CLinker.toJavaString(path);  
 addFile(jPath.substring(1));  
 返回0;  
 }

实现doWrite

保险丝签名:

 int (*write) (const char *, const char *, size_t, off_t, struct fuse_file_info *);

Jextract 生成方法:

 int apply(MemoryAddress x0, MemoryAddress x1, long x2, long x3, MemoryAddress x4);

do write 有一个 buffer 参数,其中包含我们需要保存在内存中的字节。

 static int doWrite(MemoryAddress path, MemoryAddress buffer, long size, long offset, MemoryAddress info) {  
 byte[] array = buffer.asSegment(size, rsScope).toByteArray(); // 一个  
 String jPath = CLinker.toJavaString(path).substring(1);  
 filesContent.put(jPath, new String(array, java.nio.charset.StandardCharsets.UTF_8));  
 返回 Math.toIntExact(size);  
 }

在“A”行,我们使用缓冲区的内存地址和大小创建一个段。有了这些,我们创建了一个 字节数组 我们可以将其转换为字符串并存储在文件内容映射中。

填充保险丝操作和启动保险丝

我们已经实现了基本文件系统的所有方法。现在是时候将它们添加到熔断器操作结构中了。

 公共静态无效主(字符串...参数){ System.load("/usr/lib64/libfuse3.so.3.10.5"); args = new String[]{"-f", "-d", "/mnt/test/"}; files.add("file54");  
 filesContent.put("file54", "file54 的内容"); 尝试 (var scope = ResourceScope.newSharedScope()) {  
 rsScope = 范围;  
 var arguments = Arrays.stream(args).m​​ap(s -> CLinker.toCString(s, scope)).toArray(MemorySegment[]::new);  
 var allocator = SegmentAllocator.ofScope(scope);  
 var argumentCount = args.length;  
 var argumentSpace = allocator.allocateArray(CLinker.C_POINTER, arguments); MemorySegment 操作MemorySegment = fuse_operations.allocate(scope); fuse_operations.getattr$set(operationsMemorySegment, fuse_operations.getattr.allocate((path, stat, fi) -> getAttr(path, stat), scope)); // 一个  
 fuse_operations.readdir$set(operationsMemorySegment, fuse_operations.readdir.allocate((路径, 缓冲区, 填充物, 偏移量, fileInfo, i) -> readDir(路径, 缓冲区, 填充物, 偏移量, fileInfo), 范围));  
 fuse_operations.read$set(operationsMemorySegment, fuse_operations.read.allocate((path, buffer, size, offset, fileInfo) -> read(path, buffer, size, offset, fileInfo), scope));  
 fuse_operations.mkdir$set(operationsMemorySegment, fuse_operations.mkdir.allocate((MemoryAddress x0, int x1) -> doMkdir(x0, x1), scope));  
 fuse_operations.mknod$set(operationsMemorySegment, fuse_operations.mknod.allocate((MemoryAddress x0, int x1, long x2) -> doMknod(x0, x1, x2), scope));  
 fuse_operations.write$set(operationsMemorySegment, fuse_operations.write.allocate((MemoryAddress x0, MemoryAddress x1, long x2, long x3, MemoryAddress x4) -> doWrite(x0, x1, x2, x3, x4), scope)); fuse_h.fuse_main_real(argumentCount, argumentSpace, operationsMemorySegment, operationsMemorySegment.byteSize(), MemoryAddress.NULL); // 乙  
 }  
 }

在上面的代码中,您可以看到完成的 main 方法。我们在资源范围内添加了六个方法调用,以将方法添加到熔断操作结构中。我们还添加了 fuse_h.fuse_main_real 挂载我们的文件系统。

在“A”行,您可以看到我们如何添加 获取属性 的方法 fuse_operations .在这一行发生的事情是我们称 fuse_operations 类并告诉它我们要设置 获取属性 方法上我们的熔断操作 MemorySegment(第一个参数)。第二个参数创建生成的 Jextract 代码和 Fuse 可以使用的 lambda 方法的内存地址。最后一个参数是作用域,它是所用内存段和内存地址的所有者。

我们需要为我们想要 FUSE 调用的每个方法执行此操作。这六个调用共享相同的模式。我们只需要将 lambda 指向正确的函数并调用匹配的函数 fuse_operations .

在“B”行,我们挂载我们的文件系统。我们称之为 fuse_main_real fuse_h 带有参数的类 参数 ,我们实现了六个方法的fuse_operations,以及结构体的大小。应用程序启动后,您可以在挂载点内创建文件和目录。该程序会一直运行,直到您停止它或卸载文件系统。您可以使用卸载它 fusermount -u {MOUNT_LOCATION} . *注意如果您自己停止应用程序,您仍然需要卸载它。

结论

你做到了!我们使用外部链接器 API 在 Java 中创建了一个内存文件系统。我们使用 Jextract 从 C 头文件生成 Java 类。使用 Clinker 将 Java 字符串转换为 C 字符串,反之亦然。我们还调用了 C 函数 fuse_main_real 直接来自Java代码。创建了六个向上调用,当文件系统内发生事件时,FUSE 可以调用这些调用。

最初发表于 https://www.davidvlijmincx.com 2021 年 11 月 28 日。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/40126/37170109

posted @ 2022-10-01 09:37  哈哈哈来了啊啊啊  阅读(504)  评论(0编辑  收藏  举报