Sistem operasi modern menjalankan banyak program secara serempak: browser, editor teks, server database, dan layanan latar belakang semuanya hidup berdampingan. Sistem operasi mencapai hal tersebut dengan dua mekanisme utama: proses sebagai unit isolasi berat (memori terpisah, file descriptor sendiri) dan thread sebagai unit eksekusi ringan di dalam proses (berbagi memori dengan thread lain). Pada chapter ini kita membedakan keduanya secara konkret lewat pemanggilan getpid() dan pembuatan thread dengan pthread_create, sehingga perbedaan isolasi memori antara proses dan thread terlihat dari kode nyata, bukan sekadar teori.

Proses: Unit Isolasi Berat #

Proses adalah instance program yang sedang berjalan. Setiap proses memiliki address space sendiri—kode, data, heap, dan stack terpisah dari proses lain. Kernel Linux memberi setiap proses sebuah angka unik bernama PID (process ID). Sebuah proses juga punya PPID (parent PID) yang menunjukkan proses induknya, serta UID (user ID) pemilik. Kita membaca ketiga nilai ini lewat getpid(), getppid(), dan getuid() dari header <unistd.h>.

c
/* File: proses_info.c - menampilkan PID dan info proses */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void) {
    pid_t pid  = getpid();
    pid_t ppid = getppid();
    uid_t uid  = getuid();

    printf("PID  (process ID) : %d\n", pid);
    printf("PPID (parent PID): %d\n", ppid);
    printf("UID  (user ID)   : %d\n", uid);
    return 0;
}

Compile dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -o proses_info proses_info.c && ./proses_info

Output yang Dihasilkan (nilai PID bervariasi antar eksekusi):

text
PID  (process ID) : 11200
PPID (parent PID): 11192
UID  (user ID)   : 1001

Mari kita telaah baris-baris kode di atas:

  1. Baris 2 (#define _POSIX_C_SOURCE 200809L): Meminta compiler agar hanya mengekspos API POSIX edisi 2008 (POSIX.1-2008). Tanpa define ini, sebagian sistem tidak menyediakan deklarasi getpid di <unistd.h> saat mode standar c11.
  2. Baris 6 (pid_t pid = getpid();): Tipe pid_t adalah integer bertanda dari <sys/types.h> yang digunakan kernel untuk menyimpan PID. Fungsi getpid() selalu sukses—tidak pernah mengembalikan error.
  3. Baris 7 (pid_t ppid = getppid();): Mengambil PID proses induk. Bila program dijalankan dari shell, PPID biasanya berupa PID shell itu sendiri.
  4. Baris 8 (uid_t uid = getuid();): Mengambil user ID pemilik proses. UID 0 berarti root; UID 1000 atau lebih biasanya user biasa.
  5. Baris 10-12 (printf(...)): Mencetak ketiga nilai. Karena pid_t dan uid_t umumnya berupa int, format %d aman dipakai.

Memantau Proses dengan ps dan top #

Perintah ps dan top adalah alat bawaan POSIX untuk memantau proses yang sedang berjalan. Kita dapat menggunakan program sederhana berikut untuk mendemonstrasikan bahwa PID yang dicetak program memang cocok dengan yang dilaporkan kernel.

c
/* File: lihat_pid.c - tunjukkan PID sendiri agar bisa diverifikasi dengan ps */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>

int main(void) {
    pid_t pid = getpid();
    printf("PID program ini = %d\n", pid);
    printf("Verifikasi di terminal lain: ps -p %d -o pid,ppid,cmd\n", pid);
    sleep(2);   /* beri waktu pembaca menjalankan ps */
    return 0;
}

Compile lalu jalankan di satu terminal, dan di terminal lain jalankan ps:

bash
gcc -std=c11 -Wall -Wextra -o lihat_pid lihat_pid.c
./lihat_pid &
ps -p $! -o pid,ppid,cmd

Output yang Dihasilkan (PID akan berbeda tiap pemanggilan):

text
PID program ini = 11216
Jalankan di terminal lain: ps -p 11216 -o pid,ppid,cmd
Atau lihat daftar proses: ps -e | head
  PID  PPID CMD
11216 11192 ./lihat_pid

Baris terakhir berasal dari ps. Kita lihat PID yang dilaporkan kernel persis sama dengan PID yang dicetak program. Inilah bukti bahwa setiap proses punya identitas unik yang dapat diakses baik dari dalam proses (lewat getpid) maupun dari luar (lewat ps).

Thread: Unit Eksekusi Ringan #

Thread adalah alur eksekusi di dalam sebuah proses. Semua thread dalam proses yang sama berbagi address space, file descriptor, dan heap—sehingga komunikasi antar thread cepat, tetapi data shared harus dilindungi dari akses bersamaan. Thread memiliki stack sendiri (biasanya 8 MB di Linux) untuk variabel lokal dan call frame.

c
/* File: thread_pertama.c - membuat thread dengan pthread */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <pthread.h>

/* Fungsi yang dijalankan oleh thread baru */
static void *cetak_salut(void *arg) {
    long id = (long) arg;
    printf("Halo dari thread pekerja, arg=%ld\n", id);
    return NULL;
}

int main(void) {
    pthread_t pekerja;
    long argumen = 42L;

    /* Buat thread baru, jalankan cetak_salut */
    if (pthread_create(&pekerja, NULL, cetak_salut, (void *) argumen) != 0) {
        perror("pthread_create");
        return 1;
    }

    /* Tunggu thread pekerja selesai */
    if (pthread_join(pekerja, NULL) != 0) {
        perror("pthread_join");
        return 1;
    }

    printf("Thread pekerja selesai, kembali ke main.\n");
    return 0;
}

Compile dengan flag -pthread:

bash
gcc -std=c11 -Wall -Wextra -pthread -o thread_pertama thread_pertama.c && ./thread_pertama

Output yang Dihasilkan:

text
Halo dari thread pekerja, arg=42
Thread pekerja selesai, kembali ke main.

Mari kita telaah baris-baris kode di atas:

  1. Baris 3 (#include <pthread.h>): Header POSIX Threads. Berisi deklarasi pthread_t, pthread_create, pthread_join, dan lain-lain.
  2. Baris 6 (static void *cetak_salut(void *arg)): Signature fungsi thread WAJIB mengembalikan void * dan menerima satu argumen void *. Konvensi ini memungkinkan pthread_create meneruskan argumen apa pun lewat pointer.
  3. Baris 7 (long id = (long) arg;): Kita meneruskan integer 42 dengan cara meng-cast-nya menjadi void *. Pola ini aman untuk nilai kecil, tetapi untuk objek kompleks sebaiknya kirim pointer ke struct yang dialokasikan di heap.
  4. Baris 13 (pthread_t pekerja;): Variabel ini menyimpan ID thread baru. Kita akan menggunakannya untuk pthread_join.
  5. Baris 15 (pthread_create(&pekerja, NULL, cetak_salut, (void *) argumen)): Argumen kedua NULL berarti pakai atribut default (stack size default, tidak detached). Fungsi mengembalikan 0 saat sukses—selalu cek return value karena thread creation bisa gagal pada sistem kelelahan resource.
  6. Baris 21 (pthread_join(pekerja, NULL)): Memblokir main hingga thread pekerja selesai. Argumen kedua NULL berarti abaikan return value fungsi thread.

Perbandingan Proses vs Thread #

Pilihan antara proses dan thread bergantung pada kebutuhan isolasi vs kebutuhan berbagi data:

AspekProsesThread
Address spaceTerpisah totalBerbagi dengan thread lain di proses sama
PembuatanMahal (~1-10 ms via fork)Murah (~10-100 µs via pthread_create)
KomunikasiIPC: pipe, socket, shared memoryLangsung lewat memori shared
Isolasi crashSatu proses crash tidak memengaruhi yang lainSatu thread crash mematikan seluruh proses
Cocok untukServer isolation, sandboxingParalelisme CPU-bound dalam satu program

Visualisasi Konsep #

Diagram berikut membandingkan isolasi memori pada dua proses terpisah dengan berbagi memori pada dua thread dalam satu proses:

Pada diagram di atas, dua proses terpisah (Proses A dan Proses B) masing-masing memiliki address space sendiri—kode, data, heap, dan stack tidak berbagi sama sekali. Komunikasi antar proses harus melalui mekanisme Inter-Process Communication (IPC) seperti pipe, socket, atau shared memory. Sebaliknya, dua thread dalam satu proses berbagi kode, data, dan heap—hanya stack yang terpisah untuk masing-masing thread. Berbagi memori membuat komunikasi antar thread sangat cepat (cukup tulis ke variabel global), tetapi memerlukan sinkronisasi eksplisit (misalnya mutex) untuk mencegah race condition ketika dua thread mengakses data yang sama secara bersamaan.