A thread can touch a valid virtual address and stop before the access completes because the page has no present backing yet. With a range registered in UFFDIO_REGISTER_MODE_MISSING, Linux can report that fault through userfaultfd instead of resolving it entirely inside the kernel. A user-space manager then decides which page contents become visible before the blocked access resumes.
This changes the ownership of one part of page-fault handling. The kernel still detects the fault, validates the virtual memory area, blocks the faulting execution, and installs mappings through the UFFDIO_* interface. User space gains control over the content and timing of resolution for registered faults.
Registration defines the interception boundary
A userfaultfd object does not redirect every page fault in a process. The descriptor is initialized with UFFDIO_API, then selected virtual address ranges are registered with UFFDIO_REGISTER.
For missing-page interception, the registration includes UFFDIO_REGISTER_MODE_MISSING. An access to a missing page in that range can produce a UFFD_EVENT_PAGEFAULT message. The event carries the fault address and flags describing properties such as whether the access was a write.
The range boundary matters. An address outside a registered region follows the ordinary virtual-memory fault path. A populated page inside a range registered only for missing faults also proceeds without a missing-page notification.
The mechanism is therefore selective: registration establishes which mappings participate, and the selected mode establishes which fault class is redirected.
The faulting thread waits for page resolution
For synchronous missing-page handling, notification is not merely an observation after the event. The faulting thread is stopped until the fault is resolved.
A manager can monitor the descriptor with poll(), select(), or epoll() when the descriptor uses nonblocking operation. Reading the descriptor yields a struct uffd_msg describing the pending event.
A simplified state sequence is:
thread accesses registered address
|
page is missing
|
kernel queues event
|
thread remains blocked
|
manager reads fault address
|
manager resolves the page
|
access can continueThis blocking boundary is central to the API. The manager is on the critical path for the first access to a missing page. Delayed resolution directly delays the faulting thread.
UFFDIO_COPY publishes a complete page
For a missing fault, UFFDIO_COPY can copy page contents from a user-space source buffer into the faulting mapping. UFFDIO_ZEROPAGE can resolve supported missing faults with a zero-filled page.
The kernel documentation describes these resolution operations as atomic with respect to page visibility: other accessors do not observe a half-populated page. Threads continue faulting or remain blocked until the page installation is complete.
That property is narrower than transaction atomicity for a larger memory region. Resolving several pages requires several operations unless another interface provides a broader guarantee. A consumer can therefore encounter one resolved page while another page in the same application-level object is still absent.
The page is the important granularity for the missing-fault resolution contract.
Resolution and wakeup can be separated
The normal resolution path wakes threads waiting on the affected range. The relevant UFFDIO_* operations also provide *_MODE_DONTWAKE variants for cases where page installation and thread wakeup need separate timing.
That split permits a manager to populate multiple pages or coordinate other state before allowing blocked execution to continue. It also creates an additional obligation: a page can be present while a waiter remains asleep if the manager deliberately suppresses wakeup.
This distinction is useful when reasoning about latency. Page availability and runnable-thread state can become separate transitions under explicit manager control.
Missing faults are not write-protect faults
userfaultfd supports multiple registration modes with different semantics. Missing mode handles accesses where the required page is absent. Write-protect mode, registered with UFFDIO_REGISTER_MODE_WP, reports writes to pages protected through the userfaultfd write-protection mechanism.
A write-protect fault is resolved by changing protection with UFFDIO_WRITEPROTECT, not by supplying a new page through UFFDIO_COPY. The existing page and its content can already be present.
The modes can be combined for supported memory types and kernel features, but they represent distinct transitions:
missing mode:
no usable page -> page supplied -> access resumes
write-protect mode:
page present, write blocked -> protection cleared -> write resumesTreating both as generic page notifications loses the state distinction that determines the valid resolution operation.
Feature negotiation is part of the contract
UFFDIO_API is a handshake, not a decorative initialization call. It identifies the API version and negotiates feature bits supported by the running kernel. Later registration and ioctl behavior depends on that negotiated capability set.
Support also varies by memory type and kernel version. Anonymous mappings, shmem, and hugetlbfs do not have identical historical support across missing, minor, and write-protect modes.
Code that assumes a mode exists because the headers define its constants can fail on another running kernel. The runtime feature bitmap is the relevant capability boundary.
This is also where architectural guarantees should be separated from deployment assumptions. The interface defines semantics for negotiated features; a specific machine may expose only a subset.
Access control limits fault interception
Current Linux exposes more than one route for creating a userfaultfd object. The userfaultfd() system call supports UFFD_USER_MODE_ONLY, which restricts the descriptor to faults originating from user mode. Broader interception is subject to kernel security controls and privileges.
Linux also supports creation through /dev/userfaultfd on systems that provide it, with access governed through the device’s filesystem permissions.
These controls matter because fault interception can influence another execution context’s progress and memory population. Availability of the API should not be treated as equivalent to unrestricted authority over every fault source.
Manager failure becomes memory-access failure
Moving resolution into user space also moves a failure dependency into user space. If the manager stalls while a faulting thread waits, the memory access stalls with it. If the descriptor lifecycle ends, registered ranges and pending-event behavior follow the kernel’s userfaultfd teardown rules rather than an application-defined fallback pager appearing automatically.
This makes liveness a design property of the manager. A system using userfaultfd for demand paging, migration, snapshots, or memory tracking needs to account for manager scheduling, event draining, cancellation, address-space changes, and shutdown ordering.
The API provides a controlled fault boundary; it does not remove the need to define what happens when the component controlling that boundary stops making progress.
User-space paging changes policy placement, not MMU ownership
userfaultfd does not turn virtual memory into an unmediated user-space page table API. The MMU still raises faults, the kernel still owns page-table updates, and the UFFDIO_* operations request kernel-managed changes.
The shift is in policy placement. For registered fault classes, user space can choose page content, coordinate external state, track writes, or delay continuation before asking the kernel to complete the mapping transition.
That boundary enables post-copy migration and other demand-driven memory systems without requiring the faulting thread to execute a signal handler that reconstructs the mapping itself. It also makes the manager’s latency and correctness part of the memory-access path.
The resulting model is precise: registration selects a virtual range and fault class, the kernel reports matching faults, affected execution waits when the mode requires synchronous handling, and a user-space manager resolves the fault through negotiated kernel operations.