Linux-Kernel-Notes

Thinking in linux kernel

View on GitHub

2.3 Open, Read, Write, and Writeback

中文 English Contents

Chapter 2: FUSE Implementation Analysis · Article 3 of 6

This article follows fs/fuse/file.c through open, data-path selection, cached I/O, direct I/O, writeback, fsync, and release. The key is to understand where path selection occurs and how partial completion is reported upward.

1. fuse_file_operations

Important regular-file entries include:

.open       = fuse_open
.read_iter  = fuse_file_read_iter
.write_iter = fuse_file_write_iter
.mmap       = fuse_file_mmap
.fsync      = fuse_fsync
.flush      = fuse_flush
.release    = fuse_release

fuse_file_aops supplies page-cache read, readahead, writepages, and dirty/writeback behavior. A syscall and a background writeback worker can therefore generate protocol I/O from different entry points.

2. OPEN and per-open state

fuse_open() builds FUSE_OPEN. The daemon returns a file handle and flags in fuse_open_out; the kernel creates a struct fuse_file and attaches it to the VFS file.

Per-open state includes concepts such as:

One inode may have many fuse_file objects, so per-open decisions cannot be modeled as inode-global state.

3. Data-path dispatch

The OPEN response can request direct I/O or cache behavior. The kernel combines those flags with inode DAX state, connection capabilities, and any established backing file.

Conceptually:

read_iter/write_iter
  -> DAX inode?             -> fuse_dax_*_iter
  -> FOPEN_DIRECT_IO?       -> FUSE direct-I/O path
  -> passthrough available? -> backing-file I/O
  -> otherwise              -> page-cache path

The source order in fuse_file_read_iter() and fuse_file_write_iter() is authoritative. In the current tree, DAX is tested first, and FOPEN_DIRECT_IO overrides passthrough.

4. Cached read

fuse_cache_read_iter() enters the generic filemap path. Cached folios satisfy the read directly; a miss invokes address-space operations and eventually fuse_send_read().

read(2)
  -> fuse_file_read_iter
  -> fuse_cache_read_iter
  -> filemap_read
  -> hit: copy cached data
  -> miss: read_folio/readahead
       -> FUSE_READ
       -> fill folios

Readahead may ask the daemon for more data than the current application request.

5. Cached write and writeback

fuse_cache_write_iter() copies data into the page cache while coordinating file size, timestamps, write serialization, and direct-I/O exclusion. With writeback cache, syscall success does not mean the daemon has received a WRITE.

Later writepages can merge small writes, split large ones, run from reclaim, and report errors only at fsync or another synchronization point. The daemon must honor offsets rather than assuming arrival order matches application syscall order.

6. Direct-I/O loop

fuse_direct_io() divides the iterator according to read/write limits and page capacity, prepares user pages and arguments, then sends each piece.

A simplified shape is:

while (iov_iter_count(iter)) {
        nbytes = choose_request_size(iter, limits);
        nres = send_one_read_or_write(pos, nbytes);
        if (nres < 0)
                break;
        total += nres;
        pos += nres;
        if (nres != nbytes)
                break;
}
return total ? total : error;

The real path also handles async completion, page release, dirty marking, append, locking, and parallel direct writes.

7. Partial completion hides a later error

Linux iterator I/O usually returns a positive byte count if earlier subrequests succeeded and a later one failed. The errno is returned directly only when zero bytes completed.

For an 8 MiB read split into 2 MiB pieces:

piece 1: 2 MiB success
piece 2: 2 MiB success
piece 3: -ETIMEDOUT
syscall result: 4 MiB, not -ETIMEDOUT

This is a short I/O result, not accidental error loss. The caller consumes the prefix and issues another operation for the remainder. It also means that an implementation cannot generally wait to see a timeout and then replay the original full request on another data path.

8. Short reads versus short writes

A short read can indicate EOF or an early daemon result. A positive short write proves that the prefix may already have side effects; retry must begin at the remaining suffix.

Fallback logic needs to preserve:

9. FLUSH, FSYNC, and RELEASE

These are not synonyms:

fuse_release_common() coordinates outstanding writes, async requests, and handle references. VFS release entry does not authorize freeing state still reachable by a completion callback.

10. Writeback error propagation

A background write can fail after the originating write() returned. The kernel records the mapping error and exposes it through a later fsync or related synchronization point. Daemons should return stable diagnostic errnos, while applications must not assume close is the only possible error boundary.

11. Reading and debugging an I/O

Record together:

  1. VFS entry and kiocb flags;
  2. DAX/direct/passthrough/cache state;
  3. initial and final iterator count;
  4. offset, size, unique, and result of every FUSE piece;
  5. final syscall result;
  6. remaining background or asynchronous completions.

One FUSE READ or WRITE is not necessarily one syscall.

12. Summary

file.c dispatches between DAX, FUSE direct I/O, passthrough, and page cache. Readahead and writeback drive cached requests, while direct I/O performs its own splitting. A positive completed prefix normally takes precedence over a later error—the critical return-value rule for timeout fallback designs.

Previous: Namespace and Metadata Operations Next: Request Lifecycle and /dev/fuse Transport