CS 134

Paging

The Story So Far

We've moved from arranging memory using segments,

Logical View Physical Memory Process 2 Process 3 OS (256 KB) Process 1 neovim Process 3 Process 2 Process 1 neovim neovim neovim
Sharing neovim with Segmentation

to arranging memory using pages,

Physical Memory Logical View Process 3 Process 2 Process 1 Process 2 OS (256 KB) neovim Process 2 Process 3 Process 1 neovim neovim neovim
Sharing neovim with Paging

Terminology

  • Page: A fixed-size chunk of memory that holds code or data.
    • 4 KB is a common size for pages, but it isn't the only option. For example, macOS on ARM CPUs uses 16 KB pages.
  • Page Table: A data structure that maps logical addresses to physical addresses.
  • Page Table Entry: A single entry in the page table that maps a single page.
  • Frame: A fixed-size chunk of physical memory that holds a page. (Sometimes called a page frame.)

Mapping Logical Addresses to Physical Addresses

The page table maps each page to a page frame. In this design, we're imagining the table is external to the CPU because it's too big to fit on the chip.

physical memory (frames) 0 f n-1 frame Processor p d f p v 1 f d page table physical address logical address 0
Page Table Translation
  • Duck speaking

    I almost get it, but why are there blue pages in the frames? I don't see any blue pages in the logical address space.

  • PinkRobot speaking

    The logical address space is per-process, and each process has its own set of pages. The blue pages are from a different process.

  • Horse speaking

    So the page table is different for each process?

  • PinkRobot speaking

    Yes, because each process has its own logical address space.

  • Dog speaking

    I guess the green is the code and the orange brown is the stack, but why is there a huge gap of white nothingness in the middle?

  • PinkRobot speaking

    Those are parts of the logical address space that don't have a page mapped to them. They're not valid addresses. But they provide room for the stack to grow downward and the heap to grow upward.

  • Horse speaking

    Hay! Don't you have that backwards?

  • PinkRobot speaking

    That's the weird thing about how we represent memory when we draw it on a page. Usually the far end of memory (the “top”) is at the bottom of the page.

Fun with Logical to Physical Address Translation: Aliasing

Given our arrangement of memory, it's technically possible to have multiple pages in our logical address space map to the same frame of physical memory: page aliasing. It's not necessarily particularly useful, but it's possible. If you click the link below, it will take you to a page in OnlineGDB where you can see page aliasing in action.

Created two mappings of the same page:
addr1: 0x78f9f10db000
addr2: 0x78f9f10a1000

Writing first string to addr1...
At address 0x78f9f10db000: "Hello from first mapping!"
At address 0x78f9f10a1000: "Hello from first mapping!"

Writing second string to addr2...
At address 0x78f9f10db000: "Greetings from second mapping!"
At address 0x78f9f10a1000: "Greetings from second mapping!"

Improving Efficiency

Because the page table is large, we placed it off-chip in memory. So every memory access actually requires two memory accesses: one to get the page-table entry and another to get the data. Which is slow.

One approach to making address translation more efficient is to add a cache that remembers a small number of recently used page-table entries. In a sane world, this feature would be called the address translation cache, but in the world of computer science, it's called the translation lookaside buffer (TLB), in part because it was invented before people started using the term “cache”.

The diagram below shows how the TLB fits into the picture. We're looking for page 4, which happens to be in frame 3. We could find that by looking in the page table, but since it's in the TLB, we can skip that step.

physical memory (frames) 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 frame Processor 4 193 3 0 1 2 4 5 6 7 8 9 10 11 12 13 14 15 3 193 physical address logical address 0 3 7 5 translation lookaside buffer page frame frame page offset offset page frame 7 0 15 10 3 13 page table 5 14 4 0 1
Adding a TLB
  • Duck speaking

    So the TLB is like a cache for the page table?

  • PinkRobot speaking

    Yes, exactly. It's a cache for the most recently used page-table entries.

  • Cat speaking

    But the page table still has lots of spaces in it, which seems wasteful. And this diagram only shows 16 entries, but on a real machine with 32-bit addressing, we said the range of logical addresses amounts to about a million pages.

  • PinkRobot speaking

    Let's sort that out.

Saving Memory with Multi-Level Page Tables

First, let's take a look at a slightly bigger page table.

Page Frames Page Table
A Larger Page Table
  • Horse speaking

    Hay! Why haven't we put any pages at the very beginning of memory?

  • PinkRobot speaking

    If we leave address zero unmapped, it can catch null-pointer dereferences, as the null pointer is usually represented as address zero. If we put anything in memory there, we'd be able to actually read something (even though it's wrong), so the program wouldn't crash. But we want programs to crash if they dereference a null pointer. So an empty slot at address zero is sometimes called a guard page.

  • Dog speaking

    So we're using the page table to help us catch bugs? So cool!

We can make two (other) observations about this page table:

  • It's sparse. Many of the entries are empty.
  • Pages are allocated in contiguous regions, so the page table tends to have long runs of unused entries.

Which suggests a way we could shorten our page table—and use less memory!

Notice that the page table has a blue line every eight entries, dividing the table into blocks of eight pages.

If we look at each block of pages, we can see that some blocks have pages in use (darker colors), and some do not. Since we only care about the memory that's in use, we can drop the empty blocks, which leaves us with three separate eight-slot page tables with pointers to physical memory frames.

Now we add a new eight-slot upper-level page table whose in-use slots point to the appropriate subtable, giving us a hierarchical or multi-level page table.

Lower Tables Page Frames Upper Table
A Multi-Level Page Table

(Notice that the two lower-level page tables with purple blocks are contiguous in the upper-level page table.

Sticking with this (toy) example, where we have a two-level page table where the top level has eight entries and each lower-level table has eight entries, what would be a pathological layout of memory that would break our assumptions and result in no net savings?

  • Cat speaking

    We're totally depending on having long runs of empty entries in the page table to make this work. If we don't have that, we're sunk. The worst case would be if we always had exactly seven pages between used pages.

  • Duck speaking

    But let's look at the positives. In our example, there is a nice big gap, and the top-level table still has a run of empty entries. Could we go even further?

  • PinkRobot speaking

    Yes. Remember that what we're looking at in our diagrams is much scaled down from what you'd see in a real system.

A Real Process Memory Map

Let's look at a real-world process. Here's a memory map for Neovim, a popular text editor, running on a Raspberry Pi Zero (a 32-bit system). The code and data are at the bottom of memory (top of the diagram), followed by the heap and then a huge gap, and then the stack. Afterwards (in blue), there are a number of shared libraries that Neovim uses. The top quarter of the address space is reserved for the kernel.

(Because sizes vary dramatically, the diagram is not drawn to scale—in fact, the height of each block is based on the cube root of the size of the block. If you hover over any block, it'll tell you more details about it.)

Unallocated Memory Size: 64K Range: 0-10000 /usr/bin/nvim Size: 2.4M Range: 10000-272000 Permissions: read, exec, private nvim 2.4M Unallocated Memory Size: 60K Range: 272000-281000 /usr/bin/nvim Size: 4K Range: 281000-282000 Permissions: read, private /usr/bin/nvim Size: 48K Range: 282000-28e000 Permissions: read, write, private (unnamed) Size: 56K Range: 28e000-29c000 Permissions: read, write, private Unallocated Memory Size: 20.0M Range: 29c000-1698000 Free 20.0M [heap] Size: 1.8M Range: 1698000-1868000 Permissions: read, write, private [heap] 1.8M Unallocated Memory Size: 2.8G Range: 1868000-b5e00000 Free 2.8G (unnamed) Size: 264K Range: b5e00000-b5e42000 Permissions: read, write, private (unnamed) 264K (unnamed) Size: 760K Range: b5e42000-b5f00000 Permissions: private (unnamed) 760K Unallocated Memory Size: 368K Range: b5f00000-b5f5c000 Free 368K /lib/arm-linux-gnueabihf/libnss_files-2.28.so Size: 36K Range: b5f5c000-b5f65000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libnss_files-2.28.so Size: 64K Range: b5f65000-b5f75000 Permissions: private /lib/arm-linux-gnueabihf/libnss_files-2.28.so Size: 4K Range: b5f75000-b5f76000 Permissions: read, private /lib/arm-linux-gnueabihf/libnss_files-2.28.so Size: 4K Range: b5f76000-b5f77000 Permissions: read, write, private (unnamed) Size: 24K Range: b5f77000-b5f7d000 Permissions: read, write, private (unnamed) Size: 4K Range: b5f7d000-b5f7e000 Permissions: private (unnamed) Size: 8.0M Range: b5f7e000-b677e000 Permissions: read, write, private (unnamed) 8.0M /usr/lib/locale/locale-archive Size: 2.5M Range: b677e000-b69f8000 Permissions: read, private locale-archive 2.5M /usr/lib/locale/locale-archive Size: 2.0M Range: b69f8000-b6bf8000 Permissions: read, private locale-archive 2.0M /lib/arm-linux-gnueabihf/libgcc_s.so.1 Size: 112K Range: b6bf8000-b6c14000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libgcc_s.so.1 Size: 60K Range: b6c14000-b6c23000 Permissions: private /lib/arm-linux-gnueabihf/libgcc_s.so.1 Size: 4K Range: b6c23000-b6c24000 Permissions: read, private /lib/arm-linux-gnueabihf/libgcc_s.so.1 Size: 4K Range: b6c24000-b6c25000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libc-2.28.so Size: 1.2M Range: b6c25000-b6d5d000 Permissions: read, exec, private libc-2 1.2M /lib/arm-linux-gnueabihf/libc-2.28.so Size: 60K Range: b6d5d000-b6d6c000 Permissions: private /lib/arm-linux-gnueabihf/libc-2.28.so Size: 8K Range: b6d6c000-b6d6e000 Permissions: read, private /lib/arm-linux-gnueabihf/libc-2.28.so Size: 4K Range: b6d6e000-b6d6f000 Permissions: read, write, private (unnamed) Size: 12K Range: b6d6f000-b6d72000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libluajit-5.1.so.2.1.0 Size: 400K Range: b6d72000-b6dd6000 Permissions: read, exec, private libluajit-5 400K /usr/lib/arm-linux-gnueabihf/libluajit-5.1.so.2.1.0 Size: 64K Range: b6dd6000-b6de6000 Permissions: private /usr/lib/arm-linux-gnueabihf/libluajit-5.1.so.2.1.0 Size: 4K Range: b6de6000-b6de7000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libluajit-5.1.so.2.1.0 Size: 4K Range: b6de7000-b6de8000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libutil-2.28.so Size: 8K Range: b6de8000-b6dea000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libutil-2.28.so Size: 60K Range: b6dea000-b6df9000 Permissions: private /lib/arm-linux-gnueabihf/libutil-2.28.so Size: 4K Range: b6df9000-b6dfa000 Permissions: read, private /lib/arm-linux-gnueabihf/libutil-2.28.so Size: 4K Range: b6dfa000-b6dfb000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libm-2.28.so Size: 452K Range: b6dfb000-b6e6c000 Permissions: read, exec, private libm-2 452K /lib/arm-linux-gnueabihf/libm-2.28.so Size: 60K Range: b6e6c000-b6e7b000 Permissions: private /lib/arm-linux-gnueabihf/libm-2.28.so Size: 4K Range: b6e7b000-b6e7c000 Permissions: read, private /lib/arm-linux-gnueabihf/libm-2.28.so Size: 4K Range: b6e7c000-b6e7d000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libunibilium.so.4.0.0 Size: 44K Range: b6e7d000-b6e88000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libunibilium.so.4.0.0 Size: 60K Range: b6e88000-b6e97000 Permissions: private /usr/lib/arm-linux-gnueabihf/libunibilium.so.4.0.0 Size: 8K Range: b6e97000-b6e99000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libunibilium.so.4.0.0 Size: 4K Range: b6e99000-b6e9a000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libtermkey.so.1.14.0 Size: 28K Range: b6e9a000-b6ea1000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libtermkey.so.1.14.0 Size: 60K Range: b6ea1000-b6eb0000 Permissions: private /usr/lib/arm-linux-gnueabihf/libtermkey.so.1.14.0 Size: 4K Range: b6eb0000-b6eb1000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libtermkey.so.1.14.0 Size: 4K Range: b6eb1000-b6eb2000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libvterm.so.0.0.0 Size: 44K Range: b6eb2000-b6ebd000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libvterm.so.0.0.0 Size: 64K Range: b6ebd000-b6ecd000 Permissions: private /usr/lib/arm-linux-gnueabihf/libvterm.so.0.0.0 Size: 4K Range: b6ecd000-b6ece000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libvterm.so.0.0.0 Size: 4K Range: b6ece000-b6ecf000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libmsgpackc.so.2.0.0 Size: 20K Range: b6ecf000-b6ed4000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libmsgpackc.so.2.0.0 Size: 60K Range: b6ed4000-b6ee3000 Permissions: private /usr/lib/arm-linux-gnueabihf/libmsgpackc.so.2.0.0 Size: 4K Range: b6ee3000-b6ee4000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libmsgpackc.so.2.0.0 Size: 4K Range: b6ee4000-b6ee5000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libdl-2.28.so Size: 8K Range: b6ee5000-b6ee7000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libdl-2.28.so Size: 60K Range: b6ee7000-b6ef6000 Permissions: private /lib/arm-linux-gnueabihf/libdl-2.28.so Size: 4K Range: b6ef6000-b6ef7000 Permissions: read, private /lib/arm-linux-gnueabihf/libdl-2.28.so Size: 4K Range: b6ef7000-b6ef8000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libnsl-2.28.so Size: 68K Range: b6ef8000-b6f09000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libnsl-2.28.so Size: 60K Range: b6f09000-b6f18000 Permissions: private /lib/arm-linux-gnueabihf/libnsl-2.28.so Size: 4K Range: b6f18000-b6f19000 Permissions: read, private /lib/arm-linux-gnueabihf/libnsl-2.28.so Size: 4K Range: b6f19000-b6f1a000 Permissions: read, write, private (unnamed) Size: 8K Range: b6f1a000-b6f1c000 Permissions: read, write, private /lib/arm-linux-gnueabihf/libpthread-2.28.so Size: 92K Range: b6f1c000-b6f33000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/libpthread-2.28.so Size: 60K Range: b6f33000-b6f42000 Permissions: private /lib/arm-linux-gnueabihf/libpthread-2.28.so Size: 4K Range: b6f42000-b6f43000 Permissions: read, private /lib/arm-linux-gnueabihf/libpthread-2.28.so Size: 4K Range: b6f43000-b6f44000 Permissions: read, write, private (unnamed) Size: 8K Range: b6f44000-b6f46000 Permissions: read, write, private /lib/arm-linux-gnueabihf/librt-2.28.so Size: 24K Range: b6f46000-b6f4c000 Permissions: read, exec, private /lib/arm-linux-gnueabihf/librt-2.28.so Size: 60K Range: b6f4c000-b6f5b000 Permissions: private /lib/arm-linux-gnueabihf/librt-2.28.so Size: 4K Range: b6f5b000-b6f5c000 Permissions: read, private /lib/arm-linux-gnueabihf/librt-2.28.so Size: 4K Range: b6f5c000-b6f5d000 Permissions: read, write, private /usr/lib/arm-linux-gnueabihf/libuv.so.1.0.0 Size: 132K Range: b6f5d000-b6f7e000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libuv.so.1.0.0 Size: 60K Range: b6f7e000-b6f8d000 Permissions: private /usr/lib/arm-linux-gnueabihf/libuv.so.1.0.0 Size: 4K Range: b6f8d000-b6f8e000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libuv.so.1.0.0 Size: 4K Range: b6f8e000-b6f8f000 Permissions: read, write, private Unallocated Memory Size: 104K Range: b6f8f000-b6fa9000 /usr/lib/arm-linux-gnueabihf/libarmmem-v6l.so Size: 8K Range: b6fa9000-b6fab000 Permissions: read, exec, private /usr/lib/arm-linux-gnueabihf/libarmmem-v6l.so Size: 60K Range: b6fab000-b6fba000 Permissions: private /usr/lib/arm-linux-gnueabihf/libarmmem-v6l.so Size: 4K Range: b6fba000-b6fbb000 Permissions: read, private /usr/lib/arm-linux-gnueabihf/libarmmem-v6l.so Size: 4K Range: b6fbb000-b6fbc000 Permissions: read, write, private /lib/arm-linux-gnueabihf/ld-2.28.so Size: 128K Range: b6fbc000-b6fdc000 Permissions: read, exec, private Unallocated Memory Size: 36K Range: b6fdc000-b6fe5000 (unnamed) Size: 24K Range: b6fe5000-b6feb000 Permissions: read, write, private Unallocated Memory Size: 4K Range: b6feb000-b6fec000 /lib/arm-linux-gnueabihf/ld-2.28.so Size: 4K Range: b6fec000-b6fed000 Permissions: read, private /lib/arm-linux-gnueabihf/ld-2.28.so Size: 4K Range: b6fed000-b6fee000 Permissions: read, write, private Unallocated Memory Size: 125.8M Range: b6fee000-bedb4000 Free 125.8M [stack] Size: 132K Range: bedb4000-bedd5000 Permissions: read, write, private Unallocated Memory Size: 596K Range: bedd5000-bee6a000 Free 596K [sigpage] Size: 4K Range: bee6a000-bee6b000 Permissions: read, exec, private Unallocated Memory Size: 1.6M Range: bee6b000-bf000000 Free 1.6M Kernel Modules Size: 5.8M Range: bf000000-bf5cc000 Kernel Modules 5.8M Unallocated Memory Size: 10.2M Range: bf5cc000-c0008000 Kernel Free 10.2M Kernel Space Size: 38.7M Range: c0008000-c26c4000 Kernel Space 38.7M Unallocated Memory Size: 985.2M Range: c26c4000-ffff0000 Kernel Free 985.2M [vectors] Size: 4K Range: ffff0000-ffff1000 Permissions: read, exec, private
Neovim Memory Map
  • Duck speaking

    So the kernel memory is part of the page table?

  • PinkRobot speaking

    Not while user-space code is running. The kernel has its own page table, and it's mapped into the top quarter of the address space when the kernel is running. When user-space code is running, the kernel's memory is not accessible.

  • Cat speaking

    So user programs are restricted to at most 3 GB of memory on a 32-bit system?

  • PinkRobot speaking

    On Linux, with the standard kernel configuration, yes. This arrangment makes copyin and copyout operations much simpler, as the kernel can just map the user-space memory into its own address space and access it directly. If we're willing to make copyin and copyout more complex, we could give user-space programs access to the full 4 GB of address space. FWIW, this this latter approach was used by Mac OS X on 32-bit systems.

Realistic Multi-Level Page Tables (32-bit Addressing)

On a 32-bit system, we can use the two-level approach for page tables. We have 32 bits of address with 4 KB pages, so we'll break things down as follows:

  • The low 12 bits will be the offset within the page.
  • The remaining 20 bits are the page number, but we break those 20 bits into two 10-bit chunks:
    • The top 10 bits will be the index into the top-level table.
    • The next 10 bits will be the index into the second-level table.

Thus, for example, the address 0x016980d0 (representing an address inside Neovim's heap) is encoded in binary as

  • 0000 0001 0110 1001 1000 0000 1101 0000; thus,
    • The top 10 bits, representing the upper-level page number are 0000 0001 01 or 0x005;
    • The next 10 bits, representing the lower-level page number are 10 1001 1000 or 0x298;
    • The low 12 bits are the offset within the page, 0000 1101 0000 or 0x0d0.
  • Duck speaking

    How big is one of these tables?

Each table has 1024 entries (210). If we can fit all the data we need into four bytes per entry, then each table will be 4 KB, which is also the size of a page, so very convenient. We can imagine each entry in the second-level table has

  • 24 bits for the frame number, allowing 224 frames (since a frame is 4 KB, that's 64 GB of physical memory).
  • 8 bits for flags, like whether the page is present or read-only.
  • Goat speaking

    Meh, only 64 GB of memory? Weak.

  • PinkRobot speaking

    64 GB of memory on a 32-bit system is actually pretty good. The Raspberry Pi Zero only has 512 MB of memory.

Suppose that we have a process with 128 MB of code and data at the bottom of memory, and a 1 MB stack at the top of memory. How much memory will we need to store the page tables for this process? Explain your answer.

(For simplicity, assume there is no guard page at address zero.)

Realistic Multi-Level Page Tables (64-bit Addressing)

The architecture of an x86_64 system supports 64-bit addresses, but current implementations typically use 48-bit virtual addresses (the high 16 bits must be sign-extended from bit 47). With 4 KB pages (like our 32-bit example), the low 12 bits are still the page offset, which leaves us with 36 bits for the page number, which we break into four 9-bit chunks. These chunks serve as indices into a four-level page table hierarchy, often called the PGD (Page Global Directory), PUD (Page Upper Directory), PMD (Page Middle Directory), and PTE (Page Table Entry). Each 9-bit index allows 512 entries per table, and, with 8-byte entries, each table consumes 4 KB.

For example, the address 0x00007f3144816042 (a typical location for shared library code) is encoded in binary as

  • 000000000011111110001100010010000010000000100100; thus,
    • The L4 (PGD) index bits are 000000000 or 0x000;
    • The L3 (PUD) index bits are 011111110 or 0x1FE;
    • The L2 (PMD) index bits are 001100010 or 0x062;
    • The L1 (PTE) index bits are 010000010 or 0x082;
    • The page offset bits are 000000100100 or 0x042.

Note that the high 16 bits (not shown) must be either all 0s or all 1s, matching bit 47, forming the “canonical form” required by x86_64. So we have a 256 TB virtual address space, split evenly between user and kernel space.

  • Goat speaking

    Meh. Only 256 terabytes of address space? Weak.

  • Rabbit speaking

    Actually, x86_64 has extended its virtual address space beyond 48 bits with Page Map Level 5 (PML5) tables, adding another 9-bit index to reach 57 bits.

  • Cat speaking

    Why did it drop from 10-bits per level to 9-bits?

  • PinkRobot speaking

    On the 32-bit chip, a pointer is 4 bytes, so we can fit 1024 pointers in a page. On the 64-bit chip, a pointer is 8 bytes, so we can fit 512 = 29 pointers in a page. So it makes sense to have 9-bit indices.

Aside: ARM64 Page Tables

ARM64 systems like Apple Silicon also use 48-bit virtual addressing by default (though the architecture supports 52-bit addresses with an optional feature), but handle it slightly differently from x86_64. Instead of four 9-bit fields, ARM64 uses a three-level page table by default, with a 16 KB page size splitting the address into

  • 14 bits of page offset (for 16 KB pages)
  • Three 11-bit chunks for the page table indices

For example, the address 0x0000004208016000 would break down as

  • 000001000010000010000000010110000 00000000000000; thus,
    • The L3 index bits are 00000100001 or 0x021;
    • The L2 index bits are 00000100000 or 0x020;
    • The L1 index bits are 00010110000 or 0x0B0;
    • The page offset bits are 00000000000000 or 0x0000.

Each table has 2048 entries (211), and with 8-byte entries, each table takes up 16 KB. ARM64 can be configured to use different page sizes, with the page table structure adjusting accordingly.

The ARM64 architecture also supports 4 KB pages with four-level page tables, similar to x86_64, or can use 64 KB pages with three-level page tables supporting 52-bit addresses.

Like x86_64, ARM64 requires addresses to be in canonical form, with the unused high bits matching bit 47 (or bit 51 in 52-bit mode).

(When logged in, completion status appears here.)