Bagaimana Program Mengakses RAM

1. RAM Fisik (Physical Memory)

RAM 8GB yang kamu lihat itu adalah memori fisik yang terpasang di motherboard. Ini adalah chip hardware yang menyimpan data dalam bentuk bit (0 dan 1).

  • Alamat fisik: 0x00000000 sampai 0x1FFFFFFFF (untuk 8GB)

  • Setiap byte punya alamat unik

  • TAPI, program kamu TIDAK pernah akses RAM fisik secara langsung! Ini penting!

2. Virtual Memory (Memori Virtual)

Setiap program/proses mendapat ilusi bahwa mereka punya seluruh memori untuk diri mereka sendiri. Ini disebut virtual memory.

Kenapa perlu virtual memory?

  • Isolasi: Program A tidak bisa akses memori Program B (keamanan)

  • Simplifikasi: Program tidak perlu tahu di mana posisi fisik RAM-nya

  • Overcommitment: Total virtual memory semua program bisa lebih dari 8GB fisik

3. MMU (Memory Management Unit)

MMU adalah chip hardware (bagian dari CPU) yang mentranslasi alamat virtual ke alamat fisik.

Cara kerja:

Program: "Saya mau akses alamat 0x400000"
         ↓
MMU: "OK, alamat virtual 0x400000 itu sebenarnya ada di RAM fisik 0x8A3F000"
         ↓
RAM: Memberikan data dari alamat fisik tersebut

Page Table

MMU menggunakan Page Table (tabel halaman) untuk mapping:

  • Memori dibagi jadi chunk bernama pages (biasanya 4KB)

  • Page Table menyimpan mapping: Virtual Page β†’ Physical Page

  • Disimpan di RAM, dikelola oleh OS kernel

4. Peran Kernel/OS

Kernel Linux/Windows adalah "manager" dari semua memori. Tugasnya:

a) Memory Allocation

Ketika program minta memori (malloc/new), kernel:

  1. Cari physical page yang kosong

  2. Update page table proses tersebut

  3. Return alamat virtual ke program

b) Page Fault Handling

Kalau program akses alamat yang belum dimapping:

1. CPU: "Eh, page ini belum ada di page table!"
2. CPU: *trigger page fault exception*
3. Kernel: *terbangun*
4. Kernel: "OK, saya siapkan physical page-nya"
5. Kernel: "Update page table"
6. Kernel: "Lanjutkan program"

c) Memory Protection

Setiap page punya permission (read/write/execute). Kalau program coba akses yang tidak diizinkan β†’ segmentation fault!

5. SWAP (Swap Space)

Ketika RAM fisik penuh, kernel bisa "mengusir" page yang jarang dipakai ke disk (swap).

Proses:

1. RAM penuh (8GB terpakai semua)
2. Program baru minta memory
3. Kernel: "Hmm, saya swap out page yang lama tidak dipakai"
4. Kernel: Copy page dari RAM ke disk (swap file/partition)
5. Kernel: Tandai page table bahwa page itu "swapped out"
6. Kernel: Berikan physical page yang dikosongkan ke program baru

Kalau program akses page yang di-swap:

1. CPU akses page β†’ page fault
2. Kernel: "Oh, page ini di swap"
3. Kernel: Baca dari disk ke RAM (swap in)
4. Kernel: Mungkin swap out page lain kalau RAM masih penuh
5. Program lanjut

Swap itu LAMBAT karena disk jauh lebih lambat dari RAM (HDD: ~100 MB/s vs RAM: ~25 GB/s)

6. mmap (Memory Mapped Files)

mmap adalah syscall yang "memetakan" file ke memory address space program.

Contoh:

// Buka file 1GB
file, _ := os.Open("bigdata.txt")

// mmap: peta file ke memory
data, _ := syscall.Mmap(int(file.Fd()), 0, size, 
                         syscall.PROT_READ, syscall.MAP_SHARED)

// Akses seperti array biasa!
fmt.Println(data[1000]) // Baca byte ke-1000 dari file

Yang terjadi di belakang layar:

1. mmap() tidak langsung load 1GB ke RAM!
2. Hanya setup page table: "alamat virtual X = file offset Y"
3. Saat program akses data[1000] β†’ page fault
4. Kernel: "Oh ini mmap-ed file, baca dari disk"
5. Kernel: Load 4KB (1 page) dari file ke RAM
6. Program lanjut

Keuntungan mmap:

  • Efficient: hanya load yang diakses

  • Shared memory: beberapa proses bisa mmap file yang sama

  • OS bisa cache-kan di page cache

7. Go Runtime Memory Management

Go punya memory allocator sendiri di atas syscall OS:

a) Memory Arena

Go runtime minta memory dari OS dalam chunk besar (biasanya beberapa MB):

Go Runtime: "Hey OS, kasih saya 64MB" β†’ OS: berikan virtual memory

b) Go Allocator

Runtime Go punya allocator yang efisien:

  • Small objects (<32KB): dialokasikan dari memory pool per-size

  • Large objects (β‰₯32KB): langsung dari OS

  • Stack: setiap goroutine punya stack (mulai 2KB, bisa grow)

c) Garbage Collector

GC Go secara periodik:

  1. Scan semua pointer yang masih dipakai

  2. Tandai object yang masih "hidup"

  3. Bebaskan memory object yang mati

  4. Tidak langsung return ke OS, disimpan di free list untuk reuse

8. Dari Kode Go ke RAM

Mari trace alur lengkap dari kode Go:

package main

func main() {
    // Alokasi slice
    data := make([]byte, 1024*1024) // 1MB
    data[0] = 42
}

Yang terjadi:

STEP 1: Compile Time
β”œβ”€ Compiler Go: compile ke binary
└─ Binary berisi instruksi mesin untuk CPU

STEP 2: Program Dimulai
β”œβ”€ OS: Load binary ke memory
β”œβ”€ OS: Buat process baru
β”œβ”€ OS: Setup page table kosong untuk process
β”œβ”€ OS: Setup virtual address space:
β”‚      β”œβ”€ 0x00000000-0x00400000: text (kode program)
β”‚      β”œβ”€ 0x00400000-0x00600000: data (global variables)
β”‚      β”œβ”€ 0x00600000-0xC0000000: heap (dynamic allocation)
β”‚      └─ 0xC0000000-0xFFFFFFFF: stack
└─ Go Runtime: Inisialisasi (setup allocator, GC, scheduler)

STEP 3: make([]byte, 1MB)
β”œβ”€ Go Runtime: "Butuh 1MB"
β”œβ”€ Go Runtime: Cek memory pool β†’ tidak ada yang cukup
β”œβ”€ Go Runtime: syscall mmap/brk untuk minta memory dari OS
β”‚      β”œβ”€ Syscall: trap ke kernel mode
β”‚      β”œβ”€ Kernel: "OK, saya reservasi 1MB virtual memory"
β”‚      β”œβ”€ Kernel: Update page table (masih belum allocate physical!)
β”‚      └─ Kernel: Return alamat virtual 0x12340000 ke program
β”œβ”€ Go Runtime: Setup slice header:
β”‚      β”œβ”€ pointer: 0x12340000
β”‚      β”œβ”€ length: 1048576
β”‚      └─ capacity: 1048576
└─ Return slice ke kode kamu

STEP 4: data[0] = 42
β”œβ”€ CPU: Eksekusi instruksi "MOV [0x12340000], 42"
β”œβ”€ MMU: Translate 0x12340000 β†’ cek page table
β”œβ”€ MMU: "Eh, page ini belum ada physical mapping!"
β”œβ”€ CPU: Trigger PAGE FAULT (interrupt)
β”œβ”€ Kernel: *handle page fault*
β”‚      β”œβ”€ Cari free physical page (misal: 0x8A3F000)
β”‚      β”œβ”€ Update page table: 0x12340000 β†’ 0x8A3F000
β”‚      └─ Set permission: read/write
β”œβ”€ CPU: Retry instruksi
β”œβ”€ MMU: Translate 0x12340000 β†’ 0x8A3F000 (berhasil!)
└─ RAM: Write value 42 ke alamat fisik 0x8A3F000

9. Visualisasi Lengkap

Sempurna! Saya sudah melengkapi visualisasi interaktifnya. Sekarang mari saya tambahkan beberapa penjelasan tambahan yang penting:

10. Copy-on-Write (COW)

Ada mekanisme penting lain yang perlu kamu tahu:

Ketika proses fork (duplikasi proses), tidak semua memory langsung di-copy! Kernel menggunakan Copy-on-Write:

1. Process A fork β†’ Process B
2. Kernel: Copy page table, tapi physical pages SAMA
3. Kernel: Tandai semua pages sebagai "read-only"
4. Process B coba WRITE β†’ page fault!
5. Kernel: "Oh COW page, saya copy sekarang"
6. Kernel: Buat physical page baru, copy data, update page table B
7. Process B lanjut dengan page yang baru

Ini menghemat memory dan waktu saat fork!

11. Huge Pages / Large Pages

Normalnya page size = 4KB, tapi modern CPU support huge pages:

  • x86-64: 2MB atau 1GB pages

  • Keuntungan: Lebih sedikit entries di page table, lebih sedikit TLB miss

  • Go bisa pakai: Transparent Huge Pages (THP) di Linux

12. NUMA (Non-Uniform Memory Access)

Di server dengan multiple CPU socket, RAM tidak "seragam":

[CPU 0] ←fastβ†’ [RAM Bank 0]
   ↓ slow
[RAM Bank 1] ←fastβ†’ [CPU 1]

Akses RAM "lokal" lebih cepat dari RAM "remote". Kernel coba allocate memory dari RAM bank terdekat ke CPU yang eksekusi process.

13. Kenapa Perlu Virtual Memory?

Tanpa virtual memory, masalah besar:

  1. Fragmentasi: Program perlu continuous physical memory (susah!)

  2. Security: Program bisa akses memory program lain (bahaya!)

  3. Relocation: Program harus load di alamat fisik yang sama setiap run

  4. Limited address space: Terbatas physical RAM saja

Dengan virtual memory, semua masalah solved!

Last updated