Belajar C Language - System Programming & OS Interaction
Episode 18 of 24

Belajar C Language - System Programming & OS Interaction

Episode ini menyentuh lapisan sistem operasi: system call dan wrapper libc, process creation dengan fork, exec, dan wait, signals, pipes, dan inter-process communication, serta file descriptors dengan select dan poll untuk event-driven I/O.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Program C tidak berjalan sendiri di atas perangkat keras; ia berjalan di atas sistem operasi yang menyediakan layanan lewat system call. Episode 18 membawa kalian ke level sistem programming: menciptakan proses, berkomunikasi antar proses, dan menangani I/O yang banyak sekaligus.

Kita mulai dari system call dan wrapper libc, lalu fork, exec, dan wait untuk manajemen proses, kemudian signals dan pipes sebagai alat komunikasi antar proses. Terakhir, select dan poll memungkinkan satu program menangani banyak file descriptor tanpa memblokir.

Ini adalah materi yang menghubungkan C dengan kernel — bahasa yang sama yang menulis Unix dan Linux kembali digunakan untuk berinteraksi dengannya.

System Call Dasar dan Wrapper

Wrapper libc

Aplikasi tidak memanggil kernel secara langsung, melainkan memanggil fungsi libc yang menjadi wrapper. Misalnya open, read, write, dan close memanggil system call dengan nama yang sama, ditambah menangani errno dan parameter. Wrapper ini menyeragamkan antarmuka dan menangani detail arsitektur.

Semua I/O Linux berjalan lewat file descriptor, bilangan yang mewakili file, socket, atau pipe terbuka:

Tulis dengan write
#include <fcntl.h>
#include <unistd.h>
 
int main(void) {
    int fd = open("catatan.txt", O_WRONLY | O_CREAT, 0644);
    if (fd < 0) {
        return 1;
    }
    write(fd, "data dari system call\n", 22);
    close(fd);
    return 0;
}
EOF
gcc -Wall -Wextra syscall.c -o syscall && ./syscall

Panggilan open("catatan.txt", O_WRONLY | O_CREAT, 0644) membuka file dan mengembalikan descriptor, write menulis byte, dan close melepaskannya. O_CREAT membuat file jika belum ada, dan 0644 adalah izin default. Ini dasar dari seluruh I/O Linux, termasuk socket di episode 12.

Process Creation dengan fork, exec, dan wait

Menyalin Proses dengan fork

fork menyalin proses saat ini menjadi dua: proses induk dan anak, keduanya melanjutkan dari titik yang sama tetapi menerima nilai kembalian berbeda:

Fork dan wait
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        printf("proses anak\n");
        return 0;
    }
    waitpid(pid, NULL, 0);
    printf("proses induk selesai\n");
    return 0;
}

pid_t pid = fork() mengembalikan 0 di anak dan PID anak di induk. waitpid(pid, NULL, 0) membuat induk menunggu anak selesai, mencegah zombie process. Tanpa wait, proses anak yang selesai meninggalkan jejak di tabel proses.

Mengganti Gambar dengan exec

exec menggantikan gambar proses saat ini dengan program lain. Kombinasi klasik: fork membuat anak, lalu anak memanggil exec untuk menjalankan program baru:

Jalankan program lewat exec
#include <sys/wait.h>
#include <unistd.h>
 
int main(void) {
    pid_t pid = fork();
    if (pid == 0) {
        execlp("ls", "ls", "-l", NULL);
        return 1;
    }
    waitpid(pid, NULL, 0);
    return 0;
}

execlp("ls", "ls", "-l", NULL) memanggil ls -l di dalam proses anak. Argumen pertama adalah nama program, yang kedua adalah nama yang muncul di argv[0], lalu argumen berikutnya, dan NULL sebagai penutup. Jika exec gagal, fungsi mengembalikan kontrol — inilah mengapa ada return 1 setelahnya.

Signals, Pipes, dan IPC

Menangani Signals

Signals adalah notifikasi asinkron ke proses: SIGINT saat Ctrl+C, SIGTERM saat diminta berhenti. Program bisa menangkap signal dan menjalankan handler:

Tangkap SIGTERM
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
 
void handler(int sig) {
    write(2, "menerima sinyal\n", 16);
}
 
int main(void) {
    signal(SIGTERM, handler);
    pause();
    return 0;
}

signal(SIGTERM, handler) mendaftarkan handler untuk sinyal terminasi, dan pause menahan proses sampai ada sinyal. Handler sebaiknya hanya memanggil fungsi async-signal-safe seperti write, bukan printf.

Pipes: Komunikasi Antar Proses

Pipe menghubungkan output satu proses ke input proses lain, seperti perintah cat file | grep pola di shell:

Pipe antar proses
#include <stdio.h>
#include <unistd.h>
 
int main(void) {
    int fd[2];
    pipe(fd);
    pid_t pid = fork();
    if (pid == 0) {
        close(fd[0]);
        write(fd[1], "halo dari anak\n", 15);
        return 0;
    }
    close(fd[1]);
    char buf[64];
    read(fd[0], buf, sizeof(buf));
    printf("%s", buf);
    return 0;
}

pipe(fd) menyediakan dua descriptor: fd[1] untuk menulis dan fd[0] untuk membaca. Anak menulis, induk membaca. Menutup sisi yang tidak dipakai adalah aturan penting untuk mencegah kebuntuan.

File Descriptors, select, dan poll

Menangani Banyak Descriptor

Server yang harus melayani banyak koneksi tidak bisa memblokir di satu read. select dan poll memantau banyak descriptor sekaligus dan memberi tahu mana yang siap dibaca:

Pantau descriptor dengan poll
#include <poll.h>
#include <stdio.h>
 
int main(void) {
    struct pollfd fds[1];
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    int siap = poll(fds, 1, 5000);
    if (siap > 0 && (fds[0].revents & POLLIN)) {
        printf("stdin siap dibaca\n");
    }
    return 0;
}

struct pollfd fds[1] mendeklarasikan array descriptor yang dipantau, dan poll(fds, 1, 5000) menunggu sampai ada yang siap atau timeout 5 detik. Ini pola dasar event-driven I/O yang dipakai server berskala besar — menunggu tanpa menghabiskan satu thread per koneksi.

Tip

Perhatikan perbedaan pola: satu thread per koneksi menangani concurrency dengan simpel tetapi mahal di sumber daya. select, poll, atau epoll memungkinkan satu thread menangani ribuan koneksi — pilihan penting untuk server produksi.

Penutup

Inti yang harus dibawa pulang:

  • Wrapper libc menerjemahkan panggilan C menjadi system call kernel.
  • Semua I/O Linux berjalan lewat file descriptor.
  • fork menyalin proses, exec mengganti gambar, wait menunggu anak.
  • Signal adalah notifikasi asinkron yang bisa ditangkap dengan handler.
  • Pipe menghubungkan output proses ke input proses lain.
  • select dan poll memungkinkan satu thread memantau banyak descriptor.

Di episode 19 selanjutnya kita akan membahas tooling modern dan build automation — toolchain GCC, Clang, dan MSVC, static analysis, sanitizer, dan linting dalam workflow, build automation dengan make, CMake, dan CI pipelines, serta packaging binary dan dasar cross-compilation.