Referensi interaktif: PGD → P4D → PUD → PMD → PTE → Physical page. Kernel vs userspace mapping, VA decoder, PTE flags.
Base dari CR3 register. Entry berisi physical address P4D (atau PUD di 4-level) + flags.
pgd_t *pgd = pgd_offset(mm, addr)
swapper_pg_dir. Entry 0-255 = user space, unik per proses. pgd_offset(mm, addr) = mm->pgd + pgd_index(addr). Saat fork(), kernel entries di-copy dari init_mm.pgd, user entries awalnya kosong (demand paging). CR3 reload saat context switch → TLB flush kecuali PCID match.
Di CPU tanpa LA57, level ini di-fold: pgtable-nop4d.h membuat p4d_offset() = pgd. Di kernel 6.x dengan CONFIG_X86_5LEVEL, ini jadi level nyata.
p4d_t *p4d = p4d_offset(pgd, addr)
CONFIG_X86_5LEVEL=y. CPU harus support CPUID leaf 0x7 bit ECX[16] (LA57). Saat kernel boot deteksi LA57, cr4.la57 di-set dan virtual address space naik ke 57 bit (128 PiB per half). Perlu Linux ≥ 4.17 dan glibc ≥ 2.28. Tanpa ini, p4d_offset() simply return argument pgd-nya = zero overhead.
Jika bit PS=1 di PUD entry → 1 GB huge page, walk berhenti di sini. Dipakai kernel direct map region (physmap). pud_huge() cek ini.
pud_t *pud = pud_offset(p4d, addr)
pud_trans_huge(*pud). Untuk userspace, 1 GB huge pages jarang (perlu MAP_HUGETLB + explicit size hint).
2 MB THP (Transparent HugePage) bekerja di level ini — kernel auto-promote 512 contiguous 4K pages. pmd_trans_huge() cek ini. Split via __split_huge_pmd().
pmd_t *pmd = pmd_offset(pud, addr)
khugepaged daemon. Keuntungan: drastis kurangi TLB pressure. Masalah: fragment internal (allocate 2MB, pakai 4KB = 2044KB terbuang). Control via /sys/kernel/mm/transparent_hugepage/enabled. Kernel pakai pmd_devmap() untuk DAX (Device-DAX) yang juga pakai PMD leaf.
Leaf node untuk 4KB page. Bit 63 = NX. Bit 2 = US (user/supervisor). Physical address = PFN × 4096.
pte_t *pte = pte_offset_map(pmd, addr)
pte_to_swp_entry(). Saat swap in, do_swap_page() restore PTE dengan physical frame baru. Juga: bit P=0 dengan bit khusus = "migration entry" (page sedang di-migrate antar NUMA node).
MMU hardware lakukan walk ini secara otomatis dari TLB miss. Kernel manual walk via walk_page_table() / follow_pte() / follow_pfn().
PA = (pte_pfn(*pte) << PAGE_SHIFT) | (addr & ~PAGE_MASK)
struct page di kernel (8-64 byte per frame). Akses via pfn_to_page(pfn). Di 5-level paging dengan RAM besar, struct page array sendiri bisa puluhan GB — kernel pakai memory sections dan sparse memory model (CONFIG_SPARSEMEM_VMEMMAP) untuk efisiensi. page_address(page) return virtual address di physmap region.
PGD → PTE. P4D, PUD, PMD di-fold. 32-bit compat mode, jarang di x86_64 kernel 6.x modern.
PGD → PUD → PMD → PTE. P4D folded. VA space: 256 TiB user + 256 TiB kernel. Semua distro x86_64 modern.
PGD → P4D → PUD → PMD → PTE. CONFIG_X86_5LEVEL=y. VA space: 128 PiB. CPU harus support CPUID LA57. Linux ≥ 4.17.
0x0000000000000000 … 0x00007fffffffffff = userspace canonical0xffff800000000000 … 0xffffffffffffffff = kernel canonical0x0000800000000000 … 0xffff7fffffffffff = NON-CANONICAL — #GP fault0x…00007fffffffffff → 0x…ff8000000000000 (128 PiB).
Linux x86_64 virtual address space layout (kernel 6.x, 4-level paging). Klik region untuk detail. Warna gelap = kernel space (ring 0).
| Bit(s) | Nama | Kernel macro | Fungsi | Scope |
|---|
Data page — writable, accessible dari ring 3, tidak executable (NX=1). Awalnya RW=0 sampai write (demand paging), lalu set dirty.
Code page — read-only (RW=0), executable (NX=0), user accessible. Shared antar fork via copy-on-write.
RW=0 (read-only post init via mark_rodata_ro), NX=0 (executable code), US=0 (kernel-only). Global=1 agar TLB entry di-share semua CPU.
Writable data, tidak executable (NX=1), kernel-only (US=0). Per-CPU data: virtual address sama, physical frame berbeda per CPU.
Saat child atau parent write → #PF → do_wp_page() → alokasi frame baru → copy content → set RW=1 di PTE baru. VMA tetap WRITE tapi PTE RW=0.
Proteksi ini enforce di hardware level, bukan di PTE. Kernel bypass SMAP secara eksplisit via copy_from_user() / copy_to_user() yang wrap stac/clac instructions.
Ring 3 pakai shadow PGD yang hanya map kernel trampoline + IDT. Saat syscall/interrupt → switch ke full PGD. Overhead: ~1-3% pada I/O-heavy workload. Bisa disable via nopti boot param jika CPU immune (KAISER flag).