Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 44.126 phút đọc

Tín hiệu và sigaction

Sau bài này bạn sẽ làm được

  • Kể các tín hiệu quan trọng và hành vi mặc định
  • Giải thích vì sao SIGKILL và SIGSTOP không bắt được
  • Viết bộ xử lý bằng sigaction với SA_RESTART
  • Dùng đúng kiểu volatile sig_atomic_t

Tín hiệu là cách hệ điều hành gõ vào vai chương trình của bạn: Ctrl+C, một tiến trình khác gửi kill, hay một lỗi bộ nhớ. Chúng đến bất kỳ lúc nào, và xử lý chúng đúng là một trong những phần tinh tế nhất của lập trình hệ thống.

#Tín hiệu là gì

Tín hiệu (signal)
Một ngắt mềm không đồng bộ do nhân gửi tới tiến trình. Không đồng bộ nghĩa là nó đến bất kỳ lúc nào, kể cả giữa chừng một hàm. Khi tín hiệu đến, chương trình tạm dừng việc đang làm, chạy bộ xử lý, rồi tiếp tục.

#Các tín hiệu quan trọng

Tín hiệuSốNguyên nhânMặc địnhBắt được
SIGINT2Ctrl+CKết thúcCó
SIGQUIT3Ctrl+BslashKết thúc + coreCó
SIGKILL9kill -9Kết thúcKHÔNG
SIGSEGV11Truy cập bộ nhớ saiKết thúc + coreCó
SIGTERM15kill mặc địnhKết thúcCó
SIGSTOP19Tạm dừngDừngKHÔNG
SIGCHLD17Con kết thúcBỏ quaCó
SIGPIPE13Ghi ống đã đóngKết thúcCó
SIGUSR1 / SIGUSR210 / 12Người dùng định nghĩaKết thúcCó

#signal, đơn giản nhưng có vấn đề

sig.c, bắt SIGINT bằng signal()
#include <stdio.h>
#include <signal.h>

static volatile sig_atomic_t nen_thoat = 0;

static void xu_ly(int s) {
    (void)s;
    nen_thoat = 1;              /* CHI dat co, khong lam gi khac */
}

int main(void) {
    signal(SIGINT, xu_ly);     /* SIGINT (Ctrl+C) -> goi xu_ly */

    int vong = 0;
    while (!nen_thoat) {
        printf("  vong %d\n", ++vong);
        if (vong == 3) raise(SIGINT);   /* mo phong Ctrl+C */
    }
    printf("da bat tin hieu, don dep va thoat sach\n");
    return 0;
}
terminal
gcc -O2 -Wall -Wextra sig.c -o sig.exe && ./sig.exe
dang chay, se tu gui SIGINT sau 3 vong
  vong 1
  vong 2
  vong 3
da bat tin hieu, don dep va thoat sach

#sigaction, cách đúng

sigaction, kiểm soát đầy đủ
#include <signal.h>
#include <string.h>

static volatile sig_atomic_t nen_thoat = 0;
static void xu_ly(int s) { (void)s; nen_thoat = 1; }

int main(void) {
    struct sigaction sa;
    memset(&sa, 0, sizeof sa);
    sa.sa_handler = xu_ly;
    sigemptyset(&sa.sa_mask);       /* khong chan tin hieu nao khac trong handler */
    sa.sa_flags = SA_RESTART;       /* tu khoi dong lai syscall bi ngat */

    if (sigaction(SIGINT,  &sa, NULL) < 0) { perror("sigaction"); return 1; }
    if (sigaction(SIGTERM, &sa, NULL) < 0) { perror("sigaction"); return 1; }

    while (!nen_thoat) { lam_viec(); }
    don_dep();
    return 0;
}
signal() cho mã nghiêm túc
signal(SIGINT, xu_ly); /* Hanh vi tuy he. Khong co SA_RESTART, nen syscall bi ngat tra EINTR va ban phai xu ly bang tay khap noi. */
sigaction()
struct sigaction sa = {0}; sa.sa_handler = xu_ly; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGINT, &sa, NULL); /* Nhat quan, co SA_RESTART, kiem soat day du. */

#Kiểu volatile sig_atomic_t

terminal
./sig.exe
  vong 1
  vong 2
  vong 3
da bat tin hieu, don dep va thoat sach

Tự làm thử

  1. Bắt SIGINT bằng signal và in một thông điệp trước khi thoát.
  2. Viết lại bằng sigaction với SA_RESTART và bắt cả SIGTERM.
  3. Thử gửi kill -9 cho chương trình và xác nhận nó không bắt được.
  4. Bỏ volatile khỏi biến cờ, dịch với -O2, và quan sát vòng lặp không thoát.
  5. Bắt SIGUSR1 và SIGUSR2 để hai tín hiệu làm hai việc khác nhau.
  6. Đặt sa_mask chặn SIGTERM trong khi xử lý SIGINT và quan sát hiệu ứng.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • Tín hiệu là ngắt mềm không đồng bộ, đến bất kỳ lúc nào kể cả giữa chừng một hàm.
  • SIGKILL và SIGSTOP không bắt được; đó là cơ chế cuối cùng của hệ điều hành.
  • signal() đơn giản nhưng hành vi khác nhau giữa các hệ; luôn dùng sigaction cho mã nghiêm túc.
  • sigaction có sa_handler, sa_mask, sa_flags với SA_RESTART, và hành vi nhất quán.
  • Biến chia sẻ với handler phải là volatile sig_atomic_t; handler đặt cờ, main xử lý.