傳統 pause() 存在競態條件:「檢查條件 → 呼叫 pause()」間隙若信號到達,將導致永久阻塞。sigsuspend() 提供原子性的「暫時替換信號屏蔽集並等待信號」操作,完美解決此問題。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
// 1. 將進程信號屏蔽集暫時替換為 mask 指向的集合
// 2. 進程掛起,等待信號
// 3. 捕捉到信號並執行處理函數返回後,恢復原屏蔽集
// 4. 回傳 -1,設定 errno = EINTR(永遠「失敗」返回,實為正常)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void check_set(const char *stage) {
sigset_t set;
sigprocmask(0, NULL, &set);
printf("[%s] Blocked: SIGINT=%d SIGTSTP=%d SIGTERM=%d\n",
stage,
sigismember(&set, SIGINT),
sigismember(&set, SIGTSTP),
sigismember(&set, SIGTERM));
}
void handler(int sig) {
printf("[Handler] Caught signal %d\n", sig);
}
int main() {
sigset_t block_set, zero_set, pend_mask;
pid_t pid = getpid();
printf("PID: %ld\n", (long)pid);
// 1. 註冊處理函數
signal(SIGINT, handler);
// 2. 準備信號集
sigemptyset(&block_set);
sigemptyset(&zero_set);
sigaddset(&block_set, SIGINT); // 要阻塞的信號
// 3. 設定屏蔽集:阻塞 SIGINT
sigprocmask(SIG_SETMASK, &block_set, NULL);
check_set("After SIG_BLOCK");
// 4. 檢查待決信號
sigpending(&pend_mask);
if (sigismember(&pend_mask, SIGINT))
printf("SIGINT pending before sigsuspend\n");
// 5. 原子性等待:暫時解除阻塞並休眠
printf("Calling sigsuspend()...\n");
if (sigsuspend(&zero_set) != -1) {
printf("sigsuspend unexpected return\n");
exit(1);
}
// 6. 返回點:信號處理函數已執行完畢
printf("[Main] After sigsuspend return\n");
check_set("After return");
// 7. 恢復原始屏蔽集
sigprocmask(SIG_SETMASK, &block_set, NULL); // 這裡示範恢復阻塞
printf("SIGINT unblocked\n");
return 0;
}
初始狀態:屏蔽集 = {SIGINT}
│
├─ sigsuspend(&zero_set) ← zero_set = {}
│ │
│ ├─ 核心:屏蔽集暫存為 {}(SIGINT 解除阻塞)
│ ├─ 進程睡眠
│ │
│ ├─ 【外部送 SIGINT (Ctrl+C)】
│ │ │
│ │ ├─ 核心:信號遞送,喚醒進程
│ │ ├─ 進程:執行 handler()
│ │ └─ handler 返回
│ │
│ └─ 核心:恢復屏蔽集為 {SIGINT}
│
└─ sigsuspend 返回 -1 (EINTR)
進程繼續執行後續程式碼

| 特性 | 說明 |
|---|---|
| 原子性 | 「解除阻塞 + 睡眠」不可分割,無競態 |
| 屏蔽集暫時替換 | 僅在 sigsuspend 期間生效,返回自動恢復 |
| 永遠回傳 -1 | errno = EINTR,不可作為錯誤判斷 |
| 信號處理函數必須返回 | 若處理函數呼叫 longjmp/exit,不會恢復屏蔽集 |
| 不可被 SIGKILL/SIGSTOP 中斷 | 這兩信號無法捕捉,直接終止/停止進程 |
volatile sig_atomic_t got_signal = 0;
void handler(int sig) { got_signal = 1; }
int main() {
sigset_t block_mask, wait_mask;
sigemptyset(&block_mask);
sigaddset(&block_mask, SIGINT);
sigprocmask(SIG_BLOCK, &block_mask, NULL); // 主迴圈阻塞 SIGINT
sigemptyset(&wait_mask); // 等待時完全開放
signal(SIGINT, handler);
while (!got_signal) {
// 做些工作...
printf("Working...\n");
sleep(1);
// 安全等待:若這期間有 SIGINT,會立即喚醒
sigsuspend(&wait_mask);
}
printf("Signal received, exiting gracefully\n");
return 0;
}
| 錯誤 | 後果 | 修正 |
|---|---|---|
| 傳遞未初始化的 sigset_t | 未定義行為、隨機阻塞 | 必須 sigemptyset/sigfillset 初始化 |
| 誤以為回傳 0 成功 | 邏輯錯誤 | 永遠回傳 -1/EINTR,勿檢查回傳值 |
| 在 handler 中呼叫非 async-signal-safe 函數 | 死鎖、資料損壞 | 僅設旗標/寫 pipe/eventfd |
| 忘記註冊 handler 就呼叫 sigsuspend | 收到信號進程終止(預設動作) | 先 signal()/sigaction() 註冊 |