首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> C++ 入門

C++ 信號處理:signal() 捕獲 SIGINT/SIGTERM、raise() 主動發送與安全處理模式

     編輯:C++ 入門

UNIX/Linux 進程接收信號 (Signal) 實現優雅關閉、配置重載、狀態傾印。本文講解 signal() 註冊處理器、raise() 自發信號、異步信號安全函數、SA_RESTART、信號集阻塞/等待。

常見可捕獲信號

信號值來源預設動作
SIGINT2Ctrl+C / kill -2終止
SIGTERM15kill / systemd stop終止
SIGHUP1終端掛起 / kill -1終止 (常用於重載配置)
SIGQUIT3Ctrl+\核心傾印
SIGUSR1/210/12用戶自定義終止
SIGSEGV11非法記憶體訪問核心傾印
SIGFPE8算術異常 (除零)核心傾印
SIGPIPE13寫入無讀者管道終止 (網路編程常忽略)

不可捕獲:SIGKILL (9)、SIGSTOP (19)。

signal() 註冊處理器

#include <csignal> #include <iostream>
volatile std::sig_atomic_t g_shutdown = 0; // 唯一安全的全域變量類型

void handler(int sig) {
    g_shutdown = sig; // 僅設置標誌,不做複雜操作
    // 禁止:malloc、printf、lock、std::cout、throw、長時間運算
}

int main() {
    std::signal(SIGINT, handler);
    std::signal(SIGTERM, handler);
    // SIG_IGN 忽略,SIG_DFL 恢復預設
    while (!g_shutdown) {
        // 主循環工作
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    std::cout << "Shutdown signal " << g_shutdown << " received, cleaning up..." << std::endl;
    return 0;
}

raise() 主動發送信號

#include <csignal>
int main() {
    std::signal(SIGINT, [](int){ std::cout << "Caught self-raised SIGINT" << std::endl; });
    for (int i = 1; i <= 3; ++i) {
        std::cout << "Iteration " << i << std::endl;
        if (i == 2) std::raise(SIGINT); // 自己給自己發信號
    }
    return 0;
}

進階:sigaction() 替代 signal() (可移植、功能全)

struct sigaction sa = {};
sa.sa_handler = handler;
sa.sa_flags = SA_RESTART; // 被中斷的系統調用自動重啟 (read, accept 等)
sigemptyset(&sa.sa_mask); // 處理器執行期間不阻塞其他信號
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);

異步信號安全函數 (Async-Signal-Safe)

信號處理器中僅能調用 POSIX 規定的安全函數 (節選):

  • 寫入:write, _exit, sigaction, signal, raise, kill, alarm, pause
  • 鎖:pthread_mutex_lock 不安全 (可能死鎖),需用無鎖環形緩衝區或 eventfd/pipe 通知主線程。
  • 記憶體:malloc/free 不安全。
  • I/O:printf, std::cout, fopen 皆不安全。

最佳實踐:自我管道技巧 (Self-Pipe Trick)

int sig_pipe[2];
pipe(sig_pipe); // sig_pipe[0] 讀端, sig_pipe[1] 寫端
fcntl(sig_pipe[0], F_SETFL, O_NONBLOCK);
fcntl(sig_pipe[1], F_SETFL, O_NONBLOCK);

void handler(int sig) {
    char c = sig; write(sig_pipe[1], &c, 1); // 僅寫入管道,極快、安全
}

// 主事件循環 (select/epoll/kqueue 監聽 sig_pipe[0])
while (running) {
    int n = epoll_wait(epfd, events, MAX, -1);
    for (int i = 0; i < n; ++i) {
        if (events[i].data.fd == sig_pipe[0]) {
            char sig; read(sig_pipe[0], &sig, 1);
            handle_signal(sig); // 在主線程安全處理:日誌、鎖、複雜邏輯
        }
    }
}

多線程信號處理

  • 信號遞送給任意一個未阻塞該信號的線程 (Linux: 主線程優先)。
  • 統一策略:主線程解除阻塞處理信號,工作線程全部阻塞。
  • sigset_t mask; sigfillset(&mask); // 阻塞所有信號
    pthread_sigmask(SIG_BLOCK, &mask, nullptr); // 在工作線程創建前調用
    // 主線程保持默認掩碼,或專門創建信號處理線程 sigwait()
    
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved