我正在实现一个FUSE文件系统,旨在通过熟悉的 POSIX 调用提供对实际存储在 RESTful API 后面的文件的访问。一旦文件第一次被检索到,文件系统就会缓存文件,以便在后续访问中更容易获得它们。
我在多线程模式下运行文件系统(这是 FUSE 的默认设置),但发现 getattr 调用似乎是序列化的,即使其他调用可以并行发生。
打开文件时,FUSE 始终首先调用 getattr,而我支持的客户端需要此初始调用返回的文件大小准确(我无法控制此行为)。这意味着如果我没有缓存文件,我需要通过 RESTful API 调用实际获取信息。有时这些调用发生在高延迟网络上,往返时间约为 600 毫秒。
由于 getattr 调用的明显顺序性质,对当前未缓存的文件的任何访问都将导致整个文件系统在服务此 getattr 时阻止任何新操作。
我想出了很多方法来解决这个问题,但所有方法看起来都很丑陋或冗长,实际上我只是希望 getattr 调用像所有其他调用一样并行运行。
查看源代码,我不明白为什么 getattr 应该这样做,FUSE 确实锁定了 tree_lock 互斥锁,但仅用于读取,并且没有同时发生写入。
为了在这个问题中发布一些简单的东西,我敲开了一个非常基本的实现,它只支持 getattr 并允许轻松演示问题。
#ifndef FUSE_USE_VERSION
#define FUSE_USE_VERSION 22
#endif
#include <fuse.h>
#include <iostream>
static int GetAttr(const char *path, struct stat *stbuf)
{
std::cout << "Before: " << path << std::endl;
sleep(5);
std::cout << "After: " << path << std::endl;
return -1;
}
static struct fuse_operations ops;
int main(int argc, char *argv[])
{
ops.getattr = GetAttr;
return fuse_main(argc, argv, &ops);
}
使用几个终端在(大致)同一时间在路径上调用 ls 表明第二个 getattr 调用仅在第一个完成后才开始,这导致第二个 ls 需要约 10 秒而不是 5 秒。
1号航站楼
$ date; sudo ls /mnt/cachefs/file1.ext; date
Tue Aug 27 16:56:34 BST 2013
ls: /mnt/cachefs/file1.ext: Operation not permitted
Tue Aug 27 16:56:39 BST 2013
2号航站楼
$ date; sudo ls /mnt/cachefs/file2.ext; date
Tue Aug 27 16:56:35 BST 2013
ls: /mnt/cachefs/file2.ext: Operation not permitted
Tue Aug 27 16:56:44 BST 2013
如您所见,与date
之前的两个输出的时间差ls
仅相差 1 秒,但与之后的两个输出ls
相差 5 秒,这对应于GetAttr
函数的延迟。这表明第二个调用在 FUSE 深处的某个地方被阻塞。
输出
$ sudo ./cachefs /mnt/cachefs -f -d
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56
INIT: 7.10
flags=0x0000000b
max_readahead=0x00020000
INIT: 7.8
flags=0x00000000
max_readahead=0x00020000
max_write=0x00020000
unique: 1, error: 0 (Success), outsize: 40
unique: 2, opcode: LOOKUP (1), nodeid: 1, insize: 50
LOOKUP /file1.ext
Before: /file1.ext
After: /file1.ext
unique: 2, error: -1 (Operation not permitted), outsize: 16
unique: 3, opcode: LOOKUP (1), nodeid: 1, insize: 50
LOOKUP /file2.ext
Before: /file2.ext
After: /file2.ext
unique: 3, error: -1 (Operation not permitted), outsize: 16
上面的代码和示例与实际应用程序或应用程序的使用方式完全不同,但演示了相同的行为。我没有在上面的示例中展示这一点,但我发现一旦 getattr 调用完成,后续的 open 调用就能够并行运行,正如我所预料的那样。
我搜索了文档以尝试解释这种行为,并试图找到其他报告类似经历但似乎找不到任何东西的人。可能是因为 getattr 的大多数实现都非常快,你不会注意到或关心它是否被序列化,或者可能是因为我在配置中做了一些愚蠢的事情。我使用的是 FUSE 2.7.4 版,因此这可能是一个已修复的旧错误。
如果有人对此有任何见解,将不胜感激!