Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

4. 并发、背压、安全与失败模型

[中文] English 教程目录
上一篇 进入第二章

1. FUSE 天生是并发协议

多个进程可以同时操作同一挂载点;readahead、writeback、异步 I/O、FORGET 和 notification 又会产生系统调用之外的并发。unique 允许 daemon 区分和乱序完成请求。

daemon 不应假设:

2. 同步请求与后台请求

2.1 同步请求

调用线程构造请求并等待应答。等待过程中可能遇到:

一旦请求已经交给 daemon,内核不能简单释放其参数,因为 daemon 仍可能写回。请求引用计数、状态位和 waitqueue 共同保证生命周期。

2.2 后台请求

后台请求用于:

后台请求通过 completion callback 归还 folio、file reference 和 I/O context。任何错误路径都必须最终调用 completion,否则会泄漏引用或永久保持 writeback 状态。

3. 背压

如果 daemon 消费速度低于生产速度,系统必须限制积压:

num_background < max_background
        -> 可激活更多后台请求

num_background == max_background
        -> channel blocked,后续请求留在 bg_queue

completion
        -> 降低计数,flush 更多请求

congestion_threshold 让 VM/writeback/readahead 感知 FUSE 拥塞。它与 max_background 的区别是:前者用于提前降速,后者是硬的后台在途上限。

过大配置会增加内存、尾延迟和 abort 成本;过小会让 daemon/后端吃不满。应结合请求大小、daemon 线程数和后端并发能力调节。

4. 主要锁域

FUSE 不用一把全局锁保护全部状态。常见锁域包括:

锁/同步原语 保护对象
inode i_rwsem 文件大小、写入、truncate、目录操作的 VFS 串行化
fiq->lock pending、interrupt、forget 和 input queue 状态
fpq->lock io 与 processing queue、传输复制状态
channel background lock background 计数、bg_queue、blocked
fuse_inode 内部锁 attr version、write files、状态位
mapping invalidate lock page cache/DAX mapping 与回收、truncate
request waitqueue/refcount 单个请求完成和释放

锁顺序比单把锁的功能更重要。daemon 自身若在处理请求时访问同一 FUSE mount,可能形成递归依赖;这也是部分 RELEASE 选择后台执行、writeback 标记潜在 reclaim deadlock 的原因。

5. INTERRUPT 不是强制取消

线程收到信号时有三种典型情况:

  1. 请求仍在 pending:内核可以从队列移除并结束;
  2. daemon 已读取:内核发送 FUSE_INTERRUPT(original_unique)
  3. daemon 已完成但应答正在竞争:原应答或 interrupt 结果之一先改变状态。

daemon 对 INTERRUPT 可以:

因此 application 收到信号不意味着后端操作一定未发生。涉及创建、写入等副作用时,daemon 和应用都要考虑“不确定完成”。

6. FORGET、删除与打开文件

Unix 允许文件 unlink 后继续通过已打开 fd 访问。对应到 FUSE:

daemon 必须把 namespace lifetime 和 open lifetime 分开,否则容易过早释放后端对象。

7. 安全模型

7.1 谁能访问挂载点

默认情况下,FUSE 内核 client 对非挂载所有者施加限制;allow_other 可放宽,但通常还受 /etc/fuse.conf 控制。它是 guest VFS 入口策略,不替代 daemon 后端权限。

7.2 请求凭据

请求头携带 uid/gid/pid。当前内核还支持:

daemon 应验证字段和协商能力,并明确后端操作使用 daemon 凭据、请求者凭据还是映射凭据。

7.3 不可信 daemon

daemon 能返回恶意长度、非法属性、错误 nodeid 或不回复。内核需要验证结构长度、属性范围、目录项对齐和 error 值。FUSE mount 不会让 daemon 获得任意内核内存访问权,但 daemon 可以让依赖该挂载的进程阻塞或收到错误。

8. 连接失败与恢复

daemon 退出

关闭最后一个 /dev/fuse fd 或 transport 断开后,连接变为 disconnected。pending/processing 请求以 -ENODEV-ECONNABORTED 等错误结束,等待者被唤醒。

管理员 abort

fusectl 的 abort 用于强制结束卡死连接。它是破坏性恢复:在途写可能处于不确定状态,应用需要重新验证。

请求超时

通用 FUSE 长期依赖信号和 abort;新协议能力可以提供 request timeout,但 timeout 的定义必须明确:

对有副作用的操作,盲目重试可能重复执行。

9. 常见死锁模式

  1. daemon 的后端路径递归访问自己的 FUSE mount;
  2. 内存回收等待 FUSE writeback,而 daemon 分配内存又依赖同一回收;
  3. 持 inode lock 等 daemon,daemon 反向操作需要同一 inode lock;
  4. DAX range 回收等待长期 GUP pin;
  5. 同步 RELEASE 由 daemon 线程间接触发,而完成需要该 daemon 线程。

设计时应减少 daemon 对自身挂载的依赖,预留内存,使用规定的锁顺序和异步完成路径,并为 abort 提供可观测性。

10. 可靠性检查表

第一章总结

第一章建立了 FUSE 的设计模型:分层边界、四种身份、三类缓存、多种 I/O 模式、并发队列和失败语义。第二章将这些概念逐一定位到 Linux 7.2-rc6 的具体源码。

上一篇 进入第二章