A thread can access a valid virtual address and stop before that access completes because another userspace component has been given responsibility for resolving the page fault. With Linux userfaultfd, selected memory ranges can turn faults into descriptor messages while the faulting thread remains blocked until an appropriate resolution operation makes progress possible.
This is not a replacement for the kernel’s virtual-memory subsystem. The kernel still detects the fault, validates the registered range, blocks the affected execution path, and performs the page-table operation requested by the manager. The unusual boundary is that userspace can participate in deciding when and with what contents a fault is resolved.
Registration defines which faults cross the boundary
A userfaultfd object starts as an unconfigured file descriptor. UFFDIO_API establishes the protocol version and negotiates feature bits. Memory ranges are then registered with UFFDIO_REGISTER, whose mode selects the classes of faults that userspace will receive.
Missing mode reports accesses for which the registered address lacks a page that satisfies the access. The manager can resolve such a fault with operations such as UFFDIO_COPY or UFFDIO_ZEROPAGE, subject to the memory type and capabilities reported for the range.
Minor mode addresses a different state. A backing page already exists, but the relevant page-table entry is absent. For supported shmem and hugetlbfs mappings, the manager can update backing contents through another mapping if needed and then use UFFDIO_CONTINUE to let the kernel install the mapping.
Write-protect mode adds another boundary. A present page can be marked for userfaultfd write protection; a write then produces a page-fault event carrying the write-protect flag. Clearing that protection with UFFDIO_WRITEPROTECT resolves the blocked write path.
These modes are not interchangeable descriptions of one generic fault. They represent distinct memory states and require mode-appropriate resolution.
The descriptor carries events, not the memory itself
The manager receives uffd_msg records by reading the userfaultfd. The page-fault record includes an address and flags describing properties such as write access, write-protect state, or minor-fault state when those features apply.
The descriptor therefore separates notification from page contents. A missing-page event does not contain the page that should appear at the faulting address. The manager must obtain or construct that data elsewhere, then invoke an ioctl that asks the kernel to resolve the fault.
This split is useful for process boundaries. A userfaultfd can be transferred over a Unix domain socket, allowing a manager process to handle registered memory belonging to another process. The descriptor transfers access to the fault-management interface; it does not transfer the target address space as ordinary readable bytes.
Fault resolution is atomic at the page-visibility boundary
UFFDIO_COPY is specified so other threads do not observe a partially populated destination page. Threads that reach the unresolved state continue faulting or remain blocked until the operation has established the page.
That atomicity is narrower than transaction semantics for the surrounding application. If a manager resolves several pages independently, userfaultfd does not turn those resolutions into one multi-page commit. Application data structures spanning pages can still require their own synchronization and consistency protocol.
Wakeup is also separable from some resolution operations. The UFFDIO_*_MODE_DONTWAKE variants allow a manager to perform work without immediately waking blocked faults, followed by an explicit wake operation. This permits batching of visibility and scheduling decisions, but it also creates a state in which page preparation has completed while affected threads intentionally remain asleep.
Blocking creates a dependency edge into the manager
Synchronous fault handling makes the faulting thread depend on the manager’s ability to run and resolve the event. That dependency is observable even when the original memory access looks like an ordinary load or store in source code.
A manager that waits for work requiring the blocked thread can therefore create a cycle. The kernel cannot infer an application-level escape from that dependency; it only knows that the fault remains unresolved. Designs using synchronous modes must keep the resolution path independent of resources whose progress depends on faulting threads.
The same boundary affects latency. A page access can now include scheduling the manager, obtaining page data, issuing a resolution ioctl, and waking blocked execution. No fixed latency follows from the API. Storage, network, scheduler, and manager behavior all remain deployment-specific inputs.
Address-space changes are separate events
For non-cooperative management, page faults are not the only relevant state changes. Optional userfaultfd features can report operations such as fork, remap, and removal events. These notifications let an external manager track changes that can invalidate assumptions about registered virtual addresses.
Event delivery does not make address-space state immutable. The manager must treat mapping changes as part of the protocol and synchronize its own metadata with the events it requested. A cached association between an address and external page data can become stale if the process changes its mappings.
This also means that an address alone is not a durable object identity. It is meaningful within a particular address-space state and registration context.
Closing the descriptor removes the delegation
When the last descriptor referring to a userfaultfd object is closed, registered ranges are unregistered and unread events are flushed. Descriptor lifetime is therefore part of the fault-management contract.
That does not mean closing the descriptor destroys the mappings. It removes the userfaultfd registration from those ranges. The memory remains governed by the normal virtual-memory rules that apply after the delegation disappears.
This distinction matters for ownership transfer. Duplicating or passing the descriptor can extend the lifetime of the userfaultfd object beyond one manager’s local descriptor. Cleanup must account for all references rather than assuming that one close() necessarily ends the protocol.
Delegated faults remain a negotiated kernel interface
Userfaultfd exposes page-fault control through a versioned, feature-negotiated interface rather than a universal promise that every mode works for every mapping. Supported memory types and operations vary by mode and kernel capability, and callers are expected to inspect feature and ioctl masks returned during setup.
The resulting contract is precise: selected faults can become messages, selected threads can remain blocked, and userspace can request specific kernel operations to resolve those faults. The kernel still owns page tables and validates each operation. Userspace owns the external policy only inside the capabilities it successfully negotiated.
That boundary is what makes userfaultfd useful for mechanisms such as demand population, external memory managers, and migration systems. It does not make memory access asynchronous in general; it creates an explicit protocol at registered fault points, with descriptor lifetime, manager progress, mapping state, and negotiated kernel support all remaining part of correctness.