Memahami Virtual Memory: Bagaimana OS Mengelola Memori

Rabu, 25 Maret 2026

Setiap kali Anda menjalankan program, sistem operasi memberikan ilusi bahwa program tersebut memiliki seluruh ruang alamat memori untuk dirinya sendiri. Ini adalah virtual memory1 — salah satu abstraksi paling penting dalam ilmu komputer modern.

Artikel ini akan membawa Anda memahami bagaimana virtual memory bekerja, dari konsep dasar hingga implementasi hardware di arsitektur x86-64.

Mengapa Virtual Memory?

Pada awal komputasi, program mengakses memori fisik secara langsung. Ini menimbulkan tiga masalah fundamental:

  • Isolasi — Satu program bisa membaca/menulis memori program lain

  • Fragmentasi — Memori fisik menjadi terpecah seiring program dimuat dan dihapus

  • Kapasitas — Program dibatasi oleh jumlah RAM fisik yang tersedia

Virtual memory menyelesaikan ketiga masalah ini dengan menambahkan lapisan indirection antara alamat yang dilihat program (virtual address) dan alamat sebenarnya di RAM (physical address)2.

AI Prompt

Jelaskan konsep virtual memory seolah-olah saya adalah seorang junior developer. Gunakan analogi perpustakaan: buku adalah data, rak adalah RAM fisik, dan katalog kartu adalah page table. Jelaskan mengapa kita butuh katalog alih-alih langsung pergi ke rak.

Page Table dan Address Translation

Memori virtual dibagi menjadi blok berukuran tetap yang disebut page (biasanya 4 KB). Setiap page virtual dipetakan ke frame di memori fisik melalui struktur data yang disebut page table.

Proses translasi alamat:

  1. CPU menghasilkan virtual address
  2. Virtual address dipecah menjadi page number dan offset
  3. Page number digunakan untuk mencari frame number di page table
  4. Physical address = frame number + offset
Note

Pada arsitektur x86-64, virtual address adalah 48-bit (256 TB address space), meskipun register pointer adalah 64-bit. Bit 48-63 harus merupakan sign extension dari bit 47 — inilah yang disebut canonical address3.

Multi-Level Page Table

Dengan 48-bit address space dan 4 KB page, kita membutuhkan 2362^{36} entri page table — sekitar 512 GB hanya untuk page table! Solusinya adalah multi-level page table4:

C
// x86-64 menggunakan 4-level page table
typedef struct {
    uint64_t entries[512];  // 512 entri × 8 byte = 4 KB per table
} PageTable;

// Virtual address breakdown (48-bit):
// [47:39] PML4 index   (9 bit → 512 entries)
// [38:30] PDPT index   (9 bit → 512 entries)
// [29:21] PD index     (9 bit → 512 entries)
// [20:12] PT index     (9 bit → 512 entries)
// [11:0]  Page offset  (12 bit → 4096 bytes)
AI Prompt

Saya ingin memahami multi-level page table di x86-64. Tolong jelaskan langkah demi langkah bagaimana CPU menerjemahkan virtual address 0x00007FFE12345678 menjadi physical address. Tunjukkan bagaimana setiap level (PML4, PDPT, PD, PT) di-index dan bagaimana hasilnya digabungkan.

Translation Lookaside Buffer (TLB)

Dengan 4 level page table, setiap akses memori membutuhkan 4 tambahan akses memori untuk walk page table. Ini jelas tidak praktis. Solusinya adalah TLB — cache khusus di dalam CPU yang menyimpan translasi alamat yang baru digunakan5.

C
// Pseudocode TLB lookup
PhysicalAddress translate(VirtualAddress vaddr) {
    // Check TLB first — O(1), parallel CAM lookup
    TLBEntry *entry = tlb_lookup(vaddr.page_number);
    if (entry && entry->valid) {
        // TLB hit — langsung dapat physical frame
        return (entry->frame_number << 12) | vaddr.offset;
    }

    // TLB miss — harus walk page table (mahal!)
    uint64_t frame = page_table_walk(vaddr);
    tlb_insert(vaddr.page_number, frame);
    return (frame << 12) | vaddr.offset;
}

TLB hit rate yang tinggi (>99%) sangat kritis untuk performa. Pada CPU modern:

KomponenKapasitasLatency
L1 TLB (instruksi)64-128 entries~1 cycle
L1 TLB (data)64-72 entries~1 cycle
L2 TLB (unified)1024-2048 entries~7 cycles
Full page walk~100+ cycles
Warning

TLB flush terjadi setiap kali Anda melakukan context switch antar proses (karena setiap proses memiliki page table sendiri). Ini adalah salah satu alasan context switch itu mahal — bukan hanya menyimpan/memuat register, tapi juga "memanaskan" ulang TLB6.

AI Prompt

Bandingkan performa TLB pada arsitektur x86-64 vs ARM (AArch64). Jelaskan bagaimana ARM menggunakan ASID (Address Space ID) untuk menghindari TLB flush saat context switch, dan bagaimana x86 menggunakan PCID (Process Context ID) untuk tujuan yang sama. Berikan contoh skenario di mana ASID/PCID memberikan peningkatan performa yang signifikan.

Page Fault dan Demand Paging

Ketika program mengakses page yang tidak ada di RAM (bit present = 0 di page table entry), CPU menghasilkan page fault. OS kemudian:

  1. Menentukan apakah akses valid (cek VMA — Virtual Memory Area)
  2. Jika valid, cari frame kosong di RAM
  3. Jika RAM penuh, pilih victim page menggunakan algoritma penggantian7
  4. Muat page dari disk (swap) ke frame yang dipilih
  5. Update page table entry
  6. Restart instruksi yang menyebabkan fault
Tip

Gunakan perf stat -e page-faults pada Linux untuk memantau jumlah page fault program Anda. Minor fault (page sudah di memory tapi belum di-map) biasanya tidak masalah, tapi major fault (harus baca dari disk) bisa memperlambat program secara drastis.

Huge Pages

Page 4 KB standar memiliki kelemahan — banyak TLB entries diperlukan untuk mengcover working set yang besar. Huge pages (2 MB atau 1 GB) mengurangi tekanan pada TLB secara signifikan:

C
// Linux: alokasi huge page
#include <sys/mman.h>

void *buf = mmap(NULL, 2 * 1024 * 1024,
    PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
    -1, 0);

Dengan huge page 2 MB, satu TLB entry mencakup 512× lebih banyak memori dibandingkan page 4 KB biasa. Untuk aplikasi database atau scientific computing yang mengakses memori dalam pola besar, ini bisa meningkatkan performa 10-30%.

AI Prompt

Jelaskan trade-off penggunaan huge pages (2MB dan 1GB) vs standard 4KB pages. Kapan huge pages memberikan benefit, kapan justru merugikan? Sertakan contoh konkret dari database (PostgreSQL shared buffers) dan JVM (Java large heap) yang menggunakan huge pages.

Penutup

Virtual memory adalah salah satu fondasi yang memungkinkan komputer modern berjalan — dari smartphone hingga server. Memahami bagaimana page table, TLB, dan page fault bekerja membantu Anda menulis program yang lebih efisien dan men-debug masalah performa yang tidak terlihat di level aplikasi.

Setiap pointer yang Anda gunakan adalah alamat virtual. Di balik setiap dereference, ada mesin translasi yang bekerja dalam satu clock cycle — begitu cepatnya hingga Anda lupa bahwa memori fisik sebenarnya ada di tempat lain.

Footnotes

  1. Virtual memory pertama kali diimplementasikan pada komputer Atlas di University of Manchester pada tahun 1962, dirancang oleh Tom Kilburn dan timnya.

  2. Konsep indirection ini sesuai dengan prinsip fundamental dalam ilmu komputer yang dikemukakan David Wheeler: "All problems in computer science can be solved by another level of indirection."

  3. Canonical address memastikan bahwa address space dibagi menjadi dua bagian: lower half (0x0000000000000000 - 0x00007FFFFFFFFFFF) untuk userspace dan upper half (0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF) untuk kernel.

  4. Intel memperkenalkan 5-level page table (LA57) yang memperluas virtual address space ke 57-bit (128 PB). Ini digunakan pada server dengan RAM sangat besar.

  5. TLB menggunakan Content-Addressable Memory (CAM) yang memungkinkan pencarian paralel di semua entri secara bersamaan — berbeda dengan cache biasa yang menggunakan indexing.

  6. Mitigasi Spectre/Meltdown memperburuk situasi ini karena memerlukan pemisahan page table kernel dari userspace (KPTI/KAISER), yang menyebabkan TLB flush tambahan pada setiap transisi user↔kernel.

  7. Algoritma penggantian page yang umum: LRU (Least Recently Used), Clock (approximasi LRU), dan pada Linux modern menggunakan multi-generational LRU (MGLRU) yang diperkenalkan di kernel 6.1.