我正在实现一个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。 / p>
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
输出的时差仅相差一秒,但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版本,所以这可能是一个已经修复的旧bug。
如果有人对此有任何见解,我们将不胜感激!
答案 0 :(得分:10)
我注册了FUSE邮件列表,发布了我的问题,最近得到了Miklos Szeredi的以下回复:
查找(即首先找到与名称相关联的文件)是 每个目录序列化。这是在VFS(通用文件系统)中 部分在内核中),所以基本上任何文件系统都容易受到影响 这个问题,不只是融合。
非常感谢Miklos的帮助。有关完整主题,请参阅http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html。
我还注意到序列化是每个目录,即如果两个文件都位于同一目录中,则会看到上述效果,但如果它们位于不同的目录中则不会。对于我的应用程序,这种缓解对我来说已经足够了,我的文件系统的客户端确实使用目录,所以虽然我可能期望很多getattr调用接近连续,但是它们在同一目录上发生的可能性足够低,我不能担心。
对于那些缓解不足的人,如果您的文件系统支持目录列表,您可以利用David Strauss的建议,即使用readdir调用作为触发器来填充缓存:
在我们的文件系统中,我们尝试预取和缓存属性 readdir期间的信息(将不可避免地要求),所以我们 不必每个人都打到后端。
由于我文件系统的后端没有目录概念,我无法利用他的建议,但希望这会对其他人有所帮助。