Virtual Memory Explorer
follow an address through an AArch64 page-table walk
Virtual address0x0000_0040_0812_3ABC
Address 0x0000_0040_0812_3ABC split into fields: L0 index 0, L1 index 256, L2 index 64, L3 index 291, offset 0xABC.
L0 table0x4000_0000
- [000] tableV T→ 0x4000_1000
- [001] invalid
- [002] invalid
- [003] invalid
- [004] invalid
L1 tablenext
L2 tablenext
L3 tablenext
PA pending: walking L0
TLB MISSTTBR0_EL1 = 0x4000_0000 · 4 levels, 48-bit VA, 4 KB granule
step 1 of 5
TLB lookup: miss. Start a table walk at L0.
TLB lookup: miss. Start a table walk at L0.
How it works
Type any virtual address and follow it through an AArch64 page table walk. Switch between 3 and 4 levels, map 2 MiB blocks, fill the TLB and trigger translation faults, step by step.
How AArch64 address translation works
Every load, store and instruction fetch your program makes uses a virtual address. Once the MMU is on, the CPU never sends that address to memory directly. It translates it first, using page tables that the kernel built in ordinary RAM.
With a 4 KB granule and a 48-bit address space, the translation is a series of lookups in 4 KB tables. Each table holds 512 eight-byte entries, so each level of the walk consumes 9 bits of the address:
| Bits | Field | One entry covers |
|---|---|---|
| [47:39] | L0 index | 512 GiB |
| [38:30] | L1 index | 1 GiB |
| [29:21] | L2 index | 2 MiB |
| [20:12] | L3 index | 4 KiB |
| [11:0] | Offset | 1 byte |
The walk starts at the table whose physical address is in a translation table base register. Bits [63:48] choose which one: all zeros selects TTBR0_EL1 (the lower half, usually user space), all ones selects TTBR1_EL1 (the upper half, usually the kernel). An address with any other pattern is outside both halves and faults immediately. The explorer colours the address bits by level, and the same colours mark the table entry each field selects.
At each level the selected entry is a descriptor, and bits [1:0] say what kind:
- Table descriptor (
0b11at L0–L2): the entry holds the physical address of the next-level table, and the walk continues there. - Block descriptor (
0b01at L1 or L2): the walk stops early and maps a whole 1 GiB or 2 MiB region. - Page descriptor (
0b11at L3): the walk ends at a 4 KiB page. - Invalid (bit 0 clear): the walk stops with a translation fault at that level.
A block or page descriptor also carries attributes. The explorer shows the ones that change the outcome: the access flag (bit 10), which must be set or the first access traps; AP[2:1], which decides whether the mapping is read-only and whether EL0 may use it; and the memory type, Normal or Device, chosen through a MAIR slot. The physical address is the descriptor’s output address plus the address bits below the level where the walk stopped: 12 bits for a page, 21 bits for a 2 MiB block.
Why the TLB exists
A four-level walk is four dependent memory reads before the access you actually asked for can start. Doing that on every instruction would make the MMU unusable, so the CPU caches finished translations in a translation lookaside buffer (TLB). On a hit, the physical address comes straight from the TLB and no table is read at all. On a miss, the walk runs and its result is stored, evicting an older entry when the TLB is full. This explorer models a small TLB with least-recently-used replacement, and a block mapping occupies one entry for its whole 2 MiB.
The TLB is a cache of the page tables, not a view of them. Change a descriptor and the TLB keeps the old translation until the kernel invalidates it, which on AArch64 means a TLBI instruction. Try unmapping a page you have just translated: the next access still succeeds through the TLB until you press Flush TLB.
Three levels or four
The number of levels follows from the size of the virtual address space. TCR_EL1.T0SZ = 16 gives a 48-bit space and a walk that starts at L0. With T0SZ = 25 the space shrinks to 39 bits (512 GiB), the L0 field disappears, and the walk starts at L1. Switch Levels to 3 and watch the bit strip lose its L0 field and the upper-half check move down to bit 39.
OS chapter 6 keeps the 48-bit configuration, so its walks start at L0, but they only touch three tables. L0[0] points at one L1 table; L1[0] and L1[1] point at two L2 tables; and the L2 entries are 2 MiB blocks. The walk never reaches L3.
Block or page mappings
Blocks are the shortcut. One L2 entry maps 2 MiB, so chapter 6 covers the whole first gigabyte of QEMU’s virt machine, where the PL011 UART sits at 0x0900_0000, with a single table of 512 Device blocks, and the kernel’s 16 MiB of RAM at 0x4000_0000 with eight Normal blocks. Fewer levels means fewer reads on a TLB miss, and each TLB entry covers far more memory.
Pages are the fine-grained option. Mapping in 4 KiB units costs one more level and many more TLB entries, but it lets the kernel give each page its own permissions, which is what process isolation needs. Use Edit page tables to add both kinds and see which intermediate tables get allocated.
The tables shown are a model, not a dump of a running kernel. Chapter 6’s tables live in .bss, so their physical addresses depend on the link. The explorer places them at 0x4000_4000, the TTBR0_EL1 value the chapter shows from one build.
Try it
-
Same page again
Translate an address, then a second address in the same 4 KB page. Why does the second translation take a single step?
-
First access: TLB miss
Switch Levels to 3 and translate the default address again. Which field disappears, and how large is the address space now?
-
2 MiB block at L2
Step through the UART address in chapter 6’s tables. Where does the walk stop, and how many bits of the virtual address pass straight into the physical address?
-
TTBR1 disabled (EPD1)
Translate an upper-half address with chapter 6’s configuration. Why does it fault before a single table is read?
-
Thrash an 8-entry TLB
Ten 2 MiB blocks visited in turn through an 8-entry TLB. Why is the hit rate zero with least-recently-used replacement, and how many entries would the TLB need before the second round could hit?
Each one opens the matching scenario in the explorer above.
Where this comes from
The model follows OS chapter 6: AArch64 Virtual Memory & the MMU, where PurgatoryOS builds these tables in C and turns the MMU on. For the hardware side (caches, the TLB and what a page fault costs) read Architecture chapter 5: the memory hierarchy.
It deliberately leaves out stage-2 translation, ASIDs, the contiguous bit, hardware access-flag updates and the 16 KB and 64 KB granules.