Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

2.1 源码地图、挂载与初始化

中文 English 目录

第二章:FUSE 代码实现分析 · 第 1/6 篇

本文以当前内核树中的 fs/fuse/include/uapi/linux/fuse.h 为基线,从模块注册一路跟到 FUSE_INIT 完成。读完后,应能找到任意 FUSE 功能的源码入口,并理解“挂载完成”和“连接完成协商”为什么是两个阶段。

1. 源码目录地图

文件 核心职责
fs/fuse/inode.c 文件系统注册、挂载、连接初始化、inode 创建与属性更新
fs/fuse/dir.c lookup、目录项、创建删除、rename、属性和权限操作
fs/fuse/file.c open/release、缓存 I/O、direct I/O、writeback、锁和 fsync
fs/fuse/dev.c /dev/fuse 请求队列、daemon read/write、完成与中断
fs/fuse/req.c 与具体 channel 解耦的同步、后台和 notify-reply 发送接口
fs/fuse/dax.c virtio-fs DAX window、iomap、fault、映射回收
fs/fuse/control.c fusectl 中的 waiting、abort 和后台队列参数
fs/fuse/ioctl.c ioctl 转发与重试协议
fs/fuse/xattr.c 扩展属性操作
fs/fuse/readdir.c readdir 和 readdirplus
fs/fuse/passthrough.c backing file passthrough
fs/fuse/virtio_fs.c virtio-fs transport 与 DAX 设备连接
fs/fuse/fuse_i.h 子系统内部核心结构和跨文件接口
fs/fuse/fuse_dev_i.h channel、request、输入队列和处理队列
include/uapi/linux/fuse.h 用户态可见协议、opcode、flags 和消息结构

阅读时先从 VFS 操作表找到入口,再沿 fuse_args 追到 req.c 和 channel;不要从 dev.c 的复制循环反向猜测上层语义。

2. 文件系统类型注册

inode.c 中的模块初始化注册 FUSE 文件系统类型。普通 FUSE 的 file_system_type 提供 .init_fs_context = fuse_init_fs_context;不同配置下还可能注册 fuseblk 等类型。

模块初始化
  -> register_filesystem(&fuse_fs_type)
  -> 用户执行 mount
  -> fuse_init_fs_context()
  -> 参数解析
  -> fuse_get_tree()
  -> get_tree_nodev()/get_tree_bdev()
  -> fuse_fill_super()
  -> fuse_fill_super_common()
  -> fuse_send_init()

新的 mount API 使用 fs_context:解析结果先存入 FUSE 私有上下文,真正构造 super_block 时再消费。这样参数解析、树创建和 superblock 初始化有清楚的生命周期边界。

3. 挂载参数中的关键对象

经典 /dev/fuse 挂载通常需要一个已经打开的 FUSE 设备文件描述符。挂载逻辑会把这个端点与新连接关联,同时处理 root mode、user/group、默认权限、最大读写尺寸和安全选项。

需要区分:

virtio-fs 会复用大量公共初始化,但 transport 和连接建立方式不同,因此不要把 /dev/fuse 文件描述符当成 FUSE 架构的必需概念。

4. fuse_fill_super_common() 做什么

公共 superblock 初始化大致完成以下工作:

  1. 填写 super_block 的操作表、时间粒度和最大文件大小等字段;
  2. 建立或连接 fuse_connfuse_mount
  3. 初始化 root inode 和 root dentry;
  4. 设置 BDI、缓存和回写相关能力;
  5. 根据挂载模式设置权限和导出等策略;
  6. 安排连接销毁时需要的引用关系。

此时 VFS 对象框架已经存在,但 daemon 支持哪些可选协议能力仍未确定。

5. FUSE_INIT 是连接能力协商

fuse_send_init() 构造 FUSE_INIT 请求。请求与响应由专门的初始化参数对象承载,响应完成函数验证版本和限制,然后把协商结果写入 fuse_conn

协商内容包括但不限于:

初始化不是“把所有本端支持位取并集”,而是取双方可用能力并验证相关字段。daemon 返回的长度还必须与其宣称的协议版本相容。

6. 为什么初始化请求是特殊的

普通请求依赖已经协商好的限制,而 FUSE_INIT 本身发生在限制生效之前。代码因此需要兼容旧版响应、默认值和降级路径。常见错误是:

7. 初始化完成前后的状态

可以把连接生命周期简化为:

分配连接
  -> 建立 transport
  -> 创建 VFS 根对象
  -> 发送 FUSE_INIT
  -> 协商成功:connection initialized
  -> 正常请求
  -> disconnect/abort/unmount
  -> 引用清零后释放

等待初始化的路径必须在成功、失败和 daemon 消失时都能被唤醒。后续请求读取的 feature 字段,应在初始化发布之后保持稳定,避免操作期间改变协议契约。

8. Root inode 的特殊性

FUSE 根节点使用协议规定的 root node ID。它不是通过普通 pathname LOOKUP 首次发现的,但仍要作为 VFS inode/dentry 存在。之后对根目录下名称的查找,才以 root node ID 作为 parent 发送 LOOKUP

root 的属性和权限策略需要有合理初值,否则在 INIT 和首次 GETATTR 之间就可能产生异常访问结果。

9. 从挂载问题定位源码

建议按症状选择入口:

10. 本文小结

FUSE 挂载先通过 VFS mount API 构造 superblock、root 和连接,再用 FUSE_INIT 确立协议版本与能力。fuse_connfuse_mountsuper_block 和 transport 各自拥有不同生命周期。后续所有代码分析都应先确认所讨论的能力是否已经在 INIT 中协商。

下一篇:路径名与元数据操作