[转载]Linux Fuse

1. 原创声明

修改自:https://blog.csdn.net/fuhanghang/article/details/133072582


2. Linux Fuse 是什么?

用户空间文件系统(Filesystem in Userspace)是指完全在用户态实现的文件系统,是 Linux 中用于将某些网络空间(比如 SSH)挂载到本地文件系统的模块。

Linux 用于支持用户空间文件系统的内核模块是 FUSE,FUSE 一词有时特指 Linux 的用户空间文件系统。它是通用操作系统的重要组成部分。传统操作系统在内核层面对文件系统提供支持。而内核态的代码通常难以调试,生产率较低。

用户态文件系统是指文件系统的 data 和 metadata 都由用户态的进程提供(这种进程被称为"daemon")。对于 Micro-Kernel 的操作系统而言,在用户态实现文件系统不算什么,但对于 Macro-Kernel 的 Linux 而言,意义有所不同。

虽然叫做用户态文件系统,但不代表其完全不需要内核参与。因为在 Linux 中,对文件的访问都统一通过 VFS 层提供的内核接口进行(比如 open/read)。因此当进程(称为"user")访问由 daemon 实现的文件系统时,依然需要途经 VFS。

当 VFS 收到 user 进程对文件的访问请求,并且判断出该文件属于用户态文件系统(根据 mount type)时,会将该请求转交给名为"fuse"的内核模块。fuse 将该请求转换为与 daemon 约定的协议格式,传送给 daemon 进程。

在该三方关系中,fuse 内核模块起转接的作用,它帮助建立 VFS(也可以说是 user 进程)与 daemon 之间的交流通道,通俗地讲,其角色其实是「代理」。

这套框架的实现在 Linux 中即为 FUSE (Filesystem in Userspace)。如图所示,红框的部分是 FUSE 类型文件系统的具体实现,也是用户态文件系统的设计者可以发挥的空间。目前已有不下百种基于 FUSE 实现的文件系统,一些基于内核的文件系统也可以 porting 成用户态文件系统,比如 ZFS 和 NTFS。本文将选用 fuse-sshfs 进行演示。

首先,安装 sshfs 软件包,然后使用如下命令将远端机器的"remote-dir"目录 mount 到本机的"local-dir"目录:

sshfs <remote-ip>:<remote-dir> <local-dir>

在"/sys/fs"目录下,将生成名为"fuse"的目录,同时可以看到"fuse"内核模块已被加载(其对应的设备为"/dev/fuse"),并且本机的挂载目录类型为"fuse.sshfs":

# ls /sys/fs/
bpf  btrfs  cgroup  ecryptfs  ext4  fuse  pstore
# mount | grep fuse
fusectl on /sys/fs/fuse/connections type fusectl (rw,nosuid,nodev,noexec,relatime)
:/Users/zhoujingjiang/vm-data on /host-data type fuse.sshfs (rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other)
ubuntu@timd.cn:/home/ubuntu on /root/timdcn type fuse.sshfs (rw,nosuid,nodev,relatime,user_id=0,group_id=0)

下面以在"fuse.sshfs"文件系统中通过"touch"命令新建文件为例,查看 fuse 内核模块与 daemon 进程(即"sshfs")的具体交互流程(代码部分基于内核 5.2.0 版本)。

【第一轮】

最开始是校验 permission,此处的校验不等同于 VFS 的权限校验。其主要目的是避免其他 user 访问私有的 fuse 文件系统。

然后是根据文件路径查找文件的 inode。由于是新建的文件,inode 不在内核的 inode cache 中,所以需要向 daemon 发送"lookup"请求:

这些请求将被放到 pending queue 中,等待 daemon 进程的回复,user 进程将陷入休眠:

作为 daemon,sshfs 进程通过读取"/dev/fuse"设备文件获取数据,如果 pending queue 为空,它将陷入阻塞等待:

当 pending queue 中有请求时,daemon 进程将被唤醒,处理这些请求。被处理的请求将被移入 processing queue,待 daemon 进程向 fuse 内核模块做出 reply 后,user 进程将被唤醒,对应的 request 将被从 processing queue 中移除。

【第二轮

接下来是执行"touch"命令时触发的其他系统调用,如果是之前访问过的 data/metadata,那么很可能存在于 cache 中,再次访问时,fuse 内核模块可以自行解决,不需要去用户空间往返一趟,否则需要上报 daemon 进程进行处理。

get_fuse_conn() 获取的是在 fuse 类型的文件系统被 mount 时创建的"fuse_conn"结构体实例。作为 daemon 进程与 Kernel 联系的纽带,除非 daemon 进程消亡,或对应的 fuse 文件系统被卸载,否则该 connection 将一直存在。

在 daemon 进程这一端,还是类似的操作。需要注意的是区别 fuse_write/read() fuse_dev_write/read() 这两个系列函数,前者是 user 进程在访问 fuse 文件系统上的文件时 VFS 的读写请求,属于对常规文件的操作,而后者是 daemon 进程对"/dev/fuse"这个代表 fuse 内核模块的设备的读写,目的是获取 request 和给出 reply。

【第三轮

fuse 内核模块与 daemon 进程的最后一轮交互是在代表 fuse 文件系统的 superblock 中获取 inode号,并且填写 metadata 相关信息。

不难发现,在 fuse 文件系统中,即便执行简单的"touch"操作,用户态和内核态的切换都比较频繁,并且还伴随着多次数据拷贝。相比于传统的内核文件系统,它整体的 I/O 吞吐量更低,而延迟也更大。

那为什么 fuse 在操作系统支持的文件系统中依然占据一席之地呢?这是因为在用户态开发有很多优势。一是便于调试,特别适合做新型文件系统 prototype 的快速验证,因此在学术研究领域颇受青睐。在内核里,只能用 C 语言,而在用户态,则没有该限制。

二是内核的 bug 往往导致整个系统 crash。在虚拟化应用中更为严重,因为宿主机 crash 将导致其上面运行的所有虚拟机 crash,而用户态的 bug 造成的影响相对有限。

所以硬币的正面是便于开发,而反面则是影响性能。前者是主观感受,后者则能够用客观的实验数据验证,那应该用什么方法才能相对准确地衡量 fuse 带来的损耗呢?

还是使用 fuse-sshfs,不过此处不再使用远端挂载,而是采用本地挂载的方式。假设本机的"dir-src"目录位于 ext4 文件系统:

sshfs localhost:<dir-src> <dir-dst>

当 daemon 进程收到请求后,它需要再次进入内核,访问 ext4 的内核模块,这种文件系统模式被称为"stackable":

以 user 进程向 fuse 文件系统发出 write() 请求为例,右边红框部分是原生的 ext4 调用路径,而左边多出来的是引入 fuse 后增加的路径:

根据这篇文档给出的数据,在这一系统调用中使用"getxattr"所形成的 request 需要 2 倍的"user-kernel"交互量。对于顺序写,相比原生 ext4 文件系统,I/O 吞吐量降低 27%,随机写则降低 44%。

不过,在 fuse 文件系统诞生的这么多年里,大家还是为它想出很多优化举措。比如顺序读写时,可以设计为向 daemon 进程批量发送 request 的形式(但随机读写不适合)。

还有就是使用 splicing 这种 zero-copy 技术,由 Linux 内核提供的 splicing 机制允许用户空间在转移两个内核的内存 buffer 数据时,不需要拷贝,因此尤其适合在 stackable 模式下,从 fuse 内核模块直接向 ext4 内核模块传递数据(但 splicing 通常用于超过 4K 的请求)。

经过这些努力,fuse 文件系统的性能可以达到什么程度呢?根据这篇报告列出的测试结果,相比起原生 ext4,在最理想的情况下,fuse 的性能损耗可以控制到 5% 以内,但最差的情况则是 83%。同时,其对 CPU 的占用增加 31%。

从 Android v4.4 到 v7.0 之间存在的 sdcard daemon,到最近几年的 Ceph 和 GlusterFS,都曾经采用过或正在采用基于 FUSE 的实现。FUSE 在 network filesystem 和虚拟化应用中都有自己的用武之地,其目的不是取代在内核文件系统,而是作为有益的补充(理论上,FUSE 可以用于实现根文件系统,但是不建议这么做,"can do" 和 "should do" 是两回事)。

转自:linux fuse指的是什么-PHP博客-李雷博客