How Linux Handles Page Faults During Execution

This article provides an overview of how the Linux kernel manages page faults when applications run. A page fault is a hardware interrupt raised by the CPU's Memory Management Unit (MMU) when an application attempts to access a virtual memory page that is not currently mapped to physical RAM. Linux resolves these interrupts dynamically through a series of checks, hardware-to-kernel handoffs, and memory-mapping strategies, categorizing each event as a minor fault, a major fault, or an invalid access violation.

The Trigger: Virtual to Physical Address Translation

Linux uses virtual memory, isolating processes by granting each its own virtual address space. The CPU's Memory Management Unit (MMU) translates these virtual addresses into physical addresses using page tables.

When a process references an address whose corresponding Page Table Entry (PTE) is marked as "not present" or violates access permissions (such as writing to a read-only page), the MMU cannot complete the translation. The hardware immediately pauses the offending instruction and triggers a page fault exception.

The Architecture Exception Handler

When the page fault occurs, the CPU switches to kernel mode and jumps to the architecture-specific fault handler, such as do_page_fault() on x86 architectures. The CPU registers pass critical details to the kernel:

Evaluating the Memory Region (VMA)

The Linux kernel checks the process's memory descriptor (mm_struct) and searches its Virtual Memory Areas (VMAs) to determine whether the requested address falls within a legally allocated region.

  1. Invalid Memory Access: If the address does not belong to any valid VMA, or if the access violates the region’s permissions (e.g., attempting to write to a read-only code segment), the kernel flags an invalid access. The kernel sends a SIGSEGV (Segmentation Fault) signal to the process, which typically terminates the application unless a custom signal handler is configured.
  2. Valid Access: If the address is within a legal VMA and the access permissions match, the kernel proceeds to resolve the missing page using handle_mm_fault().

Types of Page Faults

Linux classifies valid page faults into two primary categories based on whether disk access is required:

Minor (Soft) Page Faults

A minor page fault occurs when the data required already resides in physical memory, but the process's page tables have not yet established a mapping. Common scenarios include:

Because minor faults avoid storage operations, the kernel resolves them quickly by updating the PTE and assigning the existing physical frame.

Major (Hard) Page Faults

A major page fault occurs when the required page is not in physical RAM and must be read from disk. This happens when:

During a major fault, the kernel initiates an asynchronous I/O operation to read the page from disk into a newly allocated physical page frame. Because disk I/O is slow, the kernel places the faulting process into a sleeping state (TASK_UNINTERRUPTIBLE or TASK_INTERRUPTIBLE) and schedules other tasks to run on the CPU.

Copy-on-Write (COW) Faults

When a process calls fork(), child processes share the parent’s physical pages to save memory, with all shared pages marked as read-only. When either process attempts to write to one of these shared pages, a page fault occurs. The kernel catches the permission mismatch, allocates a new physical frame, duplicates the contents from the original page, updates the faulting process's page table to point to the new frame, and sets the permissions to read-write.

Resolution and Execution Resumption

Once the physical frame is allocated and populated:

  1. The kernel updates the corresponding Page Table Entry (PTE) with the physical frame address and marks the entry as present with the appropriate access flags.
  2. The kernel updates the Translation Lookaside Buffer (TLB) so the CPU caches the new translation.
  3. If the process was sleeping due to a major fault, the kernel marks the process as runnable, returning it to the runqueue.
  4. The kernel switches execution context back to user mode, restarting the exact instruction that previously faulted. With the page table entry now present, the instruction completes without interruption.