14

我正在实现一个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 版,因此这可能是一个已修复的旧错误。

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

4

1 回答 1

12

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

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

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

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

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

在我们的文件系统中,我们尝试在 readdir 期间预取和缓存属性信息(这将不可避免地被请求),因此我们不必为每一个都打到后端。

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

于 2013-09-23T09:26:25.467 回答