文献阅读: To FUSE or Not to FUSE: Performance of User-Space File Systems

To FUSE or Not to FUSE: Performance of User-Space File Systems

论文作者:Bharath Kumar Reddy Vangoor, Stony Brook University; Vasily Tarasov, IBM Research-Almaden; Erez Zadok, Stony Brook University

发表会议:FAST'17: Proceedings of the 15th Usenix Conference on File and Storage Technologies

发表时间:February 2017

FUSE,即 Filesystem in USErspace,用户空间的文件系统。对于传统的宏内核而言,文件系统都实现在内核态,而 FUSE 这项技术使得可以在用户空间实现文件系统。用户空间文件系统通常用于进行原型验证,这是因为它具有开发便捷的特点。然而,在用户空间实现文件系统却带来了巨大的性能开销,因此 FUSE 颇具争议:FUSE 能否用于开发生产环境下的文件系统。本文对 FUSE 进行了性能追踪和统计,并在真实负载下分析了 FUSE 的性能瓶颈。

FUSE design

High-Level Architecture

从高层视角来看 FUSE,一个 FUSE 文件系统包含两个部分:

  • 内核态 FUSE driver,它对用户态暴露 /dev/fuse,用于用户态 FUSE deamon 和 内核通信,并负责管理和分发来自应用程序的 request
  • 用户态 FUSE library,用户态的 FUSE deamon 基于 FUSE library 编写,实际上,这个库对 FUSE 操作进行了更高层的封装,使得基于 FUSE 构建文件系统更加容易

FUSE 的简化工作流程如下:

  1. 用户空间 Application 发起对 FUSE 文件系统的文件操作
  2. 内核态 VFS 将操作请求路由到 FUSE 驱动程序
  3. 根据请求类型,创建 FUSE request,并挂载到队列中
  4. /dev/fuse 分发 request 给用户态的 FUSE deamon
  5. FUSE deamon 根据收到的 request 执行对应的处理
  6. 处理结束后,FUSE deamon 通过 /dev/fuse 发送 reply 告知内核
  7. FUSE driver 将请求项从队列删除,将操作结果返回给用户程序

Implementation Details

User-Kernel Protocol

上表总结了 FUSE 的 43 种 request,其中加黑的与 VFS 操作语意有些出入。

  • INIT:当文件系统被挂载时由内核产生。用户和内核确认
    1. 协议版本
    2. 相互支持的能力集合(如 READDIRPLUS 的支持)
    3. 各种参数的设置(如 FUSE 预读大小、时间粒度)
  • DESTROY:当文件系统被卸载时由内核产生。FUSE deamon 执行清理工作,任何与该文件系统 request 都无法被送往用户空间处理,而后,FUSE deamon 将退出
  • INTERRUPT:由内核发出,用于停止某个 request(如一个等待 READ 的进程终止了)。

sequence#:每一个 request 都有一个唯一的 sequence# 用于识别,该序列号由内核分配

node ID:一个 64 位的无符号整数,用于在内核和用户空间识别 inode

  • LOOKUP:进行 path-to-inode 转换,每次 lookup 一个 inode 时,内核就将其放入 inode 缓存
  • FORGET & BATCH_FORGET:将 inode 从缓存中删除,并通知 FUSE deamon 释放相关数据结构
  • OPEN:打开文件,FUSE deamon 可以选择性地返回一个 64 位的文件句柄,而后每一个与该文件有关的 request 都将带有这个句柄

file handle:FUSE deamon 可以利用该句柄存储打开文件的信息,例如在栈文件系统中,FUSE 文件系统可以将底层文件系统的描述符存储为 FUSE 文件句柄的一部分

  • FLUSH:关闭文件
  • RELEASE:之前打开的文件没有任何引用时发出
  • OPENDIR & RELEASEDIR:与 OPEN 和 RELEASE 语义相同,但操作对象是目录文件
  • READDIR & READDIRPLUS:返回目录项,READDIRPLUS 还会返回每个项的元数据信息
  • ACCESS:内核用于确认用户进程对于某个文件是否具有访问权限

Library and API Levels

在用户空间 FUSE 的 API 严格来说有两层,底层 API 负责实现与内核 FUSE driver 的基本通信,基于底层 API 所构建的高层 API 用于方便用户态文件系统的编写。

底层 API vs. 高层 API \(\approx\) flexibility vs. development ease

底层 API:

  1. 接收并翻译来自内核的 request
  2. 发送格式化的 reply
  3. 为文件系统的配置和挂载提供便利
  4. 为内核和用户空间隐藏可能的版本差异

高层 API 包含更多语义,尤其是隐藏了 path-to-inode map 的细节,从而高层 API 中都是直接操作文件路径的方法。

Queues

FUSE 的队列存在于内核态,由 request 构成。FUSE driver 通过管理 request 队列进行 request 调度。FUSE 中包含 5 个不同的队列,分别用于管理不同种类的 request,同时,不同队列中的 request 具有不同的优先级,当 FUSE driver 决定发送哪个 request 给 FUSE deamon 时,优先级就会起到作用。

FUSE 中的 5 个队列名称及其对应功能:

名称 功能
interrupts INTERRUPT request
forgets FORGET request
pending 同步 request
processing 正在处理的 request
background 异步 request

FUSE Workflow

Linux FUSE 文档1中给出了一个 FUSE 的工作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|  "rm /mnt/fuse/file"               |  FUSE filesystem daemon
| |
| | >sys_read()
| | >fuse_dev_read()
| | >request_wait()
| | [sleep on fc->waitq]
| |
| >sys_unlink() |
| >fuse_unlink() |
| [get request from |
| fc->unused_list] |
| >request_send() |
| [queue req on fc->pending] |
| [wake up fc->waitq] | [woken up]
| >request_wait_answer() |
| [sleep on req->waitq] |
| | <request_wait()
| | [remove req from fc->pending]
| | [copy req to read buffer]
| | [add req to fc->processing]
| | <fuse_dev_read()
| | <sys_read()
| |
| | [perform unlink]
| |
| | >sys_write()
| | >fuse_dev_write()
| | [look up req in fc->processing]
| | [remove from fc->processing]
| | [copy write buffer to req]
| [woken up] | [wake up req->waitq]
| | <fuse_dev_write()
| | <sys_write()
| <request_wait_answer() |
| <request_send() |
| [add request to |
| fc->unused_list] |
| <fuse_unlink() |
| <sys_unlink() |
  1. 用户程序发起文件系统操作 rm /mnt/fuse/file
  2. FUSE deamon 读 /dev/fuse,但此时 request 队列为空,因此触发 request_wait(),进入睡眠
  3. 用户程序触发系统调用 sys_unlink(),内核路由到 FUSE 操作函数,由将操作对应 request 加入 pending 队列,并唤醒 FUSE deamon
  4. FUSE deamon 被唤醒后,将 requset 从 pending 队列移除,并将其加入 processing 队列
  5. FUSE deamon 返回用户空间后,根据读到的 request 进行相应自定义操作
  6. FUSE deamon 执行完操作后,向 /dev/fuse 写 reply,将对应 request 从 processing 队列移除,并唤醒等待的用户程序
  7. 用户程序从内核系统调用返回,完成操作

Instrumentation

为了研究 FUSE 的性能表现,研究人员利用底层 API 实现了一个栈(堆叠式)文件系统 Stackfs,直接建立在 ext4 文件系统之上,这样做的原因:

  • 大部分基于 FUSE 的文件系统都是堆叠式的
  • 减少 FUSE deamon 的复杂度,这样能够尽可能还原 FUSE driver 和 FUSE library 性能表现

测试记录了在请求处理各个阶段(包括内核和用户态)花费的时间,为此,引入一个 43x32 的二维数组,行表示 43 种不同的 request,列表示以 2 为底的对数坐标时间。

策略:在内核空间实现 3 个数组,分别用于监视 background、pending 和 processing 队列;在用户空间实现 1 个数组,用于监视 FUSE deamon 处理一个 requset 的时间 机制:内核空间利用了 fusectl,用户空间利用了 SIGUSR1

Methodology

FUSE 配置、负载信息和实验平台,在此不一一列举。

实验中 FUSE 氛围两种配置:基本配置 StackfsBase 和 优化配置 StackfsOpt,优化配置开启了 FUSE 的所有增强功能:(1)写回缓冲;(2)max_write 设置为 128KiB;(3)FUSE deamon 多线程;(4)splicing(零拷贝)对所有操作开启

Evaluation

测试结果汇总在表格中,其中的百分比表示相对于 ext4 性能变化,得到总体性的结果如下(原文翻译):

绿色区域:性能下降在 5% 以下 黄色区域:性能下降在 5-25% 之间 橙色区域:性能下降在 25-50% 之间 红色区域:性能下降在 50% 以上

Observation 1. 测试结果由于负载、外设和 FUSE 配置而导致的差异范围为 -83.1%(files-cr-1th [row #37])到 +6.2%(web-server [row #45])

Observation 2. 对于大多数负载,FUSE 的优化配置能显著提升性能,如 web-server [row #45],StackfsOpt 将性能提升 6.2%,而 StackfsBase 导致性能下降 50%。

Observation 3. 尽管对于一些负载优化配置能提升性能,对于另一部分,优化配置反而导致性能下降。如 files-rd-1th [row #39],StackfsOpt 导致性能下降 35%,下降幅度大于 StackfsBase 配置。

Observation 4. 在 Stackfs 性能最佳的配置中(包括 StackfsOpt 和 StackfsBase),只有 2 个创建文件的负载(共 45 个工作负载)在红色区域:files-cr-1th [row #37] 和 files-cr-32th [row #38]。

Observation 5. Stackfs 的性能与底层设备有很大关系。如对于顺序读 [row #1-12],Stackfs 在 SSD 上没有性能下降,而在 HDD 上有 26% 到 42% 的性能下降。而对于 mail-server [row #44] 这个负载,情况恰恰相反。

Observation 6. 对于所有的写负载(顺序的和随机的),至少有一个 Stackfs 配置能够在使用 HDD 和 SSD 的情况下都在绿色区域。

Observation 7. 对于 HDD 和 SSD,顺序读 [rows #1-12] 的性能都在绿色区域;然而对于运行在 HDD 上的 seq-rd-32th-32f [rows #5-8],性能位于橙色区域。随机读的结果 [rows #13-20] 在所有 4 个区域均有分布。另外,对于 HDD 和 SSD,性能随着 I/O 规模的增加而上升。

Observation 8. 总体上来说,相比于数据密集型负载 [rows #1-36],Stackfs 在元数据密集型和宏工作负载 [rows #37-45] 下明显性能更差,尤其是对 SSD 而言。

Observation 9. 相较于 Ext4,Stackfs CPU 利用率更高,在 +0.13% 到 +31.2% 之间;同样的,每操作的 CPU 周期数也提升了 1.2\(\times\) 到 10 \(\times\) 不等。这一行为在 HDD 和 SSD 上表现一致。

Observation 10. 对于大多数负载,StackfsOpt 的每操作的 CPU 周期数高于 StackfsBase。但对于负载 seq-wr-32th-32f [rows #25–28] 和 rnd-wr-1th-1f [rows #30–32],StackfsOpt 的每操作的 CPU 周期数更低。


  1. 1.https://www.kernel.org/doc/html/next/filesystems/fuse.html#kernel-userspace-interface ↩︎