Dalam pengembangan sistem operasi bare metal pada Raspberry Pi 1 (SoC Broadcom BCM2835), pemahaman mendalam mengenai peta memori (memory map) adalah fondasi utama. Tanpa adanya sistem operasi perantara seperti Linux, pengembang bertanggung jawab penuh untuk mengelola tata letak instruksi dan data di dalam RAM.
Artikel ini akan membedah struktur memori Raspberry Pi 1 secara rinci, mulai dari alamat paling dasar hingga area kontrol perangkat keras, dilengkapi dengan contoh implementasi praktis.
Representasi Visual Memory Map
Berikut adalah struktur pemetaan memori pada Raspberry Pi 1 saat proses booting berlangsung:
Alamat Memori Struktur / Bagian Keterangan
0x00000000 ┬───────────────────┐
│ ARM Vector Table │ (Interupsi & Exception)
│ ATAGS / Dev Tree │ (Informasi Boot dari Firmware)
│ Stack (Grows Down)│ ← Biasanya SP diatur ke 0x8000
0x00008000 ┼───────────────────┤ ← Kernel Entry (Titik Mulai .text)
│ │
│ .text │ (Kode Program / Instruksi)
│ │
├───────────────────┤
│ .data │ (Variabel Global dengan Nilai)
├───────────────────┤
│ .bss │ (Variabel Global Kosong/Nol)
├───────────────────┤
│ │
│ Free RAM │ (Tempat untuk Heap / Alokasi Dinamis)
│ │
0x20000000 ┴───────────────────┘ ← Peripheral Base Address (I/O)
│ Registers (GPIO) │ (Memory Mapped I/O)
│ Registers (UART) │
└───────────────────┘
Analisis Bagian Memori
1. Area Low Memory (0x00000000 – 0x00007FFF)
Area ini merupakan wilayah yang sangat krusial pada saat inisialisasi awal.
ARM Vector Table (0x00000000)
Terletak tepat di alamat 0x00000000, bagian ini berisi tabel instruksi yang akan dieksekusi oleh prosesor ketika terjadi exception. Struktur standar vector table adalah:
0x00000000: Reset Handler
0x00000004: Undefined Instruction
0x00000008: Software Interrupt (SWI/SVC)
0x0000000C: Prefetch Abort
0x00000010: Data Abort
0x00000014: Reserved
0x00000018: IRQ (Interrupt Request)
0x0000001C: FIQ (Fast Interrupt Request)
Setiap entry berisi instruksi branch (B atau LDR PC) yang mengarahkan eksekusi ke handler yang sesuai.
ATAGS / Device Tree (0x00000100)
Firmware Raspberry Pi (GPU) meletakkan informasi sistem di alamat 0x00000100. Struktur ATAGS berisi:
- ATAG_CORE: Tag pembuka yang menandai awal struktur
- ATAG_MEM: Informasi ukuran dan lokasi RAM fisik
- ATAG_CMDLINE: Parameter command line dari
cmdline.txt - ATAG_NONE: Tag penutup
Informasi ini sangat berguna bagi kernel untuk mengetahui spesifikasi perangkat keras yang sedang berjalan.
Stack Space
Dalam arsitektur ARM, stack tumbuh ke arah alamat yang lebih rendah (grows downward). Secara konvensi, pengembang sering mengatur Stack Pointer (SP) di alamat 0x8000. Hal ini memastikan stack memiliki ruang kosong yang cukup (32KB) di bawah kernel tanpa risiko menimpa kode program.
Pertimbangan Penting:
- Stack minimal 16KB untuk operasi dasar
- Nested function calls dan interrupt handlers memerlukan stack lebih besar
- Overflow stack dapat menimpa vector table dan menyebabkan crash fatal
2. Kernel Space (0x00008000 – Akhir BSS)
Alamat 0x00008000 adalah standar industri untuk memuat kernel pada Raspberry Pi 1.
.text (Code Section)
Berisi instruksi mesin yang dapat dieksekusi. Struktur umum:
- .text.boot: Kode assembly bootstrap pertama yang dieksekusi
- .text: Fungsi-fungsi C/C++ hasil kompilasi
- .rodata: Data read-only seperti string constants
Best Practice:
.section ".text.boot"
.globl _start
_start:
// Setup stack pointer
ldr sp, =0x8000
// Clear BSS section
ldr r0, =__bss_start
ldr r1, =__bss_end
mov r2, #0
bss_loop:
cmp r0, r1
bge bss_done
str r2, [r0], #4
b bss_loop
bss_done:
// Jump to kernel main
bl kernel_main
halt:
wfe
b halt
.data (Data Section)
Menyimpan variabel global atau statis yang sudah memiliki nilai awal. Data ini disalin dari file image kernel ke RAM saat boot.
Contoh:
int initialized_global = 42; // Masuk ke .data
const char* message = "Hello"; // Pointer di .data, string di .rodata
.bss (Block Started by Symbol)
Wilayah untuk variabel global yang belum diinisialisasi atau bernilai nol. Sesuai standar C, area ini harus dibersihkan (diisi nilai nol) oleh kode boot sebelum fungsi utama (main) dijalankan.
Contoh:
int uninitialized_global; // Masuk ke .bss
static char buffer[1024]; // Masuk ke .bss
Catatan Penting: File binary kernel tidak menyimpan data .bss (hanya informasi ukuran), sehingga mengurangi ukuran file kernel.img.
3. Free RAM (Heap)
Area yang terletak di atas bagian .bss hingga mencapai batas peripheral (maksimal ~512MB pada RPi 1 Model B+) disebut sebagai Free RAM. Area ini digunakan untuk:
- Heap allocation: Implementasi
malloc()danfree() - Dynamic data structures: Linked lists, trees, hash tables
- User space memory: Jika mengimplementasikan multi-tasking
- Buffer management: Frame buffers, I/O buffers
Implementasi Sederhana Memory Allocator:
static char heap_space[1024 * 1024]; // 1MB heap
static size_t heap_offset = 0;
void* simple_malloc(size_t size) {
if (heap_offset + size > sizeof(heap_space)) {
return NULL; // Out of memory
}
void* ptr = &heap_space[heap_offset];
heap_offset += size;
return ptr;
}
4. Peripheral Base Address (0x20000000 ke atas)
Pada SoC BCM2835, kontrol perangkat keras dilakukan melalui Memory Mapped I/O (MMIO).
Peta Peripheral Penting
0x20000000: Peripheral Base
0x20003000: System Timer
0x2000B000: Interrupt Controller
0x20100000: Power Management
0x20200000: GPIO (General Purpose I/O)
0x20201000: UART0 (Serial Communication)
0x20215000: SPI0
0x20300000: External Mass Media Controller (SD Card)
0x20980000: USB Controller
Contoh Akses GPIO
#define GPIO_BASE 0x20200000
#define GPFSEL1 ((volatile unsigned int*)(GPIO_BASE + 0x04))
#define GPSET0 ((volatile unsigned int*)(GPIO_BASE + 0x1C))
#define GPCLR0 ((volatile unsigned int*)(GPIO_BASE + 0x28))
// Set GPIO16 sebagai output (ACT LED pada RPi 1)
void led_init(void) {
unsigned int selector = *GPFSEL1;
selector &= ~(7 << 18); // Clear GPIO16 function
selector |= (1 << 18); // Set sebagai output
*GPFSEL1 = selector;
}
// Nyalakan LED
void led_on(void) {
*GPCLR0 = (1 << 16); // Clear GPIO16 (LED aktif low)
}
// Matikan LED
void led_off(void) {
*GPSET0 = (1 << 16); // Set GPIO16
}
Linker Script: Menghubungkan Teori dengan Implementasi
Linker script (.ld) adalah file konfigurasi yang memberitahu linker bagaimana mengatur section-section dalam file output.
Contoh Linker Script Lengkap
/* kernel.ld - Linker script untuk Raspberry Pi 1 bare metal */
ENTRY(_start)
SECTIONS
{
/* Kernel dimulai di 0x8000 */
. = 0x8000;
__start = .;
/* Boot code harus di paling awal */
.text.boot : {
*(.text.boot)
}
/* Kode program */
.text : {
*(.text)
*(.text.*)
}
/* Data read-only */
.rodata : {
*(.rodata)
*(.rodata.*)
}
/* Data dengan nilai awal */
.data : {
*(.data)
*(.data.*)
}
/* BSS - harus di-zero oleh boot code */
.bss : {
__bss_start = .;
*(.bss)
*(.bss.*)
*(COMMON)
__bss_end = .;
}
__end = .;
/* Heap dimulai setelah BSS */
__heap_start = .;
}
Penggunaan Symbol dalam Kode
Symbol yang didefinisikan dalam linker script dapat diakses dari kode C:
extern unsigned int __bss_start;
extern unsigned int __bss_end;
extern unsigned int __heap_start;
void clear_bss(void) {
unsigned int *bss = &__bss_start;
unsigned int *bss_end = &__bss_end;
while (bss < bss_end) {
*bss++ = 0;
}
}
Cache dan Memory Barriers
Raspberry Pi 1 memiliki instruction cache dan data cache yang dapat mempengaruhi perilaku memory access.
Pertimbangan Cache
- Write Buffer: CPU ARM memiliki write buffer yang dapat menunda write operations
- Cache Coherency: Penting saat mengakses peripheral registers
- Memory Barriers: Diperlukan untuk memastikan urutan operasi
Implementasi Memory Barrier
// Data Memory Barrier
static inline void dmb(void) {
asm volatile ("mcr p15, 0, %0, c7, c10, 5" : : "r" (0) : "memory");
}
// Data Synchronization Barrier
static inline void dsb(void) {
asm volatile ("mcr p15, 0, %0, c7, c10, 4" : : "r" (0) : "memory");
}
// Instruction Synchronization Barrier
static inline void isb(void) {
asm volatile ("mcr p15, 0, %0, c7, c5, 4" : : "r" (0) : "memory");
}
// Contoh penggunaan saat akses MMIO
void mmio_write(uint32_t reg, uint32_t data) {
dmb(); // Pastikan write sebelumnya selesai
*(volatile uint32_t*)reg = data;
dsb(); // Tunggu write selesai
}
Virtual Memory dan MMU
Meskipun pengembangan bare metal sering dimulai tanpa virtual memory, Raspberry Pi 1 memiliki Memory Management Unit (MMU) yang dapat dimanfaatkan.
Keuntungan Mengaktifkan MMU
- Memory Protection: Isolasi antar process/task
- Cache Control: Enable/disable cache per region
- Address Translation: Flexibility dalam memory layout
- Access Permissions: Read-only, read-write, execute
Setup Dasar MMU
#define MMU_SECTION (1 << 1)
#define MMU_CACHEABLE (1 << 2)
#define MMU_BUFFERABLE (1 << 3)
#define MMU_AP_FULL (3 << 10)
// Translation table (harus aligned 16KB)
__attribute__((aligned(16384)))
static uint32_t page_table[4096];
void setup_mmu(void) {
// Identity map RAM (0x00000000 - 0x20000000)
for (int i = 0; i < 512; i++) {
page_table[i] = (i << 20) |
MMU_SECTION |
MMU_CACHEABLE |
MMU_BUFFERABLE |
MMU_AP_FULL;
}
// Map peripherals (0x20000000 - 0x21000000) as device memory
for (int i = 512; i < 528; i++) {
page_table[i] = (i << 20) |
MMU_SECTION |
MMU_AP_FULL;
}
// Set translation table base
asm volatile ("mcr p15, 0, %0, c2, c0, 0" : : "r" (page_table));
// Set domain access (all domains = client)
asm volatile ("mcr p15, 0, %0, c3, c0, 0" : : "r" (0xFFFFFFFF));
// Enable MMU
uint32_t control;
asm volatile ("mrc p15, 0, %0, c1, c0, 0" : "=r" (control));
control |= 0x1; // Enable MMU bit
control |= 0x4; // Enable data cache
control |= 0x1000; // Enable instruction cache
asm volatile ("mcr p15, 0, %0, c1, c0, 0" : : "r" (control));
}
Debugging dan Troubleshooting
Common Memory-Related Issues
- Stack Overflow
- Gejala: Random crashes, corrupted data
- Solusi: Tingkatkan ukuran stack, kurangi recursive calls
- Debug: Isi stack dengan pattern (0xDEADBEEF) dan cek boundary
- BSS Tidak Ter-clear
- Gejala: Global variables memiliki nilai random
- Solusi: Pastikan boot code clear BSS sebelum call main
- MMIO Access Tanpa Volatile
- Gejala: Register writes hilang karena compiler optimization
- Solusi: Selalu gunakan
volatileuntuk MMIO pointers
- Unaligned Access
- Gejala: Data abort exception
- Solusi: Gunakan
__attribute__((aligned(4)))atau access byte-by-byte
Tool untuk Debugging
// Simple UART printf untuk debugging
void uart_puts(const char* str) {
while (*str) {
// Tunggu UART siap
while (*(volatile uint32_t*)(0x20201018) & (1 << 5)) {}
// Tulis karakter
*(volatile uint32_t*)(0x20201000) = *str++;
}
}
// Dump memory region
void dump_memory(uint32_t* addr, int count) {
uart_puts("Memory dump at 0x");
uart_put_hex((uint32_t)addr);
uart_puts(":\n");
for (int i = 0; i < count; i++) {
if (i % 4 == 0) {
uart_put_hex((uint32_t)(addr + i));
uart_puts(": ");
}
uart_put_hex(addr[i]);
uart_puts(" ");
if (i % 4 == 3) uart_puts("\n");
}
}
Performance Optimization
Cache Optimization
// Align data structures ke cache line (32 bytes pada ARM11)
struct __attribute__((aligned(32))) CacheOptimized {
uint32_t frequently_accessed[8];
// ... data lainnya
};
// Prefetch data untuk mengurangi cache miss
static inline void prefetch(const void* addr) {
asm volatile ("pld [%0]" : : "r" (addr));
}
Memory Access Patterns
- Sequential Access: Lebih cepat karena hardware prefetcher
- Avoid Striding: Access setiap N-bytes dapat miss cache
- Struct Layout: Letakkan data yang sering diakses bersama berdekatan
Kesimpulan dan Best Practices
Checklist Pengembangan Bare Metal
- ✓ Set stack pointer di boot code (
SP = 0x8000) - ✓ Clear BSS section sebelum call main
- ✓ Gunakan linker script yang benar
- ✓ Tandai MMIO pointers sebagai
volatile - ✓ Gunakan memory barriers saat akses peripheral
- ✓ Align data structures sesuai kebutuhan (4-byte untuk word access)
- ✓ Implementasi exception handlers untuk debugging
- ✓ Test dengan berbagai ukuran RAM (256MB vs 512MB)
Kesalahan Umum yang Harus Dihindari
- Menggunakan stack sebelum menginisialisasi SP
- Menulis ke ROM region (peripheral registers yang read-only)
- Lupa disable/flush cache saat DMA operations
- Mengasumsikan nilai awal variabel global tanpa clear BSS
- Mengakses unaligned addresses untuk multi-byte operations
Resource Tambahan
- BCM2835 ARM Peripherals Guide: Dokumentasi resmi register peripheral
- ARM1176JZF-S Technical Reference Manual: Dokumentasi CPU
- GNU Linker Manual: Referensi lengkap linker script syntax
Langkah Selanjutnya
Dengan pemahaman peta memori yang solid, Anda siap untuk:
- Implementasi Exception Handlers: Tangani interrupt dan error dengan proper
- Driver Development: Tulis driver untuk GPIO, UART, SPI, I2C
- Memory Management: Implementasi heap allocator yang efisien
- Multi-tasking: Buat simple scheduler dengan context switching
- File System: Akses SD card dan implementasi FAT32
Pengembangan OS bare metal adalah perjalanan yang menantang namun sangat memuaskan. Setiap byte memori yang Anda kendalikan membawa Anda lebih dekat ke pemahaman fundamental tentang bagaimana komputer bekerja di level terendah.
Selamat mengembangkan sistem Anda!
Appendix A: Quick Reference
Memory Regions
0x00000000 - 0x00007FFF : Low Memory (Vectors, ATAGS, Stack)
0x00008000 - 0x???????? : Kernel (.text, .data, .bss, heap)
0x20000000 - 0x20FFFFFF : Peripherals (MMIO)
Critical Registers
SP (Stack Pointer) : 0x8000 (recommended initial value)
PC (Program Counter) : 0x8000 (kernel entry point)
CPSR (Status Register): 0xD3 (SVC mode, IRQ/FIQ disabled)
Build Commands
# Compile
arm-none-eabi-gcc -mcpu=arm1176jzf-s -fpic -ffreestanding -c kernel.c
# Link
arm-none-eabi-ld -T kernel.ld -o kernel.elf kernel.o
# Convert to binary
arm-none-eabi-objcopy kernel.elf -O binary kernel.img
