POSIX menyediakan dua syscall fundamental untuk membuat proses baru: fork yang menduplikasi proses pemanggil menjadi dua proses identik (parent dan child), dan exec yang mengganti image program saat ini dengan program lain dari file. Kombinasi fork + exec adalah cara Unix menjalankan program baru—shell menggunakan pola ini setiap kali kita mengetik perintah. Pada chapter ini kita membuat child dengan fork, menunggu child selesai dengan waitpid, mengganti image child dengan execl, dan akhirnya menjalankan beberapa child paralel untuk membandingkan eksekusi berurutan vs paralel.
fork: Membuat Proses Baru #
fork() adalah satu-satunya cara POSIX membuat proses baru (selain beberapa varian khusus seperti vfork dan clone di Linux). Setelah fork, ada dua proses yang menjalankan kode yang sama: parent dan child. Cara membedakannya adalah nilai kembali fork—di parent, ia berisi PID child; di child, ia berisi 0.
/* File: fork_dasar.c - fork() dasar: parent dan child */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
/* flush stdout sebelum fork agar child tidak mewarisi buffer kotor */
fflush(stdout);
pid_t pid = fork();
if (pid < 0) {
perror("fork gagal");
return 1;
}
if (pid == 0) {
/* Kode ini berjalan di child */
printf("[CHILD] PID=%d, PPID=%d\n", getpid(), getppid());
fflush(stdout);
printf("[CHILD] selesai.\n");
fflush(stdout);
_exit(7); /* exit code 7; _exit tidak flush stdio */
} else {
/* Kode ini berjalan di parent */
printf("[PARENT] PID=%d, child PID=%d\n", getpid(), pid);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("[PARENT] child exit code = %d\n", WEXITSTATUS(status));
}
printf("[PARENT] selesai.\n");
}
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o fork_dasar fork_dasar.c
timeout 5 ./fork_dasarOutput yang Dihasilkan (PID bervariasi antar eksekusi):
[CHILD] PID=12795, PPID=12794
[CHILD] selesai.
[PARENT] PID=12794, child PID=12795
[PARENT] child exit code = 7
[PARENT] selesai.Mari kita telaah baris-baris kode di atas:
- Baris 8 (
fflush(stdout);): Buffer stdout di-flush sebelumfork. Tanpa ini, child mewarisi buffer yang belum ditulis—bila child juga memanggilprintflalu keluar, pesan parent bisa tercetak dua kali (satu saat parent akhirnya flush, satu saat child exit). - Baris 10 (
pid_t pid = fork();): Membuat proses baru. Setelah pemanggilan, ada dua proses dengan kode identik. Memory layout (heap, stack, file descriptors) di-copy-on-write—child melihat snapshot memory parent saat fork. - Baris 16 (
if (pid == 0)): Cabang child. Di child,fork()selalu mengembalikan0. Cabang ini menulis PID dan PPID child, lalu keluar dengan kode7. - Baris 24 (
_exit(7)): Di child, gunakan_exit(dengan underscore), bukanexit._exitlangsung memanggil kernel tanpa flush stdio buffer atau memanggilatexithandler—tepat untuk child setelahfork. - Baris 27 (
else): Cabang parent. Variabelpiddi parent berisi PID child. - Baris 29 (
waitpid(pid, &status, 0)): Parent memblokir hingga child dengan PIDpidselesai. Status keluar disimpan distatus. Argumen ketiga0berarti default (wait blocking). - Baris 30-32 (
WIFEXITED(status),WEXITSTATUS(status)): Macro untuk membaca status.WIFEXITEDtrue bila child berakhir denganreturn/exitnormal.WEXITSTATUSmengambil 8-bit exit code yang dilewatkan ke_exit/exit—dalam kasus ini7.
exec: Mengganti Image Program #
fork saja jarang berguna—biasanya kita ingin child menjalankan program lain. Fungsi keluarga exec (execl, execv, execlp, execvp, execle, execve) mengganti image program saat ini dengan program dari file. Bila exec sukses, kode setelah pemanggilan tidak pernah dieksekusi—image program lama hilang.
/* File: fork_exec.c - fork + exec: ganti image child dengan /bin/echo */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void) {
fflush(stdout);
pid_t pid = fork();
if (pid < 0) {
perror("fork gagal");
return 1;
}
if (pid == 0) {
/* Child: ganti image program dengan /bin/echo */
printf("[CHILD] PID=%d sebelum exec\n", getpid());
fflush(stdout);
execl("/bin/echo", "echo", "Halo", "dari", "child!", NULL);
/* execl tidak kembali kecuali gagal */
perror("execl gagal");
_exit(127);
} else {
printf("[PARENT] PID=%d, child PID=%d\n", getpid(), pid);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
int code = WEXITSTATUS(status);
printf("[PARENT] child exit code = %d\n", code);
}
}
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o fork_exec fork_exec.c
timeout 5 ./fork_execOutput yang Dihasilkan:
[CHILD] PID=12746 sebelum exec
Halo dari child!
[PARENT] PID=12745, child PID=12746
[PARENT] child exit code = 0Mari kita telaah baris-baris kode di atas:
- Baris 17 (
execl("/bin/echo", "echo", "Halo", "dari", "child!", NULL)): Mengganti image program child dengan/bin/echo. Argument pertama adalah path program. Argument kedua ("echo") menjadiargv[0]di program baru—biasanya sama dengan nama program. Argument berikutnya ("Halo","dari","child!") menjadiargv[1],argv[2],argv[3]. Daftar argument wajib diakhiriNULLsebagai sentinel. - Suffix
lpadaexecl: Berarti list—argument dilewatkan satu per satu. Varian lain:execv(vector/array),execlp(list + cari diPATH),execvp(vector + cari diPATH). Untuk shell,execvpadalah pilihan umum karena menerimaargv[]yang sudah di-parse dari input user. - Baris 20 (
perror("execl gagal"); _exit(127);): Bilaexeclkembali, pasti gagal—sukses tidak pernah kembali. Konvensi exit code127artinya “command not found” (sama seperti shell). Tanpa baris ini, bilaexecgagal, child akan lanjut mengeksekusi kode parent yang seharusnya tidak boleh dijalankan dua kali. - Output
Halo dari child!berasal dari program/bin/echoyang baru—bukan dari printf. Ini membuktikan image program sudah berganti.
Menjalankan Banyak Child Paralel #
Salah satu kekuatan fork adalah parent dapat membuat banyak child, lalu menunggu semuanya. Karena setiap child adalah proses terpisah dengan PID sendiri, kernel scheduler menjalankan mereka paralel pada mesin multicore. Program berikut membuat tiga child yang masing-masing tidur 1 detik—total waktu sekitar 1 detik, bukan 3 detik.
/* File: fork_paralel.c - menjalankan banyak child secara paralel */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
static double sekarang_detik(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
int main(void) {
const int N = 3;
double mulai = sekarang_detik();
pid_t child_pids[3];
for (int i = 0; i < N; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
return 1;
}
if (pid == 0) {
/* Child: tidur 1 detik, lalu exit */
struct timespec ts = { 1, 0 };
nanosleep(&ts, NULL);
_exit(i + 10); /* exit code unik per child */
}
child_pids[i] = pid;
}
/* Parent: tunggu semua child */
for (int i = 0; i < N; i++) {
int status;
pid_t pid = waitpid(child_pids[i], &status, 0);
if (WIFEXITED(status)) {
printf("[PARENT] child PID %d exit code = %d\n",
pid, WEXITSTATUS(status));
}
}
double selesai = sekarang_detik();
printf("[PARENT] total waktu = %.2f detik (paralel)\n", selesai - mulai);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o fork_paralel fork_paralel.c
timeout 5 ./fork_paralelOutput yang Dihasilkan (urutan child bisa berbeda):
[PARENT] child PID 12775 exit code = 10
[PARENT] child PID 12776 exit code = 11
[PARENT] child PID 12777 exit code = 12
[PARENT] total waktu = 1.00 detik (paralel)Mari kita telaah baris-baris kode di atas:
- Baris 18 (
pid_t pid = fork();di loop): Setiap iterasi membuat satu child. Parent menyimpanpidke arraychild_pids. Setelah loop, ada 4 proses: 1 parent + 3 child, semuanya menjalankan kode yang sama tetapi di cabang berbeda (parent lanjut loop, child keluar loop karenapid == 0). - Baris 28 (
_exit(i + 10)): Exit code child ke-iadalahi + 10—jadi child pertama exit10, kedua11, ketiga12. Pola ini membantu parent mengidentifikasi child mana yang selesai. - Baris 35 (
waitpid(child_pids[i], &status, 0)): Parent menunggu child ke-ispesifik. Karena kita tunggu sesuai urutan array, output juga muncul dalam urutan array. Untuk aplikasi yang tidak peduli urutan, gunakanwait(&status)yang mengembalikan salah satu child yang sudah mati. - Total waktu
1.00 detik: Bila ketiga child berjalan sequential, total butuh 3 detik. Karena kernel menjadwalkan mereka paralel (mesin kita punya 2 core), total hanya ~1 detik—durasi child terlama.
Perbandingan fork vs pthread_create #
Kapan pilih fork dan kapan pthread_create? Berikut ringkasan praktis:
| Aspek | fork | pthread_create |
|---|---|---|
| Isolasi memory | Ya—address space terpisah | Tidak—berbagi address space |
| Pembuatan | Mahal (~1 ms karena copy-on-write setup) | Murah (~10-100 µs) |
| Komunikasi | IPC: pipe, socket, shared memory | Langsung lewat variabel shared |
| Crash isolation | Tinggi—child crash tidak memengaruhi parent | Rendah—thread crash mematikan seluruh proses |
| Cocok untuk | Menjalankan program eksternal, server isolation | Paralelisme dalam satu program |