Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

1. 边界、分层与端到端架构

[中文] English 教程目录

下一篇:核心对象、协议身份与能力协商

本文目标

本文先回答三个问题:

  1. FUSE 把哪些工作留在内核,哪些工作交给用户态?
  2. 一次系统调用为什么可能对应零个、一个或多个 FUSE 请求?
  3. /dev/fuse、FUSE over io_uring 和 virtio-fs 在整个架构中分别处于哪一层?

1. FUSE 不是“用户态 VFS”

应用通过标准系统调用访问 FUSE 文件系统。进入内核后,VFS 仍然负责通用文件系统语义:

FUSE daemon 实现的是具体文件系统策略和后端访问:

因此,FUSE 的准确定位是:Linux 内核中的 FUSE client 把 VFS 对具体文件系统提出的问题编码成协议请求,用户态 daemon 返回答案;内核再用答案维护 VFS 对象。

2. 分层架构

Application
  open / stat / read / write / mmap / rename
        |
        v
Linux VFS
  namei, dcache, inode cache, page cache, file_operations
        |
        v
FUSE kernel client
  inode/dir/file operations, cache policy, request construction
        |
        v
Transport
  /dev/fuse read/write | FUSE over io_uring | virtio-fs virtqueue
        |
        v
Userspace or host daemon
  libfuse/virtiofsd/custom server
        |
        v
Backend
  local files, network service, object store, generated namespace

层次之间有两个方向的数据流:

3. 系统调用与协议请求不是一一对应

open("/mnt/a/b", O_RDONLY) 为例:

VFS 查找 /mnt/a
  cache miss -> FUSE_LOOKUP("a")

VFS 查找 /mnt/a/b
  cache miss -> FUSE_LOOKUP("b")

VFS 打开 inode
  -> FUSE_OPEN

第二次打开时,若两个 dentry 和 inode 属性仍有效,路径查找可以完全命中内核缓存,只剩 FUSE_OPEN;若 daemon 声明 FUSE_NO_OPEN_SUPPORT,连 OPEN 也可能由内核省略。

相反,一次 4 MiB read(2) 可能因为 max_readmax_pages、页缓存缺页边界或 direct-I/O 拆分而生成多个 FUSE_READ。所以分析性能时应分别观察:

这四个数字通常并不相同。

4. 元数据路径和数据路径

4.1 元数据路径

元数据操作通常以小请求为主,但会放大延迟:

path walk -> LOOKUP -> ENTRY reply -> dentry/inode cache
stat      -> GETATTR -> ATTR reply  -> attribute cache
create    -> CREATE -> ENTRY + OPEN reply
unlink    -> UNLINK -> dentry/inode invalidation
readdir   -> READDIR or READDIRPLUS

READDIRPLUS 能在返回目录项时附带属性和 nodeid,减少后续 LOOKUP,但也增加单次应答大小和引用记账。

4.2 数据路径

FUSE 提供多种数据路径:

这些路径共享 inode 和文件生命周期,却有不同的一致性与锁要求。它们不是可以在任意时刻无条件互换的性能开关。

5. 传输层是可替换的

5.1 /dev/fuse

传统 daemon 打开 /dev/fuse

5.2 FUSE over io_uring

当前内核可以协商 FUSE_OVER_IO_URING,用 io_uring command 提供请求槽和完成路径。它改变传输机制和调度成本,但不改变 LOOKUP、READ 等 FUSE opcode 的文件系统语义。

5.3 virtio-fs

virtio-fs 在 guest 中复用 FUSE client 和协议对象,以 virtqueue 把请求送到 host 上的 virtiofsd。DAX 又增加共享内存 window 和 SETUPMAPPING/REMOVEMAPPING。因此分析 virtio-fs 时应分开:

6. 性能成本模型

一次未缓存请求的延迟大致由以下部分组成:

VFS/FUSE client CPU
+ 请求排队
+ 唤醒与调度 daemon
+ 参数/数据复制或映射
+ daemon 调度与协议解析
+ 后端访问
+ 应答排队与复制
+ 唤醒原线程

优化不能只盯住 daemon 的后端函数。常见手段对应不同成本:

手段 主要减少什么 代价
增大 entry/attr timeout 元数据往返 后端外部变化更晚可见
readahead READ 次数和等待 可能读入无用数据
writeback cache 同步 WRITE 等待 一致性和错误延迟更复杂
增大请求 每字节协议开销 内存占用和尾延迟可能上升
增加并发 daemon/后端利用率 排队、锁竞争和内存压力
passthrough/DAX 数据面往返和拷贝 模式切换及一致性更严格

7. 设计边界检查表

设计一个 FUSE 文件系统前,应明确:

  1. 后端是否可能被 daemon 之外的进程修改?
  2. 名称、属性和数据分别允许多长的陈旧窗口?
  3. 哪些操作必须有严格顺序,哪些可以并行?
  4. daemon 卡死或退出时,应用应等待、超时还是快速失败?
  5. 使用 cached、direct、passthrough 或 DAX 中的哪些路径?
  6. user namespace、allow_other 和后端凭据如何映射?
  7. 如何观测 unique、opcode、队列深度和后端延迟?

小结

FUSE 的核心不是字符设备,而是一条横跨 VFS、内核 client、可替换传输和 daemon 的对象与请求协议。下一篇将建立这些对象之间的精确映射。

下一篇:核心对象、协议身份与能力协商