FUSE getattr操作是否应该始终序列化?

时间:2013-08-27 16:46:17

标签: c++ c linux multithreading fuse

我正在实现一个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。

如果有人对此有任何见解,我们将不胜感激!

1 个答案:

答案 0 :(得分:10)

我注册了FUSE邮件列表,发布了我的问题,最近得到了Miklos Szeredi的以下回复:

  

查找(即首先找到与名称相关联的文件)是   每个目录序列化。这是在VFS(通用文件系统)中   部分在内核中),所以基本上任何文件系统都容易受到影响   这个问题,不只是融合。

非常感谢Miklos的帮助。有关完整主题,请参阅http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html

我还注意到序列化是每个目录,即如果两个文件都位于同一目录中,则会看到上述效果,但如果它们位于不同的目录中则不会。对于我的应用程序,这种缓解对我来说已经足够了,我的文件系统的客户端确实使用目录,所以虽然我可能期望很多getattr调用接近连续,但是它们在同一目录上发生的可能性足够低,我不能担心。

对于那些缓解不足的人,如果您的文件系统支持目录列表,您可以利用David Strauss的建议,即使用readdir调用作为触发器来填充缓存:

  

在我们的文件系统中,我们尝试预取和缓存属性   readdir期间的信息(将不可避免地要求),所以我们   不必每个人都打到后端。

由于我文件系统的后端没有目录概念,我无法利用他的建议,但希望这会对其他人有所帮助。