Page Fault
Access to an un-mapped virtual address causes a page fault:
Link to original
- which is a hardware interrupt
- we may kill the process; ‘segmentation fault’
- the OS may handle the page fault and do something interesting:
- lazily allocate physical memory
- allocate and lazily load files into memory
- provide virtual memory via page swapping
- provide memory-mapped files; create the illusion that files on the disk are resident in the address space
- emulate the access
The OS may choose to reserve some virtual address space without allocating physical memory, we then can run a process as if there is memory there. Accessing this memory causes a page fault.

‘abort’ page fault handler
Here is a simple ‘abort’ page fault handler on x86:
static void handle_page_fault(const IRQ *irq, void *priv) {
// Retrieve fault address from control reigster
uint64_t fault_address;
asm volatile("mov %%cr2, %0" : "=r" (fault_address));
if (current_thread == NULL) {
// If there is no current thread, then this fault happened early.
// We must abort.
log("PAGE FAULT @ vaddr = %p", fault_address);
}
// If there is a current thread, we abort it.
log("PAGE FAULT @ vaddr = %p, rip = %p, proc = %s",
fault_address, current_thread->rip, current_thread->owner().name);
current_thread->owner().terminate(-1);
}Lazy allocation
Lazy allocation
Lazy allocation is where we allocate, grow, and copy memory as it is needed.
Link to original
- stack: allocate / grow as used
- heap: allocate / grow as used
- program segments: copy as needed from disk

This is quite similar to how it actually works however heap mapping doesn’t work like this, instead it is made and grown explicitly by system calls and not grown lazily by page fault.
Lazy zeroing
We can create the illusion of a zero-initialised region by mapping the same ‘read-only zero frame’ to all virtual addresses.

On first write, the fault handler maps a fresh frame. This means that:
- allocation appears fast
- we can make spare use of memory zeroes fit in memory and zeroes are readable at all times

Handling page faults with lazy zeroing
Let’s see what happens when we execute:
mov $0x00120348,(%rax) ; %rax points to zero-initialised memory areaThis triggers a page fault, after which the OS:
- Considers the faulting address and the address space (process).
- Determines how the area was allocated: ‘copy on write’
- Allocated a fresh frame
- Mapped it at the faulting page (replacing previous mapping of shared zero frame)
- Copied the shared contents (zeroes) into the new frame
- Resumed the process at the faulting instruction which now succeeds

Here’s how it could be implemented in code:
static void handle_page_fault(const IRQ *irq, void *priv) {
// Retrieve fault address from control reigster
uint64_t fault_address;
asm volatile("mov %%cr2, %0" : "=r" (fault_address));
if (current_thread == NULL) {
// If there is no current thread, then this fault happened early.
// We must abort.
log("PAGE FAULT @ vaddr = %p", fault_address);
}
// If there is a current thread, we abort it.
log("PAGE FAULT @ vaddr = %p, rip = %p, proc = %s",
fault_address, current_thread->rip, current_thread->owner().name);
// Is there a non-zero cookie in the PTE?
VMA & vma = current_thread->owner().vma();
uint32_t cookie;
bool success = vma.get_pte_cookie(fault_address, cookie);
if (success && cookie) {
// perform lazy zeroing
return;
}
current_thread->owner().terminate(-1);
}Copy on Write
Copy on Write
Lazy zeroing is actually a special case of lazy copying known as Copy on Write. Any shared data, not just zeros, can be shared by ‘many-to-one’ read-only mappings, then at the time of write (if it ever happens), we copy said data.
Link to original
Often this is used for text segments in executable files, programs rarely write to their own text so all processes that are instances of the same program can share it, hence saving memory.
