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

Linux C 信號同步利器:sigsuspend 原子性等待與信號遞送流程分析

     編輯:關於C語言

為什麼需要 sigsuspend?

傳統 pause() 存在競態條件:「檢查條件 → 呼叫 pause()」間隙若信號到達,將導致永久阻塞。sigsuspend() 提供原子性的「暫時替換信號屏蔽集並等待信號」操作,完美解決此問題。

函數原型與語意

#include <signal.h>

int sigsuspend(const sigset_t *mask);
// 1. 將進程信號屏蔽集暫時替換為 mask 指向的集合
// 2. 進程掛起,等待信號
// 3. 捕捉到信號並執行處理函數返回後,恢復原屏蔽集
// 4. 回傳 -1,設定 errno = EINTR(永遠「失敗」返回,實為正常)

完整範例:安全等待 SIGINT

#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 執行截圖

關鍵特性總結

特性說明
原子性「解除阻塞 + 睡眠」不可分割,無競態
屏蔽集暫時替換僅在 sigsuspend 期間生效,返回自動恢復
永遠回傳 -1errno = 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() 註冊

延伸閱讀

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved