FUSE getattr操作是否应该始终串行化
Should the FUSE getattr operation always be serialised?
我正在实现一个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
如您所见,ls
之前的两个date
输出之间的时间差仅相差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调用完成,后续的打开调用就可以并行运行,正如我所期望的那样。
我搜索了医生,试图解释这种行为,并试图找到其他人报告类似的经历,但似乎什么都找不到。可能是因为getattr的大多数实现都很快,你不会注意到或关心它是否被串行化,也可能是因为我在配置中做了一些愚蠢的事情。我使用的是FUSE的2.7.4版本,所以这可能是一个已经修复的旧错误。
如果有人对此有任何见解,我们将不胜感激!
我注册了FUSE邮件列表,发布了我的问题,最近收到了Miklos Szeredi的以下回复:
查找(即首先查找与名称相关联的文件)是按目录序列化。这是在VFS(公共文件系统部分在内核中),因此基本上任何文件系统都容易受到这个问题,不仅仅是导火索。
非常感谢米克洛斯的帮助。有关完整线程,请参阅http://fuse.996288.n3.nabble.com/GetAttr-calls-being-serialised-td11741.html.
我还注意到串行化是按目录进行的,即如果两个文件都在同一目录中,则会出现上述效果,但如果它们在不同的目录中,就不会出现这种效果。对于我的应用程序来说,这种缓解措施对我来说已经足够了,我的文件系统的客户端确实使用目录,因此,虽然我可能会期望大量的getattr调用连续进行,但它们都发生在同一目录上的可能性很低,我不必担心。
对于那些这种缓解措施还不够的人来说,如果你的文件系统支持目录列表,你可以利用David Strauss的建议,即使用readdir调用作为触发来启动你的缓存:
在我们的文件系统中,我们尝试预取和缓存属性readdir期间的信息(将不可避免地被请求),因此我们不必为每一个都打后端。
由于我的文件系统后端没有目录的概念,我无法利用他的建议,但希望这对其他人有帮助。
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 重载操作程序时出错>>用于类中的字符串 memebr
- 对字符串进行位操作
- 我可以在 C++ 中的函数体之外进行操作吗?
- MPI突然停止了对多个核心的操作
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 对字符数组中的元素执行逐位操作
- 如何在directx/c++中进行平移/缩放操作
- 逐位操作的隐式类型转换
- 为什么一个向量上的多线程操作很慢
- 排序时无法执行交换操作.我做的时候它会崩溃.为什么
- 位移操作和位掩码未检测到重复字符
- 如何进行特定的位操作?
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 字符串操作 - 字符计数
- 此代码中的操作流程是什么?C/C++.
- 复制和交换习惯用法与移动操作之间的交互
- 像union_这样的 Boost.Geometry 操作如何处理浮点类型的基本不精确性?
- 为什么 std::lerp 不适用于任何已实现所需操作的类型?
- FUSE getattr操作是否应该始终串行化