1.1 Boundary and Overall Architecture
| 中文 | English | Contents |
Chapter 1: FUSE Subsystem Design · Article 1 of 4
FUSE is not simply “a filesystem implemented in user space.” It is a split filesystem architecture: the kernel keeps the VFS-facing half, while a user-space daemon implements filesystem policy. The protocol boundary between them is the central design fact from which performance, consistency, security, and failure behavior follow.
1. Learning goals
After this article, you should be able to:
- place FUSE correctly in the Linux VFS stack;
- explain which responsibilities remain in the kernel and which move to the daemon;
- trace a pathname lookup and a file read across the boundary;
- distinguish classic
/dev/fuse, io_uring transport, and virtio-fs; - identify the unavoidable and avoidable costs of the architecture.
2. The architectural boundary
A native filesystem usually resolves a VFS operation entirely inside the kernel. FUSE inserts a request/reply protocol between the VFS operation and the filesystem implementation:
application
|
| openat/read/write/stat/mmap/...
v
Linux VFS and page cache
|
| inode_operations / file_operations / address_space_operations
v
FUSE kernel client
|
| FUSE request/reply protocol
v
transport: /dev/fuse, io_uring, or virtio-fs
|
v
user-space daemon or host-side backend
|
v
backing store, remote service, archive, object store, or synthetic data
The application still sees ordinary POSIX syscalls. It does not know whether the operation is serviced by ext4, a FUSE daemon, or a remote backend.
3. Responsibilities on each side
3.1 Kernel-side responsibilities
The kernel client must integrate with invariants that cannot be delegated:
- VFS pathname walking and object lifetime;
- dentry, inode, page-cache, and mmap integration;
- request construction, queueing, interruption, and completion;
- credential and namespace context carried in requests;
- cache validation and invalidation;
- writeback, direct I/O, passthrough, and DAX dispatch;
- mount teardown and behavior after a dead or disconnected daemon.
The kernel is therefore a stateful protocol client, not a thin syscall forwarder.
3.2 Daemon-side responsibilities
The daemon supplies filesystem policy and backend integration:
- mapping names to stable node identities;
- returning attributes and directory entries;
- implementing create, unlink, rename, permission, and xattr semantics;
- creating open handles and processing data I/O;
- maintaining backend consistency and persistence;
- issuing invalidation notifications when data changes externally;
- applying policy that is not already enforced by the kernel.
A daemon may use a local directory, a database, an RPC service, or no persistent storage at all.
4. A syscall is not always one FUSE request
The VFS works on objects and caches, while the protocol exchanges messages. Their boundaries do not match one-to-one.
One openat() may require several LOOKUP requests and an OPEN; or zero requests if dentries and attributes remain valid. One large read() may be split into several READ requests. A page fault may issue I/O without a new read() syscall. Conversely, readahead and writeback can create requests that have no direct syscall counterpart.
This is why diagnosing FUSE solely from syscall traces is incomplete. You must also observe the protocol and cache state.
5. Metadata path example
For stat("/mnt/a/b"), the conceptual path is:
- VFS begins from the mount root.
- It checks the dentry cache for
aand thenb. - For a missing or expired component, FUSE sends
FUSE_LOOKUP(parent_nodeid, name). - The daemon returns a node ID, generation, attributes, and validity intervals.
- The kernel instantiates or updates the dentry and inode.
- If cached attributes expire later,
FUSE_GETATTRmay refresh them.
A lookup reply is therefore both a namespace result and a time-bounded cache lease.
6. Data path example
For a buffered read(fd, buf, len):
- VFS enters the FUSE file operations.
- The page cache satisfies already-cached ranges.
- Missing folios cause one or more
FUSE_READrequests. - The daemon fetches data and writes a reply.
- The kernel fills the page cache and copies data to the application.
Direct I/O bypasses the page cache and divides the user range into protocol requests. Passthrough can redirect operations to a backing kernel file. DAX maps file ranges through a finite device window and services faults through filesystem DAX helpers. These modes have different coherency and fallback constraints; they are not interchangeable optimizations.
7. Transport variants
7.1 Classic /dev/fuse
A daemon reads requests from a FUSE device file and writes replies back. The kernel maintains pending and processing queues, while each request carries a unique identifier for reply matching.
7.2 FUSE over io_uring
Newer kernels can negotiate an io_uring-based transport. It changes how buffers and completion are delivered, but not the VFS-facing architecture or the meaning of protocol operations.
7.3 virtio-fs
virtio-fs reuses the FUSE protocol across a virtio transport, normally between a guest kernel and a host backend such as virtiofsd. It can also expose a DAX window for shared mappings. This removes some copies and VM exits on suitable workloads, but introduces finite-window allocation, mapping recall, and guest/host coherency questions.
8. Cost model
Potential costs include:
- context switches or cross-VM transitions;
- request allocation, queue synchronization, and wakeups;
- serialization and validation of protocol structures;
- data copies between application, kernel, daemon, and backend;
- many metadata round trips during pathname walking;
- lock hold time and queueing under daemon saturation.
The largest optimization opportunities usually come from reducing request count, increasing useful batching, selecting the right data path, and assigning cache validity intervals that match the consistency model.
9. Design questions to ask first
Before tuning or extending FUSE, answer these questions:
- Which side is authoritative for names, attributes, and file contents?
- Can the backend change without going through this mount?
- Which objects may be cached, for how long, and who invalidates them?
- What happens when the daemon stalls, crashes, or replies late?
- Can the chosen data paths coexist without incoherent aliases?
- Which resources are bounded: requests, daemon workers, DAX mappings, or backing handles?
- Does a proposed fallback preserve the original I/O position and partial-completion semantics?
These questions are more useful than treating every latency spike as merely “user-space overhead.”
10. Summary
FUSE splits one filesystem into a kernel protocol client and a user-space policy engine. The kernel preserves VFS semantics and caches; the daemon defines the filesystem and accesses its backend. The protocol is the architectural seam, and all later topics—identities, leases, queueing, timeout, DAX, and teardown—must be understood relative to that seam.