Skip to the content.

FUSE 机制梳理

本文基于原始笔记 /home/martins3/data/vn/docs/kernel/fs/fuse/fuse.md 重新整理。原始文件只作为问题来源读取,没有修改。

主要源码和材料依据:

1. FUSE 是什么

FUSE 是一个把 VFS 文件系统操作转发给用户态进程处理的内核/用户态协议框架。它不是单纯的库,也不是一个具体文件系统。完整系统通常包含:

一个普通进程访问挂载点时仍然走标准 VFS:

应用程序
  -> read/write/open/lookup/stat 等 syscall
  -> VFS
  -> FUSE 内核文件系统 fs/fuse/*
  -> FUSE 请求队列
  -> /dev/fuse read 或 io_uring CQE
  -> 用户态 daemon 处理请求
  -> /dev/fuse write 或 io_uring commit
  -> FUSE 内核唤醒原 syscall

所以 FUSE 的关键价值是:应用程序看到的是普通 POSIX 文件系统接口,具体文件系统逻辑却可以在用户态实现。它降低了文件系统开发、调试和部署成本,也允许非特权用户挂载自己的文件系统。代价是多一次内核/用户态往返、数据拷贝或页映射管理、调度开销,以及更复杂的缓存一致性问题。

2. FUSE 在内核中的定位

FUSE 内核代码一边接 VFS,一边接 daemon 通信通道:

FUSE 的“文件系统逻辑”不是完全在用户态,也不是完全在内核态。更准确的拆分是:

3. FUSE 协议和核心对象

3.1 UAPI 协议

include/uapi/linux/fuse.h 定义了 FUSE 协议对象。所有普通请求都有 header:

unique 是单个连接里的请求 ID。daemon 写回 reply 时,内核用它在 processing 队列里找到原来的 fuse_req

核心 opcode 包括:

3.2 struct fuse_conn

struct fuse_conn 是一个 daemon 连接。它不是一个单独文件,而是一条“内核 FUSE 实例”和“daemon 通道”的连接。

关键字段:

3.3 struct fuse_mount

struct fuse_mount 绑定 fuse_conn 和 superblock。一个 connection 可以有多个 fuse_mount,这允许 submount 共享 daemon 连接,但有各自的 superblock 和设备号。

3.4 struct fuse_inode

struct fuse_inode 包含内核 struct inode,并附带 FUSE 私有状态:

fuse_iget()iget5_locked()nodeid 查找或创建内核 inode,然后用 daemon 返回的 fuse_attr 填充属性。也就是说,内核维护 icache,但 inode 的权威元数据来自 daemon。

3.5 struct fuse_file

struct fuse_file 是内核里每个打开文件的 FUSE 私有状态:

关键点:FOPEN_DIRECT_IOFOPEN_KEEP_CACHEFOPEN_PASSTHROUGH 是按 open file 生效,不是全局 inode 属性。但 iomode.c 会限制同一 inode 上 cached 和 uncached/passthrough 混用,避免 page cache 和直接后端访问并发破坏一致性。

3.6 struct fuse_argsstruct fuse_args_pagesstruct fuse_req

struct fuse_args 描述一次 FUSE 请求的 opcode、nodeid、输入参数、输出参数和页参数方向。struct fuse_args_pagesfuse_args 外增加 folio 数组和每个 folio 的 offset/length。

struct fuse_req 是真正排队的请求对象:

4. 请求生命周期

4.1 传统 /dev/fuse 同步请求

LOOKUPGETATTR、同步 READ 等为例,调用路径是:

VFS 操作
  -> fs/fuse/dir.c 或 file.c 构造 fuse_args
  -> fuse_simple_request()
  -> __fuse_simple_request()
  -> fuse_get_req()
  -> fuse_args_to_req()
  -> __fuse_request_send()
  -> fuse_send_one()
  -> fiq->ops->send_req(),传统路径为 fuse_dev_queue_req()
  -> request_wait_answer() 睡眠等待

fuse_dev_queue_req() 做三件事:

daemon 调用 read(/dev/fuse) 时进入:

fuse_dev_read()
  -> fuse_dev_do_read()
  -> 从 fiq->pending 取请求
  -> 拷贝 fuse_in_header 和输入参数到 daemon buffer
  -> 如果需要 reply,把请求移到 fuse_pqueue.processing[hash(unique)]
  -> 设置 FR_SENT

daemon 处理后调用 write(/dev/fuse)

fuse_dev_write()
  -> fuse_dev_do_write()
  -> 读取 fuse_out_header
  -> 按 unique 在 processing hash 表找到 fuse_req
  -> 拷贝输出参数到 req->args
  -> fuse_request_end()
  -> 唤醒原 syscall 线程

所以,FUSE 请求不是“系统调用直接调用用户态函数”。中间有明确的队列、request ID、copy state、processing hash、等待队列和中断处理。

4.2 后台请求和限流

readahead、writeback、release 等可能走 background 路径:

fuse_simple_background()
  -> fuse_get_req(..., for_background=true)
  -> fuse_request_queue_background()
  -> bg_queue / active_background / num_background

max_background 限制后台请求总量。达到上限后 fc->blocked = 1,后续 background 请求会等待。congestion_threshold 达到后,内核会跳过一些异步 readahead,或者延迟非同步 writeback,避免 daemon 堵塞时继续放大积压。

/sys/fs/fuse/connections/<id>/waitingmax_backgroundcongestion_thresholdabort 来自 fusectl,是生产上判断 FUSE 是否 hang 住和强制 abort 的重要入口。

4.3 信号中断

request_wait_answer() 对中断的处理和文档一致:

这解释了为什么杀掉访问 FUSE 的进程不一定立刻终止底层 daemon 正在处理的操作:请求是否已经进入 userspace 决定了后续行为。

5. mount 和初始化

传统 mount 时,用户态一般先打开 /dev/fuse,再把 fd 通过 mount option fd=N 传给内核。内核建立 fuse_conn 后发送 FUSE_INIT

fuse_new_init() 构造 FUSE_INIT,内核声明自己支持的能力,例如:

daemon 的 FUSE_INIT reply 决定实际启用哪些能力。process_init_reply() 会设置 fc->writeback_cachefc->async_diofc->parallel_diropsfc->passthroughfc->io_uring 等字段,并确定 max_writemax_pages、readahead 页数、后台请求限制。

需要注意:

6. dcache 和 icache 到底谁维护

答案是:内核和 daemon 都维护,但分工不同。

6.1 dcache

内核使用普通 VFS dentry。FUSE 给 dentry 挂了 struct fuse_dentry

FUSE_LOOKUP reply 的 struct fuse_entry_out 包含:

其中 entry_valid 控制 dentry 名字缓存,attr_valid 控制 inode 属性缓存。

fuse_lookup() 发送 FUSE_LOOKUP,拿到 reply 后:

fuse_dentry_revalidate() 在 dentry 过期、LOOKUP_REVALLOOKUP_EXCL 等场景重新发 FUSE_LOOKUP。如果新 lookup 返回不同 nodeid,内核让 VFS invalid dentry 并重查。

daemon 也可以主动写 notification:

6.2 icache

内核维护 struct inode 和 inode hash。FUSE 用 nodeid 作为 inode 查找 key:

fuse_lookup_name()
  -> FUSE_LOOKUP
  -> fuse_iget(sb, nodeid, generation, attr, ...)
  -> iget5_locked(sb, nodeid, ...)

daemon 维护 nodeid 的生命周期。内核每 lookup 成功一次会增加 fi->nlookup。inode 回收或 dentry 释放到一定程度后,内核发送 FUSE_FORGETFUSE_BATCH_FORGET,告诉 daemon 对应 nodeid 的 lookup 引用减少。

这就是 FUSE 中 LOOKUP/FORGET 的核心语义:它不是普通路径查询而已,还承担 daemon 端对象生命周期引用计数。

6.3 属性缓存

属性缓存在 fuse_inode

fuse_update_get_attr() 会根据 i_timeinval_mask、statx sync flag 决定是否发送 FUSE_GETATTRFUSE_STATX。如果属性有效,就用 generic_fillattr() 从内核 inode 填 stat。

writeback-cache 开启时,内核更信任本地的 size/mtime/ctime。fuse_get_cache_mask() 对 regular file 返回 STATX_MTIME | STATX_CTIME | STATX_SIZEfuse_change_attributes_i() 会避免 daemon 返回的旧 size/mtime/ctime 覆盖本地 dirty page cache 状态。

7. FUSE I/O 模式

内核文档把 FUSE I/O 分成三类:

当前源码还增加了 passthrough、DAX、io_uring 通道等优化,但基础分类仍然成立。

7.1 cached read

普通 read 路径:

fuse_file_read_iter()
  -> fuse_cache_read_iter()
  -> generic_file_read_iter()
  -> page cache miss
  -> fuse_read_folio() / fuse_readahead()
  -> fuse_do_readfolio() 或 fuse_send_readpages()
  -> FUSE_READ

现代源码里 FUSE cached read 使用 iomap 的 folio 辅助函数:

但这里的 fuse_iomap_begin() 只是返回 IOMAP_MAPPED、offset 和 length。它没有把文件 offset 映射到块设备 sector,也没有构造 bio。真正的数据来源仍然是 FUSE_READ 请求。

7.2 cached write-through

默认 cached write 是 write-through。路径大致为:

fuse_file_write_iter()
  -> fuse_cache_write_iter()
  -> fuse_perform_write()
  -> fuse_fill_write_pages()
      把用户 iov_iter 数据 copy 到 page cache folio
  -> fuse_send_write_pages()
      构造 FUSE_WRITE,携带 folio 作为 in_pages
  -> daemon reply 写入长度

这说明 write-through 不是直接把用户 buffer 给 daemon。内核先把数据复制到 page cache folio,再把这些 folio 作为 FUSE 请求 payload 发给 daemon。

部分页写入时,如果 folio 还不是 uptodate,FUSE 可能保持 folio locked 并等待 daemon 写入结果;如果短写,则清掉 uptodate 防止缓存错误数据。

7.3 writeback-cache

如果 INIT reply 启用 FUSE_WRITEBACK_CACHE

源码中 writeback 路径:

fuse_cache_write_iter()
  -> iomap_file_buffered_write()
  -> iomap_dirty_folio()
  -> fuse_writepages()
  -> iomap_writepages()
  -> fuse_iomap_writeback_range()
  -> fuse_writepages_send()
  -> fuse_send_writepage()
  -> fuse_simple_background()

writeback-cache 假设所有文件修改都经过本 FUSE 内核客户端,因此一般不适合没有强一致控制的网络文件系统。如果后端也会被其他客户端修改,daemon 必须非常谨慎地使用 invalidation 或放弃 writeback-cache。

7.4 direct-io

daemon 在 FUSE_OPEN reply 中设置 FOPEN_DIRECT_IO 后,该 open file 的 read/write 绕过 page cache:

fuse_file_read_iter()
  -> ff->open_flags & FOPEN_DIRECT_IO
  -> fuse_direct_read_iter()
  -> fuse_direct_io()
  -> fuse_get_user_pages()
  -> FUSE_READ

fuse_file_write_iter()
  -> ff->open_flags & FOPEN_DIRECT_IO
  -> fuse_direct_write_iter()
  -> fuse_direct_io(..., FUSE_DIO_WRITE)
  -> fuse_get_user_pages()
  -> FUSE_WRITE

direct-io 的重点是绕过内核 page cache,不是保证零拷贝。

fuse_get_user_pages() 对用户 iov_iter 调用 iov_iter_extract_pages(),把用户页提取为 folio/page 描述,设置:

之后 /dev/fuse 路径里的 fuse_copy_folio() 仍然会在内核和 daemon buffer 之间拷贝或通过 splice/pipe 做有限优化。daemon 不能拿到应用进程的 page 指针后直接写内核 page。daemon 是普通用户态进程,它看到的是 /dev/fuse read/write 或 io_uring buffer。

direct-io 还要处理 page cache 冲突:

7.5 passthrough

FUSE passthrough 是新的性能优化:daemon 告诉内核某个 FUSE 文件其实可以直接映射到一个 backing file,读写绕过 daemon。

启用条件:

读写路径:

fuse_file_read_iter()
  -> FOPEN_DIRECT_IO 优先
  -> fuse_file_passthrough(ff)
  -> fuse_passthrough_read_iter()
  -> backing_file_read_iter()

fuse_file_write_iter()
  -> FOPEN_DIRECT_IO 优先
  -> fuse_file_passthrough(ff)
  -> fuse_passthrough_write_iter()
  -> backing_file_write_iter()

passthrough 适合 overlay、加密、元数据由 daemon 管理但大文件数据实际在本地 backing filesystem 的场景。它不适合任意后端,例如 S3、SSH、远端 KV store,因为这些后端没有可注册给内核直接读写的本地 struct file

当前实现限制:

7.6 DAX 和 virtiofs

virtiofs 放在 fs/fuse 下不是偶然。virtiofs 复用 FUSE 协议、inode/file/dir 逻辑和很多请求结构,只是通信通道从 /dev/fuse 换成 virtqueue。

virtiofs 的 daemon 通常在 host 侧,例如 virtiofsd。guest 内核里的 virtio_fs.c 把 FUSE 请求打包进 virtqueue。对 guest 来说,server 不在 guest 本机进程里;对整体系统来说,它仍是一个处理 FUSE 协议的用户态/host 侧服务。

DAX 路径由 fs/fuse/dax.c 实现。核心是:

DAX 不是普通 FUSE 的默认行为。它主要服务 virtiofs 这类本地虚拟化共享目录性能场景。

8. /dev/fuse 数据拷贝细节

dev.cfuse_copy_state 负责在 daemon buffer 和 request 参数之间搬数据:

传统 read/write /dev/fuse 的语义决定了 daemon 最终读写的是自己的用户态 buffer。FUSE 可以通过 folio、pipe、splice、iov_iter 等减少中间开销,但不能把“用户进程 read 的目标 page”直接交给 daemon 任意写。内核必须控制页生命周期、权限、dirty 标记、cache 一致性和异常路径。

9. FUSE-over-io_uring

本地源码包含 fs/fuse/dev_uring.c 和文档 Documentation/filesystems/fuse/fuse-io-uring.rst。当前设计目标是优化 kernel/userspace 通信通道,而不是改变 FUSE 协议本质。

传统通道:

daemon read(/dev/fuse) 获取请求
daemon write(/dev/fuse) 提交 reply

io_uring 通道:

daemon 提交 IORING_OP_URING_CMD / FUSE_IO_URING_CMD_REGISTER
  -> 注册一个 header buffer + payload buffer 的 ring entry
  -> 每个 CPU queue 至少有 entry 后 ring ready

内核有 FUSE 请求
  -> fuse_uring_queue_fuse_req()
  -> 找可用 ring entry
  -> fuse_uring_copy_to_ring()
  -> io_uring_cmd_done() 让 daemon 收到 CQE

daemon 处理后
  -> IORING_OP_URING_CMD / FUSE_IO_URING_CMD_COMMIT_AND_FETCH
  -> fuse_uring_commit()
  -> fuse_uring_copy_from_ring()
  -> fuse_request_end()
  -> 同一个 entry 立刻 fetch 下一个请求

关键对象:

它的收益主要来自:

但它没有自动解决所有零拷贝问题。当前源码仍用 copy_to_user() 写 header,用 import_ubuf() + fuse_copy_args() 在 ring payload 和请求参数之间搬运 payload。文档还明确说当前并非所有请求类型都支持 io_uring,notifications 和 interrupts 仍需要 /dev/fuse 路径处理。

原笔记提到 “support io-uring registered buffers” 的 patch。从当前本地源码看,FUSE-over-io_uring 已经有 daemon 注册 header/payload buffer 的模型,但没有看到独立的、类似 io_uring registered buffer 固定缓冲池的 FUSE UAPI opcode;需要按对应 patch 版本单独确认。

10. FUSE 和 iomap

当前源码中 FUSE 已经使用 iomap 框架,但用途要分清。

file.c 中:

这说明 iomap 在这里主要是 folio/page-cache 管理框架,帮助 FUSE 适配大 folio、部分 uptodate、dirty tracking、writeback 聚合。它不意味着 FUSE 现在有 block mapping,也不意味着 FUSE 请求变成 bio。

原笔记提到 Wong 系列中的 FUSE_IOMAP_BEGIN / FUSE_IOMAP_END。在当前本地 include/uapi/linux/fuse.henum fuse_opcode 中没有这两个 opcode。因此它至少不在这份源码的 FUSE UAPI 中。这个方向如果合入,价值主要在 daemon 能把文件 offset 映射到底层本地文件或块设备 extent 的场景;如果后端是 S3、SSH、Ceph 用户态协议、数据库、日历 API 等没有本地 extent 语义的服务,收益会小很多。

11. 是否需要构造 bio

普通 FUSE I/O 不构造 bio。

bio 是块层 I/O 描述,用于文件系统把逻辑块映射到底层块设备后提交给 block layer。FUSE 的普通读写是协议请求:

FUSE_READ / FUSE_WRITE
  -> fuse_args / fuse_args_pages
  -> /dev/fuse 或 io_uring
  -> daemon 自己决定从哪里取数据或写到哪里

daemon 的后端如果是一个本地文件,它自己调用 pread/pwrite,最终那个本地文件系统可能构造 bio。daemon 的后端如果是网络对象存储,就可能是 TCP/HTTP 请求。FUSE 内核层不关心这些后端细节。

例外和相关场景:

12. 多线程 daemon 如何工作

内核侧天然支持多个 daemon 线程处理同一个连接:

libfuse 的 fuse_session_loop_mt_31() / fuse_session_loop_mt_312() 是用户态线程池封装。官方 doxygen 源码显示它维护 worker list、max_threadsmax_idle_threadsclone_fd 等配置,并创建 worker 处理请求。clone_fd 允许 worker 使用 cloned session fd,减少某些共享 fd 竞争。

daemon 需要自己保证文件系统语义的并发安全。例如同一个目录下 rename/unlink/create 的顺序,后端元数据锁,file handle 生命周期,缓存 invalidation,都不是内核能完全替 daemon 解决的。内核只保证 FUSE 请求匹配和 VFS 层必要的锁;具体后端一致性由 daemon 实现。

FUSE_PARALLEL_DIROPS 会影响目录操作并发。未启用时,fuse_lock_inode()fuse_inode.mutex 串行 lookup 和 readdir,保持老行为。启用后 daemon 表示自己能处理并行目录操作。

13. 权限和非特权挂载

FUSE 重要特点之一是非特权挂载。内核文档强调这不是 /etc/fstab user 的同一概念,而是普通用户通过 setuid fusermount 建立自己的 FUSE mount。

安全原则:

fuse_allow_current_process()default_permissionsallow_other、uid/gid 映射等共同控制访问。请求 header 中也带调用者 uid/gid/pid,daemon 可以据此实现自己的权限。

passthrough 更敏感,因此当前要求 CAP_SYS_ADMIN。原因包括 backing fd 被内核长期持有后的可见性、资源计数、stacking loop 和 shutdown deadlock 风险。

14. 典型项目和生态定位

FUSE 适合几类场景:

FUSE 的商业化空间通常来自两点:

它不适合所有场景。高吞吐、低延迟、强一致、多客户端共享 page cache、数据库 WAL、块设备语义等场景,需要谨慎评估。NFS、SMB、CephFS kernel client、iSCSI/NVMe-oF、9p、virtiofs、原生内核文件系统可能更合适。

15. 原始笔记问题整理

15.1 FUSE I/O 需要构造 bio 吗

不需要,普通 FUSE 层不构造 bio。FUSE read/write 构造的是 FUSE_READ/FUSE_WRITE 协议请求,payload 可以是普通参数或 folio/page 数组。后端如果最终写本地磁盘,那是 daemon 或 passthrough 的 backing filesystem 走自己的块层路径。

15.2 FOPEN_DIRECT_IO 时数据会拷贝吗

会有数据搬运或页提取,不等于零拷贝。

FOPEN_DIRECT_IO 绕过 FUSE inode 的 page cache。read/write 走 fuse_direct_io(),它用 iov_iter_extract_pages() 从调用者 iov_iter 提取用户页,构造 fuse_args_pages。但传统 /dev/fuse daemon 最终仍通过 read/write 设备 fd 读写自己的 buffer,fuse_copy_folio() 会在 daemon buffer 和 folio 之间复制,splice 场景有有限 page move/ref 优化。

所以 direct-io 的主要语义是 bypass page cache、避免 readahead/cache 污染和缓存一致性问题,不是 daemon 直接得到应用进程页指针。

15.3 read 时用户态 daemon 能直接往内核提供的 page 写数据吗

不能按“拿到 page 指针直接写”的方式理解。

cached read 时,内核 page cache miss 后分配/锁定 folio,然后发 FUSE_READ。daemon 写 reply 到 /dev/fuse 或 io_uring payload buffer。内核再通过 fuse_copy_out_args() / fuse_copy_folio() 把 reply payload 填到 folio,最后 iomap_finish_folio_read() 标记 uptodate 并解锁。

daemon 是用户态进程,没有内核 page 指针。它只能通过 FUSE 协议通道提交数据。

15.4 “似乎所有操作都需要转发一下”是否正确

大体正确,但有缓存和 fast path。

需要转发的典型操作:

不一定转发的情况:

15.5 FUSE 的 dcache 和 icache 是用户态维护吗

不是。内核维护 dcache 和 icache,用户态维护与之对应的权威命名和元数据状态。

内核缓存对象:

daemon 控制:

15.6 FUSE server 必须在本机吗

传统 FUSE mount 的 daemon 必须是挂载命名空间中打开 /dev/fuse 并参与 mount 的本机进程。它可以再连接远端服务,例如 sshfs 连 SSH/SFTP,s3fs 连 S3,rclone 连云存储。

从应用和内核视角看:

应用 -> 本机 FUSE 内核 -> 本机 daemon -> 远端服务

virtiofs 是特殊情况。guest 内核使用 virtio 设备发送 FUSE 请求,host 侧 virtiofsd 处理请求。server 不在 guest 本机进程里,但仍是 FUSE 协议服务端。

15.7 virtiofs 为什么放在 fs/fuse

因为 virtiofs 是 FUSE 协议的传输和共享内存扩展,而不是一个完全独立的文件系统实现。它复用:

差异在通道:

15.8 io_uring 对 FUSE 的收益是什么,能解决 zero-copy 吗

收益主要是通信通道效率,不是彻底 zero-copy。

io_uring 路径避免 daemon 反复 read/write /dev/fuse,改成注册 ring entry,内核通过 CQE 通知 daemon 有请求,daemon 用 commit-and-fetch 提交 reply 并取下一个请求。这样能减少 syscall、减少唤醒开销、改善多队列并发。

但当前源码仍有:

所以它不能被简单理解为“解决 FUSE zero-copy”。

15.9 FUSE 使用 iomap 是否多余,既然已经有 opcode

不多余,但当前用途不是替代 FUSE opcode。

FUSE opcode 描述的是内核和 daemon 的协议操作,例如 READ/WRITE/LOOKUP。iomap 是内核内部 page-cache/folio/writeback helper 框架。当前 FUSE 用 iomap 简化:

如果未来出现真正的 FUSE_IOMAP_BEGIN/END,那是另一层优化:让 daemon 把文件 offset 映射到底层 extent,然后内核可能绕过一部分 daemon 数据路径。这个方向只对本地 extent/backing-file 场景特别有意义。

15.10 用户态 ext4 和内核 ext4 的主要区别

用户态实现 ext4 是可行的,e2fsprogs 仓库里就有 fuse2fs 相关代码和近期提交。区别主要不是“能不能解析 ext4 格式”,而是运行环境和职责不同:

所以用户态 ext4 更适合调试、恢复、镜像访问、跨平台工具或受限场景;生产主文件系统通常仍应使用内核 ext4。

15.11 ZFS 是否可以放到用户态

可以,但取舍很大。历史上有 zfs-fuse 这类用户态实现;现代 OpenZFS 在 Linux 上主要是内核模块形态。

ZFS 这类复杂文件系统包含 ARC、事务组、校验、压缩、快照、RAID-Z、ZIL/SLOG 等机制。放到用户态可以降低内核集成难度,但会遇到:

因此“可以做”和“适合作为高性能生产主路径”是两件事。

15.12 sshfs 为什么需要过用户态,I/O 流程是什么

sshfs 的目标是把远端 SFTP 暴露成本地 POSIX-ish 文件系统。Linux 内核没有通用 SFTP 文件系统 client,因此 sshfs 用 FUSE 做桥:

本地应用 read/write/open
  -> 本地 VFS
  -> 本地 FUSE 内核
  -> 本地 sshfs daemon
  -> SSH/SFTP
  -> 远端 sshd/sftp-server
  -> 远端文件系统

它必须过本地用户态,因为 SSH/SFTP 协议栈、认证、加密、known_hosts、agent、配置等都天然在用户态生态里。

15.13 sshfs 和 NFS 的区别

NFS 是专门的网络文件系统协议:

sshfs 是 SFTP 到 FUSE 的适配:

“NFS client 能否发送恶意信息到 server”这个问题本质是 server 必须验证 client 请求和权限。任何网络文件系统 server 都不能信任 client。NFS server 按导出配置、认证机制、UID/GID 映射、文件权限等限制访问。client 能发畸形请求,但 server 应该防御;如果 server 有漏洞,那是服务端安全问题。

15.14 为什么很多人放弃 sshfs

主要原因不是 FUSE 单点问题,而是 sshfs/SFTP 作为文件系统的综合取舍:

sshfs 仍然有价值:临时访问远端目录、低频操作、只想依赖 SSH、不想配置 NFS/SMB 的场景。只是它不适合作为高性能、强一致、长期在线的生产文件系统。

15.15 WebDAV 为什么可能比 FUSE 更好

“WebDAV 比 FUSE 更好”不是普遍真理。它通常指某些使用场景下 WebDAV 更合适:

但 WebDAV 的 POSIX 语义更弱,性能和锁语义也未必好。如果应用必须通过普通 POSIX path 访问,FUSE 仍然更通用。

15.16 lxcfs 为什么适合 FUSE

lxcfs 需要为容器虚拟化 /proc/sys、cgroup 视图等内容。这类文件不是传统磁盘文件,内容动态生成,依赖容器 namespace/cgroup 状态。用 FUSE 可以让容器里的应用看到普通文件路径,而具体内容由用户态 daemon 按容器上下文生成。

15.17 squashfuse、ratarmount、archivemount 这类项目为什么适合 FUSE

归档和只读镜像类项目非常适合 FUSE:

squashfuse 用 FUSE 暴露 squashfs,ratarmount 暴露 tar 压缩包,archivemount 暴露归档文件。它们共同点是后端不是普通可写块设备文件系统,而是一个已有容器格式。

15.18 mergerfs / unionfs-fuse / fuse-overlayfs 的定位

这类项目用 FUSE 做路径合并或 overlay:

这类场景的元数据策略复杂但数据通常仍落在本地文件。未来 passthrough、iomap extent 类优化对它们更可能有价值,因为大文件数据可以直接走 backing file。

15.19 s3fs / gcsfuse / blobfuse / rclone mount 的限制

对象存储不是 POSIX 文件系统。FUSE 只是把它包装成 POSIX-ish 接口,无法消除底层差异:

因此这些项目适合兼容性接入,不应假定等价于本地 ext4/XFS。

15.20 Alluxio、Nydus 这类项目为什么可以商业化

它们不是简单“写一个 FUSE demo”,而是在 FUSE 接口背后提供更复杂的系统能力:

FUSE 在这里是兼容接口和接入层,商业价值在后端缓存、分发、调度和一致性系统。

15.21 libnbd / nbdfuse 的意义

NBD 是块设备协议。nbdfuse 把 NBD export 暴露成 FUSE 文件,通常是把块设备内容作为一个文件访问,或方便工具链集成。它体现的是 FUSE 的另一个用途:不一定要实现完整目录树,也可以把某种设备、对象或 API 映射成一个文件。

15.22 “如果没有创新,就做 io_uring 或 folio 适配”是否成立

这是内核演进里的常见性能和维护方向,但不能简单贬低。

对 FUSE 这类高频路径来说:

这些优化未必改变抽象模型,但能显著改善生产工作负载。

16. 读源码后的几个关键结论

  1. FUSE 的核心不是“用户态文件系统”这五个字,而是 VFS 对象缓存和用户态 daemon 权威状态之间的一套协议。

  2. 普通 FUSE 不走 bio。它走 fuse_reqfuse_args/dev/fuse 或 io_uring。

  3. FOPEN_DIRECT_IO 绕过 page cache,但不是 daemon 直接写应用进程 page,也不保证 zero-copy。

  4. 当前 FUSE 已经使用 iomap,但主要是 page-cache/folio/writeback helper,不是块映射。

  5. dcache/icache 在内核;daemon 通过 LOOKUP reply、timeout、FORGET、notify invalidation 参与维护一致性。

  6. writeback-cache 性能好,但只适合 daemon 能保证所有修改经过本客户端或能正确失效缓存的场景。

  7. passthrough 是本地 backing-file 场景的重要优化,但权限、stacking、缓存互斥都很严格。

  8. io_uring 优化的是 daemon 通信调度模型,当前没有完全替代 /dev/fuse,也没有自动消除 payload 拷贝。

  9. virtiofs 是 FUSE 协议在虚拟化共享文件系统里的扩展,所以放在 fs/fuse 下合理。

  10. FUSE 的生态价值来自“把非文件系统东西包装成文件系统”和“把复杂策略放在用户态”,但它不是所有存储问题的最佳答案。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。