A thread can fault on a virtual address and remain blocked while another userspace thread or process decides what page state should make that access continue. userfaultfd provides this boundary by turning selected page faults into messages on a file descriptor and pairing those messages with ioctls that resolve the fault.

The mechanism does not replace the kernel page-fault machinery. It inserts userspace control at registered ranges and fault classes, while the kernel still owns page tables, fault blocking, and the transition that makes the page usable again.

Registration selects the fault classes

A newly created userfaultfd must first negotiate the API with UFFDIO_API. Userspace then registers virtual-address ranges with UFFDIO_REGISTER and selects one or more supported modes.

Three established modes represent different memory states:

  • UFFDIO_REGISTER_MODE_MISSING reports access to a page that is absent from the registered mapping.
  • UFFDIO_REGISTER_MODE_MINOR reports a minor fault where backing content already exists but suitable page-table entries are not installed.
  • UFFDIO_REGISTER_MODE_WP reports writes to pages placed under userfaultfd write protection.

Mode availability depends on kernel support and memory type. Feature negotiation and the ioctl bit mask returned for a registered range are therefore part of the interface, not optional capability hints.

A fault becomes a descriptor event

When a registered fault occurs, the faulting thread is stopped and an event becomes readable from the userfaultfd. A manager reads a struct uffd_msg and receives the fault address plus flags describing the event.

This separates the thread that caused the memory access from the code that supplies or releases the page. The manager can live in another thread, and a userfaultfd can also be transferred over a Unix domain socket to an external manager.

The descriptor can participate in poll(), select(), or epoll() when configured for nonblocking operation. That property lets page-fault handling enter an event loop alongside other descriptor-driven work instead of requiring a signal handler in the faulting thread.

Missing faults require page contents

In missing mode, the fault is not resolved merely by reading its notification. Userspace must provide state that allows the access to proceed.

UFFDIO_COPY copies supplied contents into the faulting range and installs the resulting page atomically with respect to fault resolution. UFFDIO_ZEROPAGE supplies a zero-filled page where that operation is supported.

The atomicity matters at the publication boundary: other threads do not observe a partially populated page through the resolving operation. They continue to fault until the page is installed.

This behavior supports demand population where page contents come from a checkpoint image, remote store, compressed representation, or another application-managed source. The kernel provides the blocking and installation protocol; the policy for obtaining the bytes remains in userspace.

Minor faults operate on existing backing pages

Minor mode covers a different state. The backing page already exists, but the faulting mapping does not yet have the page-table state needed to access it.

For supported shmem or hugetlbfs mappings, the manager can modify the backing page through another mapping before resolving the event. UFFDIO_CONTINUE then installs the mapping state needed for the blocked access to resume.

That distinction makes minor faults materially different from missing faults. UFFDIO_COPY creates or populates the missing state; UFFDIO_CONTINUE exposes backing content that is already present.

Write protection makes writes observable

Write-protect mode allows userspace to mark pages with UFFDIO_WRITEPROTECT. A later write to a protected page generates a page-fault event carrying UFFD_PAGEFAULT_FLAG_WP, and the writing thread remains blocked until the protection is removed or another applicable resolution occurs.

This creates a synchronous dirty-tracking boundary. A manager can observe the first protected write before permitting it to continue, which is useful when memory state must be tracked across checkpointing or migration phases.

Write-protect support differs across anonymous, shmem, and hugetlbfs memory and has gained additional feature bits over time. Code that depends on a specific behavior must negotiate the corresponding UFFD_FEATURE_* capability rather than infer support from the presence of the system call.

Fault resolution and wakeup can be separated

The resolving ioctls normally wake blocked faults in the affected range. Several operations also provide DONTWAKE modes, allowing page state to be prepared without immediately releasing waiting threads.

That split permits batching: a manager can resolve multiple pages and perform a later wake operation after a larger state transition is complete. It also means correctness cannot be reduced to a single event-read loop. Page state, resolution, and wakeup form separate parts of the protocol.

Concurrent changes require similar care. A mapping can be unmapped, remapped, or removed while an external manager is operating. Optional non-page-fault events can report selected virtual-memory changes, but applications still need synchronization appropriate to their ownership model.

Access to userfaultfd is security-sensitive

Historically, faults originating from kernel access made unrestricted userfaultfd useful in kernel exploitation techniques. Linux therefore distinguishes restricted creation paths.

userfaultfd(UFFD_USER_MODE_ONLY) creates a descriptor limited to faults originating from userspace. Broader handling through the system call is subject to capability or system policy. Linux also exposes /dev/userfaultfd on systems that enable it, where filesystem permissions can control access to creating userfaultfd objects.

These controls concern who may create a fault-management channel; they do not make the manager trusted automatically. A manager that supplies incorrect page contents or fails to resolve faults can stall or corrupt the application state it controls.

The boundary is page-fault mediation, not general paging replacement

userfaultfd is narrow in a useful way. It converts selected virtual-memory faults into descriptor events, blocks the accessing thread at that boundary, and provides explicit operations for publishing page state or changing protection.

The kernel retains the page-table transition and wakeup machinery, while userspace controls selected policy decisions. That division supports post-copy migration, lazy restore, application-managed paging, and synchronous write tracking without moving the entire virtual-memory subsystem into userspace.