Last time: the MMU reads a PTE, and the valid bit is 0. The hardware cannot proceed, so it stops and hands the whole mess to the kernel. That's a page fault. (The TLB)
The name ?
A page fault is not an error. It is the normal, routine, designed mechanism by which memory gets loaded, and your machine is servicing thousands of them a second right now, this exact second, while you read this. "Fault" here means "the hardware hit a wall it can't get past alone and needs software to take over" - not "something broke." Same word English uses for "it's not your fault,".
(Small tangent, because I fell for this exact thing for a month or two of writing C: I used to think a page fault and a segfault were basically the same event with two names. They're not, and the difference is the entire point of this post. Anyway.)
Three questions, in order
When the fault lands, the kernel runs through a fixed sequence of instruction or rather you can say interrupt, and where you fall out of that sequence is the difference between a routine lazy load nobody ever notices and your program exiting with SIGSEGV.
1. Does this address even belong to you?
The kernel keeps a list of the legitimate regions of your address space, on Linux, vm_area_struct records, one per mapped region: code, heap, stack, each shared library, each mmaped file. The faulting address gets checked against that list. if it's Outside all of them? You referenced memory you were never given in the first place. Dereferencing NULL dies here, and so does a wild pointer that wandered off into nothing.
→ SIGSEGV.
btw this is a kernel SIG.
2. Fine, it's yours, but is the kind of access allowed?
Legal address but wrong permission. Writing to your own read-only code section (something like .rodata). User code reaching for a kernel-only page. Trying to execute a page that's marked no-execute. Here The address checks out but the verb do not.
again → SIGSEGV.
3. It's a legal address, a permitted access but the page just isn't in RAM yet.
This is overwhelming majority of the cases, and it's the one that's actually interesting: the kernel picks a victim frame, writes it back if it was dirty, fetches the page you actually wanted from wherever it lives, updates the PTE, and restarts the exact instruction that faulted.
Restarts it. Not resumes , restarts, from the top of that one instruction. The instruction can't know any of this happened. It fires, gets yanked out from under itself mid-flight, and fires again, and from the program's point of view, sure the whole thing just took a while, but No error code. No signal. Nothing to catch. It simply worked, eventually.
So: "segfault" isn't a curse the universe put on your code. It's the part of the plan. Your address failed check 1 or check 2, and you can usually tell which just by looking at what you touched.
The checking
None of this is free-floating logic somewhere in the kernel's head. It's bits, sitting right there in the PTE the hardware was already reading. On x86-64:
| Bit | What it means |
|---|---|
| P (present) | is this page actually in RAM right now |
| R/W | read-only vs read-write |
| U/S | user-accessible vs kernel-only |
| A (accessed) | hardware sets this on any reference — feeds the eviction policy |
| D (dirty) | hardware sets this on any write — tells the kernel this page needs writing back |
| XD (execute-disable) | instruction fetch from this page is forbidden |
That last one has a real history. Before it existed, an attacker who could smuggle bytes into a writable buffer could often just jump execution straight into it and run those bytes as code. XD lets the kernel draw a hard line , that , this region holds data, and data is never code, Period. And it killed an entire family of exploit outright. Same story as everything else in this series: the check costs nothing, because the hardware was reading that PTE anyway.
Right. Go home.
Except we've been saying "fetches the page from wherever it lives" for two posts now, and I've never told you where "wherever" actually is. Two very different answers, and which one applies to a given page decides almost everything else about how it behaves , whether it can be thrown away for free or has to be written somewhere first, what swap is actually for, why an initialized global is weirder than it looks. see you in next one.