XXNU Source Handbook Offline reading instructions
Part 10 · Chapter 10.13

UBC, clustered I/O, buffers, and storage completion

For write-path decisions, dirty tracking, direct-write coherence and transaction completion, continue with clustered writeback.

For the resident/missing-page read decision, read-ahead, mapping lifetime and truncation, see file-data caching and reads. For hashed buffer ownership, metadata storage, strategy completion and reclaim, see buffer cache and block I/O.

VFS index · VM pager/UPL chapter

One file, several representations

The unified buffer cache associates file contents with a VM object. ubc_info carries ui_pager, ui_control, owning vnode, size and mapping flags, read-ahead/write-behind state and code-signing data. ubc_info_init_internal creates the vnode pager and named VM-object association. File VM pages can serve both ordinary cached reads and mappings.

The traditional buf describes block or transfer state: vnode/device, logical and physical block numbers, byte count/residual, flags/error, data address or UPL, completion callback, queue links and filesystem/driver-private state. A buf does not necessarily own a separate copy of file data. Cluster I/O buffers often describe VM pages through b_upl; metadata buffers can have their own allocated storage. The implementations are related, not two independent universal caches through which every read passes.

Cached state What a hit establishes Detailed route
Name-cache entry A candidate name-to-vnode relationship, still subject to identity and authorization checks Name-cache lookup
Vnode / authorization state An object identity protected by the right references; separately, successful actions for a credential under cache predicates Vnode reuse and authorization
File-object resident page File bytes can be available without another storage read File-data cache
Hashed buffer A matching buffer descriptor; busy, validity, storage, and completion still have separate rules Buffer cache
Vnode cs_blob Retained signature metadata and cached policy properties for a covered range Signature attachment
VM signing masks / optional UBC bitmap Results reusable under the applicable validation predicates Page-signature validation

These states can survive different events. Closing one descriptor need not reclaim the vnode or evict file pages; evicting a page need not discard its signature metadata; clearing a signing generation does not itself reread file contents. Follow the lifetime and invalidation rules in the corresponding chapter rather than treating these as one cache.

Trace a cached read through an included filesystem

mockfs_read provides a compact concrete example: for a regular vnode it calls cluster_read with file size and I/O flags. This is an example filesystem route, not a claim that every filesystem uses the same helper.

cluster_read_ext selects cached-copy, direct or physically-contiguous paths according to flags and the uio. The ordinary copy path, cluster_read_copy, first tries copying resident UBC data. It tracks read-ahead/streaming state so I/O for future portions can overlap copying already available bytes.

On a miss it aligns a request around the file offset, bounds its size, and obtains a UPL using ubc_create_upl_kernel. It examines the UPL's per-page validity and submits runs of missing pages rather than re-reading every resident page. A UPL holds the page-transfer state needed to finish or abort; it is not just a scatter/gather address array.

UBC, clustered I/O, buffers, and storage completion diagram
UBC, clustered I/O, buffers, and storage completion diagram · Open SVG
Mermaid source
sequenceDiagram
    participant FS as Filesystem read method
    participant C as cluster_read_copy
    participant VM as UBC / VM pages
    participant IO as cluster_io
    participant D as Filesystem/device strategy
    FS->>C: File offset, size, uio
    C->>VM: Try cached copy#59; request UPL for missing data
    C->>IO: Read invalid page range without CL_COMMIT
    IO->>FS: VNOP_BLOCKMAP
    IO->>D: VNOP_STRATEGY(buf)
    D-->>IO: Completion callback
    IO-->>C: Completion accounting#59; demand caller waits
    C->>VM: Copy available data into uio
    C->>VM: Commit or abort retained UPL ranges
    C-->>FS: Bytes consumed / error

The sequence shows the demand-copy owner and omits parallel requests and special paths. Its cluster_io submission omits CL_COMMIT; the caller waits, copies, and finishes its retained UPL. Advisory read-ahead delegates completion with CL_COMMIT. Cached reads can finish before the extent/device stages; direct I/O uses caller-owned memory and has different rounding and page-lifetime requirements. See demand submission, wait/copy/completion, and the detailed read route.

Extents and device constraints

cluster_io bounds transfers by mount/device limits and asks VNOP_BLOCKMAP to map file offsets to device block numbers and contiguous lengths. The filesystem owns that mapping. A hole is represented by block number -1 with nonzero extent length; a zero-length successful result is rejected because no progress is possible. Read holes and write allocation requirements require distinct handling.

Cluster I/O prepares buffer descriptions and calls VNOP_STRATEGY, dispatching through the buffer's vnode operation vector. A local filesystem can forward through buf_strategy to its device. The included mockfs_strategy shows this explicitly. At the special-device layer, spec_strategy performs policy/accounting and calls the block-device switch's d_strategy. Hardware-driver queueing and DMA lie beyond generic VFS.

Page size, filesystem block size and device transfer limits are independent. cluster_io may round cached page I/O but explicitly avoids extending direct/device-memory requests into pages that do not belong to it. VNOP_BLOCKMAP caps a filesystem's returned run to the requested size. These checks explain why a single userspace read may split into several device requests even without errors.

Completion returns ownership

buf_biodone records completion, updates pending-I/O and throttle state, handles vnode write completion, and invokes configured one-shot callbacks or wakes/releases ordinary buffer waiters. A B_CALL callback takes ownership of the buffer; a B_FILTER callback leaves final cleanup to buf_biodone. Calling completion twice is a panic condition.

Cluster callbacks reach cluster_iodone_finish. It aggregates errors/residuals across a transaction, updates clios completion state under its mutex and wakes waiters. When the transaction owns UPL completion, success commits the range with appropriate flags; error processing calls cluster_ioerror and abort/commit handling as required. It then frees the I/O-buffer descriptions. Disk completion alone is insufficient: without UPL completion pages can remain busy or otherwise unavailable to VM.

VM page-in arrives through the vnode pager and vnode_pagein/VNOP_PAGEIN; filesystems can call cluster_pagein. Pageout similarly reaches cluster_pageout through filesystem methods. The VM pager chapter explains busy pages, object activity references and the pager-side relookup needed when locks are dropped.

Writes, delayed work, and the buffer cache

cluster_write_ext receives old/new EOF and optional head/tail zero-fill ranges in addition to data. It chooses copy/direct/contiguous paths. Cached writes can dirty UBC pages and accumulate write-behind work. In cluster_write_copy, IO_SYNC bypasses normal deferred accumulation and uses cluster_push_now to wait for issued I/O. This is an I/O-path property; durable filesystem transaction semantics still depend on filesystem and device behavior.

For conventional buffers, buf_getblk hashes by vnode and logical block, marks a found buffer busy under buf_mtx, or sleeps/retries if another caller owns it. Buffer-cache presence is not equivalent to valid bytes; B_DONE, dirty ranges, requested size and associated UPL state matter.

buf_bread starts or finds a read and waits for it. buf_bwrite submits a strategy request and waits unless asynchronous. buf_bdwrite marks delayed dirty work without immediate submission; buf_bawrite submits asynchronously, with limits on outstanding vnode writes. buf_brelse relinquishes buffer ownership; it is not a durability operation.

These paths share mount I/O constraints, VM resources, vnode write accounting and completion rules. No-cache/direct requests, compressed-file helpers, verification, content protection and I/O throttling add substantial branches. Their existence is recorded here, but their complete algorithms were not audited.

Search the handbook

Search every chapter, interface and code excerpt.

    Diagram

    Source preview

    Open full file