2. 核心对象、协议身份与能力协商
| [中文] | English | 教程目录 |
| 上一篇 | 下一篇 |
1. 三组对象必须分开理解
1.1 VFS 对象
| VFS 对象 | 含义 |
|---|---|
super_block |
一次文件系统实例的全局状态 |
vfsmount / mount |
文件系统在 namespace 中的挂载关系 |
dentry |
父目录与名字到 inode 的缓存映射,也可为 negative |
inode |
文件系统对象的内核表示 |
file |
一次打开实例,携带 offset、flags 和 private data |
address_space |
页缓存/DAX entries、writeback 与 mmap 的核心对象 |
1.2 FUSE 内部对象
| FUSE 对象 | 与 VFS/协议的关系 |
|---|---|
fuse_conn |
逻辑连接和协商能力,可服务多个 mount |
fuse_chan |
请求通道、输入队列和后台限流 |
fuse_mount |
把 superblock 连接到 fuse_conn |
fuse_inode |
嵌入 VFS inode,增加 nodeid、generation、属性版本等 |
fuse_file |
挂在 file->private_data,保存 fh 和 open flags |
fuse_args |
某个 opcode 的逻辑输入、输出和完成回调 |
fuse_req |
一次实际传输的头部、unique、状态、引用和等待者 |
1.3 daemon 对象
daemon 可自由设计内部对象,但必须遵守协议可见的身份:nodeid、generation、fh 和 lookup reference。把后端 inode number 直接当 nodeid 是一种实现选择,不是协议要求。
2. 四种身份
2.1 nodeid
nodeid 标识 daemon namespace 中的对象。它出现在请求头,告诉 daemon 操作哪个目录或文件。根节点固定为 FUSE_ROOT_ID。
nodeid 不等于:
- guest VFS 的
inode->i_ino; - 后端磁盘 inode number;
- open file handle;
- 请求 unique。
2.2 generation
daemon 可能在对象消失后复用 nodeid。generation 与 nodeid 组成生命周期内唯一的对象身份,避免仍缓存的旧 inode 被误认为新对象。
2.3 fh
FUSE_OPEN 或 FUSE_CREATE 可返回 64 位 fh。它表示一次 daemon 打开状态,后续 READ、WRITE、FSYNC、FLUSH、RELEASE 等操作可以携带它。
同一个 nodeid 可以同时有多个 fh,例如:
- 不同进程分别打开;
- 一个只读、一个可写;
- daemon 为每次 open 保存独立后端 fd;
- 某次 open 选择 direct I/O,另一次选择 caching mode。
2.4 unique
unique 标识一次请求传输。需要应答的请求从发送到结束都用它配对。unique 不表达对象身份,也不应被 daemon 重写。
nodeid -> 哪个文件系统对象
generation -> 该 nodeid 的哪一代
fh -> 哪次打开
unique -> 哪次请求
3. lookup reference 和 FORGET
成功返回 entry 的操作通常增加 daemon 侧 lookup count。内核不再需要对象时发送:
FUSE_FORGET(nodeid, nlookup);- 或
FUSE_BATCH_FORGET批量归还。
FORGET 没有应答,因为等待引用回收的回复既增加开销,也可能在内存回收路径制造死锁。daemon 收到 FORGET 后可以减少引用并在计数归零、没有 open handle 时释放对象状态。
重要规则:
- open reference 与 lookup reference 独立;
- FORGET 不代表后端对象必须删除;
- RELEASE 不代表 nodeid lookup count 归零;
- daemon 必须能处理 FORGET 与其他在途请求并发。
4. 协议头和参数布局
每个请求以 fuse_in_header 开始:
struct fuse_in_header {
uint32_t len;
uint32_t opcode;
uint64_t unique;
uint64_t nodeid;
uint32_t uid;
uint32_t gid;
uint32_t pid;
uint16_t total_extlen;
uint16_t padding;
};
每个普通应答以 fuse_out_header 开始:
struct fuse_out_header {
uint32_t len;
int32_t error;
uint64_t unique;
};
约束包括:
len覆盖头部和所有参数;error为 0 或合法的负 errno;- error 非 0 时通常只返回输出头;
- unique 必须与请求一致;
- unique 为 0 表示 notification,不是普通应答;
- 参数结构按协商的 minor version 解释,必须兼容短结构。
内核中的 fuse_args 使用小型参数数组描述请求:
opcode + nodeid + credentials
in_args[0..3]
out_args[0..1]
flags: noreply, in_pages, out_pages, out_argvar, force, ...
end callback for asynchronous completion
大块数据通常由 folio/page-backed 参数描述,避免把所有数据复制到一个连续临时 buffer。
5. opcode 的语义分组
| 类别 | 典型 opcode |
|---|---|
| 生命周期 | INIT, DESTROY, FORGET, BATCH_FORGET, INTERRUPT |
| namespace | LOOKUP, CREATE, MKNOD, MKDIR, UNLINK, RMDIR, RENAME2, LINK |
| 属性 | GETATTR, SETATTR, STATX, GETXATTR, SETXATTR, ACCESS |
| 文件 | OPEN, READ, WRITE, FLUSH, FSYNC, RELEASE, FALLOCATE, LSEEK |
| 目录 | OPENDIR, READDIR, READDIRPLUS, FSYNCDIR, RELEASEDIR |
| 锁与控制 | GETLK, SETLK, SETLKW, IOCTL, POLL |
| DAX | SETUPMAPPING, REMOVEMAPPING |
-ENOSYS 往往不只是一次失败。对部分 opcode,内核会记住 daemon 不支持该操作,之后使用 fallback 或不再发送。
6. FUSE_INIT 能力协商
INIT 的目的不是单纯比较版本号,而是建立连接契约:
kernel -> major/minor, max_readahead, supported flags
daemon -> accepted flags, max_write, max_background,
congestion_threshold, max_pages, time granularity, map alignment...
能力大致分为:
- 请求格式能力,如
FUSE_INIT_EXT、security context、supplementary groups; - I/O 能力,如
ASYNC_READ、ASYNC_DIO、BIG_WRITES、MAX_PAGES; - 缓存能力,如
WRITEBACK_CACHE、AUTO_INVAL_DATA、EXPLICIT_INVAL_DATA; - namespace 能力,如
PARALLEL_DIROPS、READDIRPLUS、submount; - 高级数据路径,如 passthrough、DAX map alignment、io_uring transport。
协商原则:
- 内核先声明自己支持的集合;
- daemon 只能选择其子集;
- 双方按最终 minor version 调整结构长度;
- 运行时限制进入
fuse_conn/fuse_chan; - 未协商的 feature 不能因为头文件中存在宏就直接使用。
7. 属性和 entry 应答
fuse_entry_out 同时返回:
- nodeid 和 generation;
entry_valid:名字到对象映射的有效期;attr_valid:属性有效期;fuse_attr:类型、mode、uid/gid、size、timestamps 等。
这解释了为什么 LOOKUP 不只是“返回 inode number”:它一次建立协议身份、VFS inode 初值、dentry lease 和 attribute lease。
8. 必须保持的协议不变量
- daemon 不得在仍有引用时让同一
nodeid:generation代表不同对象; - 普通应答必须只完成一次且 unique 正确;
- READ/WRITE 返回长度不能超过请求长度;
- 目录项必须满足对齐和名字长度要求;
- FORGET 不应回复;
- notification 的 unique 为 0,code 放在 output header 的 error 字段;
- daemon 必须按 INIT 协商结果处理结构长度和 feature。
小结
VFS 对象、FUSE 内部对象和 daemon 对象通过四种身份连接。后续讨论缓存与一致性时,nodeid 决定“缓存属于谁”,timeout 决定“答案能信多久”,notification 决定“如何提前撤销信任”。
| 上一篇 | 下一篇 |