UNIX/Linux 進程接收信號 (Signal) 實現優雅關閉、配置重載、狀態傾印。本文講解 signal() 註冊處理器、raise() 自發信號、異步信號安全函數、SA_RESTART、信號集阻塞/等待。
| 信號 | 值 | 來源 | 預設動作 |
|---|---|---|---|
| SIGINT | 2 | Ctrl+C / kill -2 | 終止 |
| SIGTERM | 15 | kill / systemd stop | 終止 |
| SIGHUP | 1 | 終端掛起 / kill -1 | 終止 (常用於重載配置) |
| SIGQUIT | 3 | Ctrl+\ | 核心傾印 |
| SIGUSR1/2 | 10/12 | 用戶自定義 | 終止 |
| SIGSEGV | 11 | 非法記憶體訪問 | 核心傾印 |
| SIGFPE | 8 | 算術異常 (除零) | 核心傾印 |
| SIGPIPE | 13 | 寫入無讀者管道 | 終止 (網路編程常忽略) |
不可捕獲:SIGKILL (9)、SIGSTOP (19)。
#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;
}
#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;
}
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);
信號處理器中僅能調用 POSIX 規定的安全函數 (節選):
write, _exit, sigaction, signal, raise, kill, alarm, pausepthread_mutex_lock 不安全 (可能死鎖),需用無鎖環形緩衝區或 eventfd/pipe 通知主線程。malloc/free 不安全。printf, std::cout, fopen 皆不安全。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); // 在主線程安全處理:日誌、鎖、複雜邏輯
}
}
}
sigset_t mask; sigfillset(&mask); // 阻塞所有信號
pthread_sigmask(SIG_BLOCK, &mask, nullptr); // 在工作線程創建前調用
// 主線程保持默認掩碼,或專門創建信號處理線程 sigwait()