A page fault occurs when a memory access fails because the MMU lookup for the virtual address ended in an invalid descriptor or in a descriptor indicating a lack of permissions (e.g. write attempt to a read-only page). When a page fault occurs, the processor performs a few actions; the details are specific to each processor architectures but the gist is the same:
- Switch to a privileged mode (e.g. kernel mode).
- Set some registers to indicate, at least, the nature of the fault, and the program counter and processor mode at the point of the fault.
- Jump to a particular address in memory, indicated by a register or itself looked up at a particular location in memory: the address of the page fault handler.
To give an example, on an (32-bit) ARM processor:
dfsr register is set to a value that describes the fault (whether it was due to a read or write, to a processor instruction or a DMA, etc.).
dfar register is set to the virtual address that was the target of the access that caused the fault.
- The processor switches to abort mode (one of the kernel-level privileged modes).
lr register is set to the program counter at the time of the fault, and the
spsr register is set to the program status register (
cpsr, the one that contains the mode bits, among other things) at the time of the fault.
cpsr registers are banked: they are restored from the value last set in abort mode.
- The execution jumps to the abort vector, one of the exception vectors.
The code of the page fault handler is part of the kernel of the operating system. Its job is to analyze the cause of the fault and to do something about it. It can consult the special-purpose registers that provide information about the nature of the fault, and if needed it can also inspect the instruction that the program was executing. It can also look up the descriptor in the MMU table; invalid descriptors can sometimes encode information such as the location of a page in swap space. The kernel knows which task is currently executing by looking at the value of a global variable or register that it updates on each context switch. Here are a few common behaviors on a page fault:
- The data about the process's memory mappings indicate that the page is in swap. The kernel finds a spare physical page, or obtains one by removing a page that contained disk cache, or obtains one by first saving its content to swap. Then it loads the data from the swap to this physical page, and changes the MMU table so that the virtual address that caused the fault is now attached to that physical page in the process's MMU map. Finally, the kernel arranges to switch back to the process at the point of the instruction that caused the fault; this time the instruction will be executed successfully.
- The data about the process's memory mappings indicate that the page is a copy-on-write page, and a write access was attempted. Rather similarly to the previous case, the kernel obtains a spare physical page, copies data to it (here, from the page that was read-only), changes the MMU descriptor, and arranges for the process to execute the instruction again.
- The data about the process's memory mappings indicate that the page is not mapped, or that it doesn't have the requisite permissions. In that case the kernel delivers a SIGSEGV signal (segmentation fault) to the process: the execution of the process resumes at the signal handler rather than at the original location, but the original location is saved on the stack. If the process has no handler for SIGSEGV, it is terminated.
It is not in general possible to determine that an exception is about to happen, except by knowing the virtual memory configuration and making checks before memory accesses. The normal flow of operation is that the reason for the page fault is recorded by the processor when the page fault happens.