Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

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 不等于:

2.2 generation

daemon 可能在对象消失后复用 nodeid。generation 与 nodeid 组成生命周期内唯一的对象身份,避免仍缓存的旧 inode 被误认为新对象。

2.3 fh

FUSE_OPENFUSE_CREATE 可返回 64 位 fh。它表示一次 daemon 打开状态,后续 READ、WRITE、FSYNC、FLUSH、RELEASE 等操作可以携带它。

同一个 nodeid 可以同时有多个 fh,例如:

2.4 unique

unique 标识一次请求传输。需要应答的请求从发送到结束都用它配对。unique 不表达对象身份,也不应被 daemon 重写。

nodeid       -> 哪个文件系统对象
generation   -> 该 nodeid 的哪一代
fh           -> 哪次打开
unique       -> 哪次请求

3. lookup reference 和 FORGET

成功返回 entry 的操作通常增加 daemon 侧 lookup count。内核不再需要对象时发送:

FORGET 没有应答,因为等待引用回收的回复既增加开销,也可能在内存回收路径制造死锁。daemon 收到 FORGET 后可以减少引用并在计数归零、没有 open handle 时释放对象状态。

重要规则:

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;
};

约束包括:

内核中的 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...

能力大致分为:

协商原则:

  1. 内核先声明自己支持的集合;
  2. daemon 只能选择其子集;
  3. 双方按最终 minor version 调整结构长度;
  4. 运行时限制进入 fuse_conn/fuse_chan
  5. 未协商的 feature 不能因为头文件中存在宏就直接使用。

7. 属性和 entry 应答

fuse_entry_out 同时返回:

这解释了为什么 LOOKUP 不只是“返回 inode number”:它一次建立协议身份、VFS inode 初值、dentry lease 和 attribute lease。

8. 必须保持的协议不变量

小结

VFS 对象、FUSE 内部对象和 daemon 对象通过四种身份连接。后续讨论缓存与一致性时,nodeid 决定“缓存属于谁”,timeout 决定“答案能信多久”,notification 决定“如何提前撤销信任”。

上一篇 下一篇