Photo by Franck V. on Unsplash

Page Faults and Demand Paging: How Operating Systems Load Memory on Demand


When a process tries to access memory that isn’t physically loaded, the operating system doesn’t crash or return an error. Instead, it triggers a page fault, transparently loads the required data from disk, and allows the program to continue as if nothing happened. This mechanism, called demand paging, is fundamental to how modern operating systems provide the illusion of abundant memory while keeping physical RAM usage efficient.

What Triggers a Page Fault

A page fault occurs when the CPU’s memory management unit (MMU) encounters a page table entry marked as “not present.” This happens in several scenarios: the page has never been loaded (a typical case when a process first starts), it was swapped out to disk under memory pressure, or it’s part of a memory-mapped file that hasn’t been read yet.

When the MMU detects this condition, it raises a page fault exception. Control transfers from user space to the kernel’s page fault handler, which determines whether the access is valid. If the address falls within a legitimate memory region for that process, the kernel proceeds to load the page. If not, it sends a segmentation fault signal, terminating the process.

The beauty of this design is that programs don’t need to manually manage what’s in RAM. The operating system makes all memory appear available, backed by a combination of physical RAM and swap space.

How Demand Paging Works

Demand paging delays loading pages until they’re actually accessed. When a process starts, the kernel sets up the virtual address space but doesn’t immediately load all executable code and data into RAM. Only the initial entry point and essential structures are resident.

As the program executes and jumps to new code paths or accesses new data, page faults fill in the gaps. The kernel reads 4KB pages (on most architectures) from the executable file or swap space, updates the page table to map the virtual address to the newly allocated physical frame, and resumes execution at the faulting instruction.

This lazy loading strategy reduces startup time and memory waste. A large application might have megabytes of initialization code that runs once, error handling paths that rarely execute, or features the user never touches. Demand paging ensures only the working set stays in RAM.

Major vs Minor Faults

Not all page faults require disk I/O. A minor fault occurs when the page is already in memory but not mapped into the process’s page table, common when multiple processes share the same executable or library. The kernel simply updates the page table and continues.

A major fault involves actual disk reads, which are orders of magnitude slower. The faulting thread blocks while the kernel issues I/O, schedules other work, and waits for the storage subsystem to deliver the page. High major fault rates indicate memory pressure or poor locality of reference.

Operating systems track these metrics separately. Tools like vmstat or /proc/pid/stat report minor and major faults, helping diagnose whether an application is thrashing or simply mapping shared libraries.

Copy-on-Write and Page Faults

When a process forks, the kernel doesn’t immediately duplicate all memory pages. Instead, it marks parent and child page tables as read-only and shares the physical frames. If either process writes to a shared page, a page fault occurs. The kernel allocates a new frame, copies the original page’s contents, and updates the faulting process’s page table to point to the private copy.

This copy-on-write optimization makes fork extremely fast, even for large processes. Only pages that are actually modified get duplicated, saving both time and memory for the common pattern of fork-then-exec.

Performance Implications

Page faults introduce latency. A minor fault might cost hundreds of nanoseconds, while a major fault can take milliseconds if it requires disk I/O. For latency-sensitive applications, this unpredictability is problematic.

Techniques to mitigate page fault overhead include pre-faulting (touching memory during initialization to force pages resident), using mlock to pin critical pages in RAM, and configuring huge pages to reduce TLB pressure and fault frequency. Understanding when and why page faults occur helps tune applications for predictable performance in production.