在信號驅動的並發編程中,sigsuspend 是實現「原子性等待信號」的唯一標準接口。它將「設置臨時信號掩碼」與「掛起進程」合併為**單一原子操作**,完美解決了「檢查條件 → 設置掩碼 → 睡眠」竞態窗口。本文深度解析原型、語義、內核實現、返回值陷阱、經典用法模式與 Stevens 經典教材中的時序細節。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
原子操作三步曲 (不可分割):
mask 指向的集合 (即「開放」mask 中的信號)。sigsuspend 返回。-1,errno = EINTR。SIGKILL),sigsuspend **永不返回**。SIGKILL/SIGSTOP) 無法被 mask 阻塞,內核強制放行。sigset_t newmask, oldmask, zeromask;
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT); // 要阻塞的信號
// 1. 阻塞 SIGINT,保存舊掩碼
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
// 2. 關鍵區代碼 (SIGINT 不會打斷)
critical_section();
// 3. 原子性:開放所有信號(zeromask)並等待
// 若 SIGINT 在此前已遞達(pending),會立即喚醒
sigsuspend(&zeromask); // 返回時 errno=EINTR,掩碼已恢復為 oldmask
// 4. 恢復原始掩碼 (解除 SIGINT 阻塞)
sigprocmask(SIG_SETMASK, &oldmask, NULL);
// 簡化版 sys_sigsuspend 邏輯
mask &= ~(sigmask(SIGKILL) | sigmask(SIGSTOP)); // 強制開放不可阻塞信號
spin_lock_irq(¤t->sigmask_lock);
saveset = current->blocked; // 保存舊掩碼
siginitset(¤t->blocked, mask); // 設置新掩碼
recalc_sigpending(current);
spin_unlock_irq(¤t->sigmask_lock);
regs->eax = -EINTR; // 預置返回值
while (1) {
current->state = TASK_INTERRUPTIBLE;
schedule(); // 進程睡眠,調度器切換
if (do_signal(regs, &saveset)) // 喚醒後嘗試投遞信號
return -EINTR; // 信號處理函數執行完,返回用戶態
}
關鍵時序:
saveset)。do_signal 檢測 pending 信號 → 執行處理函數。do_signal 返回真 → sys_sigsuspend 返回 -EINTR。這解釋了 Stevens 書籍中「信號處理函數先返回,sigsuspend 再返回」的現象:**處理函數執行在原子操作的「喚醒後、返回前」階段**。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_int(int signo) {
printf("\n在信號處理函數中: SIGINT captured\n");
}
void pr_mask(const char *str) {
sigset_t set;
sigprocmask(0, NULL, &set);
printf("%s", str);
if (sigismember(&set, SIGINT)) printf(" SIGINT");
if (sigismember(&set, SIGTSTP)) printf(" SIGTSTP");
if (sigismember(&set, SIGTERM)) printf(" SIGTERM");
printf("\n");
}
int main() {
sigset_t newmask, oldmask, zeromask;
signal(SIGINT, sig_int);
sigemptyset(&zeromask);
sigemptyset(&newmask);
sigaddset(&newmask, SIGINT);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
pr_mask("關鍵區內掩碼: ");
sigsuspend(&zeromask); // 等待 SIGINT
pr_mask("sigsuspend返回後掩碼: ");
sigprocmask(SIG_SETMASK, &oldmask, NULL);
pr_mask("最終掩碼: ");
return 0;
}
運行輸出 (按下 Ctrl+C):
關鍵區內掩碼: SIGINT
在信號處理函數中: SIGINT captured
sigsuspend返回後掩碼: SIGINT
最終掩碼:
printf/malloc/lock),演示代碼僅為教學。mask` 阻塞了目標信號,`sigsuspend` 永不被喚醒。pthread_sigmask + sigwait,進程級 sigsuspend 影響全進程。sigsuspend 是信號編程中「等待條件滿足」的**原語級工具**。理解其「設置掩碼-睡眠-恢復掩碼」原子性、內核掩碼恢復先於信號處理函數執行的時序、EINTR 語義,是編寫正確、健壯信號驅動程序的前提。掌握它,就掌握了 Unix 並發編程的靈魂。