2.1 Source Map, Mount, and Initialization
| 中文 | English | Contents |
Chapter 2: FUSE Implementation Analysis · Article 1 of 6
This article uses the current kernel tree’s fs/fuse/ and include/uapi/linux/fuse.h as its baseline. It follows registration through mount construction and FUSE_INIT, and explains why “the VFS mount exists” and “the protocol connection is negotiated” are separate milestones.
1. Source map
| File | Main responsibility |
|---|---|
fs/fuse/inode.c |
registration, mount, connection initialization, inode construction and attributes |
fs/fuse/dir.c |
lookup, namespace mutation, rename, attributes, and permissions |
fs/fuse/file.c |
open/release, cached I/O, direct I/O, writeback, locks, and fsync |
fs/fuse/dev.c |
/dev/fuse queues, daemon reads/writes, interruption, and completion |
fs/fuse/req.c |
channel-independent synchronous, background, and notification-reply sends |
fs/fuse/dax.c |
virtio-fs DAX window, iomap, faults, and mapping reclaim |
fs/fuse/control.c |
fusectl waiting/abort/background controls |
fs/fuse/readdir.c |
readdir and readdirplus |
fs/fuse/ioctl.c |
forwarded ioctl and retry protocol |
fs/fuse/xattr.c |
extended attributes |
fs/fuse/passthrough.c |
backing-file passthrough |
fs/fuse/virtio_fs.c |
virtio-fs transport and DAX-device attachment |
fs/fuse/fuse_i.h |
internal core objects and cross-file APIs |
fs/fuse/fuse_dev_i.h |
channels, requests, input queues, and processing queues |
include/uapi/linux/fuse.h |
userspace ABI: opcodes, flags, and binary structures |
The productive reading direction is VFS operation table → operation implementation → fuse_args → request/channel. Starting in the byte-copy loops in dev.c obscures the VFS contract that created the request.
2. Filesystem registration and mount chain
The FUSE module registers a file_system_type whose .init_fs_context points to fuse_init_fs_context. Depending on configuration, related filesystem types such as fuseblk are also registered.
module initialization
-> register_filesystem(&fuse_fs_type)
-> mount request
-> fuse_init_fs_context()
-> parse mount parameters
-> fuse_get_tree()
-> get_tree_nodev()/get_tree_bdev()
-> fuse_fill_super()
-> fuse_fill_super_common()
-> fuse_send_init()
The modern mount API stores parsed values in a private fs_context until tree construction consumes them. Parameter lifetime is therefore distinct from superblock and connection lifetime.
3. Objects created during mount
Keep these objects separate:
struct fuse_fs_context: temporary mount-construction parameters;struct super_block: VFS mount instance;struct fuse_mount: binds this mount to a FUSE connection;struct fuse_conn: connection-wide negotiated state and policy;- channel/transport: delivery endpoint for requests.
A classic mount normally begins with an already-open FUSE device descriptor. virtio-fs builds the transport differently while reusing much of the common superblock and connection logic. /dev/fuse is therefore a transport mechanism, not a universal architectural requirement.
4. fuse_fill_super_common()
Common superblock construction broadly:
- installs superblock operations, limits, and time granularity;
- attaches the
fuse_connandfuse_mount; - creates the root inode and dentry;
- configures BDI, cache, and writeback behavior;
- installs permission/export policy from mount mode;
- establishes references needed for later teardown.
At this point the VFS skeleton exists, but optional protocol capabilities are not yet authoritative.
5. FUSE_INIT capability negotiation
fuse_send_init() builds the initialization request. A completion handler validates the daemon’s version and fields, then publishes negotiated state into fuse_conn.
Negotiated values include, depending on protocol version:
- major/minor version;
- maximum write, readahead, page count, and alignment;
- maximum background count and congestion threshold;
- asynchronous read and writeback cache;
- locks, ACL, security context, and directory capabilities;
- DAX, passthrough, and io_uring transport support;
- timestamp granularity and extended-init format.
Negotiation is not a union of flags. A feature is usable only when both sides support it and all associated fields pass validation.
6. Why INIT is special
Normal requests depend on negotiated size and feature limits, while INIT itself occurs before those limits exist. The code must accept older response layouts, establish defaults, and downgrade cleanly.
Typical protocol mistakes include:
- advertising a new minor version with a response too short for that version;
- returning
max_writeinconsistent with the transport’s page capacity; - advertising a feature but failing its non-happy paths;
- retaining locally supported flags that the daemon rejected;
- allowing ordinary requests to proceed after INIT failure.
7. Connection state sequence
allocate connection
-> establish transport
-> create VFS root objects
-> send FUSE_INIT
-> success: publish initialized state
-> ordinary requests
-> disconnect/abort/unmount
-> release after all references drain
Every waiter for initialization must wake on success, failure, and daemon disappearance. Feature fields used by later request paths should remain stable after publication.
8. Root inode
The protocol reserves a root node ID. The root inode is not initially discovered through ordinary pathname lookup, but it must still exist as a VFS inode/dentry. A later lookup under the mount root sends that root node ID as the parent.
Root mode, ownership, and initial attributes need safe values during the interval before the first attribute refresh.
9. Debugging mount failures
Use the symptom to choose an entry point:
- rejected option:
fuse_init_fs_context()and its parameter table; - missing root or superblock failure:
fuse_get_tree()andfuse_fill_super*(); - daemon reads INIT but mount hangs:
fuse_send_init()and request completion; - feature silently absent: INIT input flags, response parsing, and
fuse_connfield; - unexpected I/O limit:
max_write,max_pages, and transport buffers; - virtio-fs DAX absent: check virtio feature, DAX-device discovery, and negotiated FUSE DAX flag together.
10. Summary
FUSE mount code first builds VFS and connection objects, then FUSE_INIT establishes the protocol contract. super_block, fuse_mount, fuse_conn, and transport have different lifetimes. Every later source path must be read in light of capabilities actually negotiated during INIT.