Episode ini membahas system programming: interaksi dengan system call dan API sistem operasi, pembuatan proses dengan fork dan pipes untuk inter-process communication, memory mapped files dan low-level I/O, serta dasar embedded systems dan pertimbangan bare-metal.

C++ tidak hanya untuk aplikasi berlevel tinggi. Ketika aplikasi harus berinteraksi dengan kernel, mengelola proses, atau berjalan di perangkat tanpa sistem operasi penuh, C++ adalah pilihan utama. Inilah system programming.
Episode 18 membawa kalian ke lapisan bawah: system call dan API POSIX, pembuatan proses dengan fork dan komunikasi lewat pipes, inter-process communication, memory mapped files dan low-level I/O, serta konsep embedded dan bare-metal. Semua contoh memakai Linux dengan POSIX API.
System call adalah pintu masuk aplikasi ke kernel: membuka file, membaca, mengirim data, dan memulai proses. Bahasa C menyediakan pembungkus langsung seperti open, read, write, dan fork. Di C++, kalian bisa memakainya langsung atau membungkusnya dalam class RAII:
cat > syscall.cpp <<'EOF'
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
int main() {
int fd = open("keluaran.txt",
O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
std::cerr << "gagal membuka file\n";
return 1;
}
const char* teks = "tulisan dari system call\n";
write(fd, teks, 24);
close(fd);
std::cout << "selesai\n";
}
EOF
g++ -std=c++20 syscall.cpp -o syscall
./syscallopen("keluaran.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644) membuat file untuk ditulis. write(fd, teks, 24) menulis byte ke file, dan close(fd) menutupnya. Header <fcntl.h> dan <unistd.h> menyediakan deklarasi POSIX.
Pola aman: bungkus file descriptor dalam class yang destructornya memanggil close. File ditutup otomatis saat objek keluar dari scope — persis prinsip RAII dari episode 7. Setiap open berpasangan dengan close, dan RAII menjaminnya meski ada exception.
fork() menduplikasi proses berjalan menjadi dua: parent dan child. waitpid membuat parent menunggu child selesai:
cat > fork.cpp <<'EOF'
#include <iostream>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
std::cout << "Child: proses anak\n";
return 0;
} else {
int status;
waitpid(pid, &status, 0);
std::cout << "Parent: child selesai\n";
}
}
EOF
g++ -std=c++20 fork.cpp -o fork
./forkfork() mengembalikan 0 di proses anak dan PID anak di proses induk. waitpid(pid, &status, 0) membuat parent menunggu. Setelah fork, kedua proses melanjutkan dari baris yang sama — perbedaan hanya pada nilai kembalian.
Pipe adalah saluran satu arah antara dua proses. pipe(fds) membuat dua deskriptor: fds[1] untuk menulis, fds[0] untuk membaca. Pola umumnya: fork, tutup ujung yang tidak dipakai di masing-masing proses, lalu kirim:
cat > pipe.cpp <<'EOF'
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int fds[2];
pipe(fds);
pid_t pid = fork();
if (pid == 0) {
close(fds[0]);
write(fds[1], "pesan dari child", 16);
close(fds[1]);
} else {
close(fds[1]);
char buf[64];
int n = read(fds[0], buf, sizeof(buf));
buf[n] = '\0';
std::cout << "Parent terima: " << buf << "\n";
close(fds[0]);
waitpid(pid, nullptr, 0);
}
}
EOF
g++ -std=c++20 pipe.cpp -o pipe
./pipepipe(fds) membuat pipe, dan setelah fork, child menulis ke fds[1] sementara parent membaca dari fds[0]. Ujung yang tidak dipakai ditutup di masing-masing proses — penting agar read tahu kapan data berakhir.
Selain pipe, Unix menyediakan berbagai mekanisme IPC: message queues untuk pesan terstruktur, shared memory untuk data bersama dengan kecepatan tertinggi, dan Unix domain sockets untuk komunikasi mirip jaringan antar proses. Pilihan bergantung pada kebutuhan:
Pipe cukup untuk komunikasi sederhana. Untuk pola yang lebih kaya, pertimbangkan message queue atau socket — keduanya memakai konsep dari episode 13. Shared memory menuntut mutex atau semaphore untuk mencegah race, seperti di episode 12.
Memory mapped files memetakan isi file langsung ke ruang alamat proses dengan mmap. Panggilan mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0) memetakan file read-only, lalu p[i] membaca byte file seolah-olah array. munmap(p, len) melepas pemetaan. Pola ini efisien untuk file besar dan jadi dasar database serta runtime loader.
Low-level I/O dengan read dan write bekerja pada byte mentah, sedangkan std::ifstream menambahkan pemformatan. Untuk data biner berukuran besar, low-level I/O memberi kontrol penuh. Untuk teks terformat, iostream lebih nyaman — episode 9 membahas sisi iostream lengkap.
Di bare-metal, tidak ada sistem operasi, malloc, atau standard library penuh. Program memakai freestanding subset C++ dan menulis langsung ke register perangkat:
#include <cstdint>
volatile std::uint32_t* REG =
reinterpret_cast<std::uint32_t*>(0x40021000);
void tulis() {
*REG = 0x1;
}volatile std::uint32_t* REG menunjuk ke alamat register perangkat keras, dan menulis ke *REG mengirim nilai ke perangkat. Kata kunci volatile mencegah compiler menghapus atau menata ulang akses.
Info
Di embedded, hindari new dan exception yang membutuhkan runtime besar. Pakai alokasi statis, std::array, dan error handling berbasis status — pola yang dibahas di episode 21.
Inti yang harus dibawa pulang:
open, write, dan close adalah pintu ke kernel.fork menduplikasi proses; waitpid menunggu child selesai.mmap memetakan file sebagai memori untuk akses cepat.Di episode 19 selanjutnya kita akan membahas tooling modern dan build automation — workflow CMake modern, continuous integration dengan GitHub Actions dan GitLab CI, static analysis dan code formatting dengan clang-format, serta reproducible builds dan dependency management.