Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

1.2 Object Model and Protocol

中文 English Contents

Chapter 1: FUSE Subsystem Design · Article 2 of 4

Correct FUSE implementations depend on understanding three different object systems at once: VFS objects, kernel FUSE objects, and daemon-owned identities. They overlap, but none is a one-to-one replacement for another.

1. Three layers of objects

1.1 VFS objects

The VFS presents familiar structures:

Their lifetimes are controlled by VFS references, not by FUSE protocol messages alone.

1.2 Kernel FUSE objects

The kernel adds protocol-specific state such as:

These objects bridge VFS lifetimes to message lifetimes.

1.3 Daemon identities

The daemon returns identifiers that have protocol meaning:

A nodeid is not a pointer, a Linux inode number, or an open handle. An fh is not a nodeid. A unique lasts for one protocol transaction only.

2. Namespace lifetime and FORGET

A successful LOOKUP gives the kernel a lookup reference on the returned node. The kernel can accumulate multiple references while dentries are cached. When it no longer needs them, it sends FORGET or BATCH_FORGET.

Important consequences:

A daemon that treats FORGET as RELEASE will eventually corrupt its lifetime model.

3. Protocol envelope

Every request begins with struct fuse_in_header, which includes message length, opcode, unique ID, node ID, caller UID/GID/PID, and related context. Most replies begin with struct fuse_out_header, which contains length, error, and the matching unique ID.

request = fuse_in_header + opcode-specific input + optional payload
reply   = fuse_out_header + opcode-specific output + optional payload

The protocol is binary. Structure layout, feature negotiation, length validation, and compatibility rules matter as much as the logical operation.

4. Kernel request representation

The kernel does not need to flatten every message immediately. It describes input and output with argument vectors, together with request flags such as whether a reply is expected or output data is variable length. Transport code then copies or maps the described buffers.

This separation allows one logical request representation to support classic device I/O, io_uring delivery, and other channels.

5. Operation classes

The opcode space covers several classes:

Not every request expects a reply. That distinction must be explicit in both kernel and daemon logic.

6. FUSE_INIT: protocol negotiation

The mount is not ready for normal operation until the kernel and daemon exchange FUSE_INIT.

Negotiated information includes:

Feature flags are a contract. A daemon must not advertise a capability and then implement only its happy path.

7. Example: LOOKUP reply

A successful LOOKUP returns a fuse_entry_out containing:

The kernel uses these values to instantiate or refresh both namespace and inode state. A negative lookup can also be cached when the daemon returns ENOENT with a configured validity interval.

8. Example: open identity versus node identity

Suppose two processes open the same FUSE inode. The VFS may use one inode object, while the daemon returns two different fh values. Per-open flags—such as direct I/O or keep-cache behavior—may differ. Later READ, WRITE, FLUSH, and RELEASE operations can carry the relevant fh even though their nodeid is identical.

This distinction is essential for backends with sessions, credentials, remote descriptors, or per-open locks.

9. Protocol invariants

A robust implementation preserves at least these invariants:

  1. Every reply is matched to exactly one live request by unique.
  2. The reply opcode payload and length agree with the original request.
  3. Node identity remains stable for the advertised lifetime.
  4. Lookup counts and open-handle counts are accounted independently.
  5. Negotiated limits are honored on both input and output.
  6. Late replies, interrupted requests, and connection teardown cannot complete the same request twice.
  7. User-controlled lengths and offsets are validated before copying or mapping.

10. Summary

VFS objects, FUSE kernel state, and daemon identifiers form three related lifetime systems. nodeid, generation, fh, and unique answer different questions. The binary protocol joins those systems, while INIT defines which optional behaviors are legal for the lifetime of the connection.

Previous: Boundary and Overall Architecture Next: Caching and Coherency