Header <signal.h> menyediakan mekanisme standar C untuk menangani signal—notifikasi asinkron yang dikirim ke proses oleh sistem operasi atau proses lain. Signal umum yang sering kita temui: SIGINT (dikirim saat pengguna menekan Ctrl+C), SIGTERM (request terminasi standar), SIGSEGV (akses memori ilegal), SIGFPE (kesalahan aritmatika), dan SIGKILL (terminasi paksa yang tidak bisa ditangkap). Dengan signal(), kita mendaftarkan handler function yang dipanggil otomatis saat signal tiba. Handler ini berguna untuk membersihkan sumber daya sebelum program berhenti, atau untuk mengimplementasikan graceful shutdown. Pada chapter ini kita akan meninjau cara memasang handler, batasan keselamatan (async-signal-safety), dan daftar signal umum.

Memasang Signal Handler #

Fungsi signal(sig, handler) memasang handler untuk signal sig. Parameter handler adalah pointer ke fungsi yang menerima satu argumen int (nomor signal) dan mengembalikan void. Saat signal tiba, eksekusi normal diinterupsi, handler dijalankan, lalu eksekusi normal dilanjutkan. Konstanta SIG_DFL mengembalikan handler ke default (biasanya terminasi), sedangkan SIG_IGN mengabaikan signal.

c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

/* Variabel global flag - dibaca dari handler */
static volatile sig_atomic_t stop = 0;

/* Signal handler: harus sig_atomic_t untuk keamanan */
static void handler_sigint(int sig) {
    (void)sig;
    /* Hanya operasi sederhana yang aman di handler */
    stop = 1;
}

int main(void) {
    /* Daftarkan handler untuk SIGINT */
    signal(SIGINT, handler_sigint);
    printf("Program berjalan. Kirim SIGINT (Ctrl+C) untuk stop.\n");
    printf("Atau jalankan: kill -SIGINT %d\n", getpid());

    /* Loop sampai flag stop diset */
    int hitung = 0;
    while (!stop) {
        printf("Bekerja... %d\n", hitung++);
        fflush(stdout);
        sleep(1);
        if (hitung >= 3) {
            /* Setelah 3 iterasi, raise sendiri SIGINT untuk demo */
            printf("-> Mengirim SIGINT ke diri sendiri\n");
            raise(SIGINT);
        }
    }

    printf("Stop diterima, keluar dengan bersih.\n");
    return 0;
}

Output yang Dihasilkan:

text
Program berjalan. Kirim SIGINT (Ctrl+C) untuk stop.
Atau jalankan: kill -SIGINT 31270
Bekerja... 0
Bekerja... 1
Bekerja... 2
-> Mengirim SIGINT ke diri sendiri
Stop diterima, keluar dengan bersih.

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (static volatile sig_atomic_t stop = 0;): variabel global yang dibaca di loop utama dan ditulis di handler. volatile mencegah kompiler mengoptimalkan pembacaan, sig_atomic_t menjamin operasi atomik (tidak terinterupsi tengah jalan oleh signal).
  2. Baris 9 (static void handler_sigint(int sig)): handler function signature—menerima nomor signal sebagai argumen. Satu handler bisa dipakai banyak signal, argumen memungkinkan kita tahu signal mana yang memicu.
  3. Baris 11 (stop = 1;): satu-satunya operasi di handler—menyetel flag stop. Operasi ini sederhana dan async-signal-safe karena hanya menulis satu variabel sig_atomic_t.
  4. Baris 18 (signal(SIGINT, handler_sigint);): mendaftarkan handler. Setelah ini, bila SIGINT tiba, fungsi handler_sigint dipanggil alih-alih program dihentikan.
  5. Baris 25 (fflush(stdout);): wajib flush sebelum sleep(1) agar output tercetak segera. Tanpa ini, output tetap di buffer dan tidak terlihat sampai program selesai.
  6. Baris 28 (raise(SIGINT);): fungsi raise mengirim signal ke diri sendiri. Berguna untuk testing—di program nyata, signal biasanya datang dari pengguna (Ctrl+C) atau proses lain (kill).

Batasan Async-Signal-Safety #

Fungsi di dalam signal handler harus async-signal-safe—artinya fungsi tersebut dapat dipanggil dengan aman saat signal tiba tanpa risiko deadlock atau data corruption. Mayoritas fungsi standard library tidak aman: printf, malloc, free, fopen, system, dan fungsi I/O standar semua tidak aman karena memakai buffer atau lock internal. Yang aman: write(2), read(2), _exit(2), pengaturan variabel sig_atomic_t, dan beberapa fungsi lain yang terdaftar di man 7 signal-safety.

c
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

/* Daftar signal umum beserta nama */
static const struct {
    int num;
    const char *nama;
    const char *deskripsi;
} daftar_signal[] = {
    { SIGINT,  "SIGINT",  "Interrupt (Ctrl+C)" },
    { SIGTERM, "SIGTERM", "Termination request" },
    { SIGKILL, "SIGKILL", "Kill (tidak bisa ditangkap)" },
    { SIGSEGV, "SIGSEGV", "Segmentation violation" },
    { SIGFPE,  "SIGFPE",  "Floating point exception" },
    { SIGABRT, "SIGABRT", "Abort (dari abort())" },
    { SIGALRM, "SIGALRM", "Alarm (dari alarm())" },
    { SIGHUP,  "SIGHUP",  "Hangup" },
};

/* Handler yang memakai write() ke STDERR - async-signal-safe */
static void handler_generic(int sig) {
    char buf[64];
    int len = snprintf(buf, sizeof(buf), "HANDLER: signal %d diterima\n", sig);
    write(STDERR_FILENO, buf, (size_t)len);
}

int main(void) {
    /* Cetak daftar signal umum */
    size_t n = sizeof(daftar_signal) / sizeof(daftar_signal[0]);
    printf("=== Daftar Signal Umum ===\n");
    for (size_t i = 0; i < n; i++) {
        printf("%-10s (%2d) : %s\n",
               daftar_signal[i].nama,
               daftar_signal[i].num,
               daftar_signal[i].deskripsi);
    }

    /* Pasang handler lalu raise */
    signal(SIGTERM, handler_generic);
    signal(SIGUSR1, handler_generic);
    printf("\n=== Demontrasi raise() ===\n");
    printf("Mengirim SIGTERM ke diri sendiri...\n");
    fflush(stdout);
    raise(SIGTERM);
    printf("Setelah SIGTERM (masih jalan karena ditangkap)\n");

    printf("Mengirim SIGUSR1 ke diri sendiri...\n");
    fflush(stdout);
    raise(SIGUSR1);
    printf("Setelah SIGUSR1\n");

    /* Kembalikan SIGTERM ke default (terminate) */
    signal(SIGTERM, SIG_DFL);
    printf("\nSIGTERM di-restore ke default (SIG_DFL)\n");
    printf("Sekarang raise(SIGTERM) akan menghentikan program\n");
    fflush(stdout);
    raise(SIGTERM);
    printf("(baris ini tidak akan tercetak)\n");
    return 0;
}

Output yang Dihasilkan:

text
=== Daftar Signal Umum ===
SIGINT     ( 2) : Interrupt (Ctrl+C)
SIGTERM    (15) : Termination request
SIGKILL    ( 9) : Kill (tidak bisa ditangkap)
SIGSEGV    (11) : Segmentation violation
SIGFPE     ( 8) : Floating point exception
SIGABRT    ( 6) : Abort (dari abort())
SIGALRM    (14) : Alarm (dari alarm())
SIGHUP     ( 1) : Hangup

=== Demontrasi raise() ===
Mengirim SIGTERM ke diri sendiri...
HANDLER: signal 15 diterima
Setelah SIGTERM (masih jalan karena ditangkap)
Mengirim SIGUSR1 ke diri sendiri...
HANDLER: signal 10 diterima
Setelah SIGUSR1

SIGTERM di-restore ke default (SIG_DFL)
Sekarang raise(SIGTERM) akan menghentikan program

Output stderr:

text
Terminated

Handler handler_generic memakai snprintf untuk memformat pesan ke buffer lokal, lalu write(STDERR_FILENO, ...) untuk menulisnya. write adalah syscall yang async-signal-safe—aman dipanggil dari handler. Sebaliknya, printf tidak aman karena memakai buffer internal yang bisa rusak bila signal tiba di tengah operasi. Fungsi signal(SIGTERM, SIG_DFL) mengembalikan handler SIGTERM ke default (terminasi). Setelah ini, raise(SIGTERM) menghentikan program dengan exit code 143 (128 + 15, sesuai konvensi shell: 128 + nomor signal). Baris terakhir printf("(baris ini tidak akan tercetak)\n"); memang tidak dieksekusi—program sudah dihentikan oleh SIGTERM.

Daftar Signal Umum dan Sifatnya #

Berikut daftar signal yang sering ditemui di sistem POSIX. Nomor aktual dapat bervariasi antar platform—selalu rujuk nama simbolik:

SignalNomor (Linux)Default ActionBisa ditangkap?Sumber Umum
SIGHUP1TerminasiYaTerminal ditutup
SIGINT2TerminasiYaCtrl+C
SIGKILL9TerminasiTidakkill -9
SIGUSR110TerminasiYaSignal user-defined
SIGSEGV11Terminasi+coreYaAkses memori ilegal
SIGUSR212TerminasiYaSignal user-defined
SIGTERM15TerminasiYakill default
SIGCHLD17DiabaikanYaChild process berhenti
SIGCONT18LanjutkanYaLanjutkan proses di-stop
SIGSTOP19PauseTidakPause proses

Signal SIGKILL (9) dan SIGSTOP (19) tidak bisa ditangkap, diabaikan, atau diblok. Inilah alasan kill -9 selalu berhasil menghentikan proses—kernel langsung memproses, tidak perlu konsensus dengan program. Signal lain seperti SIGINT, SIGTERM, SIGSEGV dapat ditangkap dengan signal() atau sigaction() (POSIX, lebih kuat dan fleksibel).