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

Linux C 信號屏蔽操作:sigprocmask 完整指南與實戰範例

     編輯:關於C語言

核心概念:阻塞 vs 忽略

操作行為信號狀態
阻塞暫時延遲遞送待決,解除阻塞後遞送
忽略直接丟棄不再遞送,也不排隊

阻塞是「暫緩」,忽略是「拒收」。阻塞期間信號會排隊(最多一個,除非使用即時信號)。

sigprocmask 原型與參數

#include <signal.h>

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oldset);

how 取值:
  SIG_BLOCK     // 新屏蔽集 = 當前 ∪ set
  SIG_UNBLOCK   // 新屏蔽集 = 當前 \ set
  SIG_SETMASK   // 新屏蔽集 = set

set    : 新增/移除的信號集指標(NULL 表示不修改,僅查詢)
oldset : 儲存舊屏蔽集指標(NULL 表示不需儲存)

回傳:成功 0,失敗 -1 (EINVAL)

信號集操作輔助函數

int sigemptyset(sigset_t *set);     // 清空集合
int sigfillset(sigset_t *set);      // 填入所有信號
int sigaddset(sigset_t *set, int signum);    // 加入信號
int sigdelset(sigset_t *set, int signum);    // 移除信號
int sigismember(const sigset_t *set, int signum); // 測試成員(1=是, 0=否, -1=錯)

範例一:動態增減屏蔽集

#include <stdio.h>
#include <signal.h>

void print_mask(const char *label) {
    sigset_t set;
    sigprocmask(0, NULL, &set);
    printf("[%s] SIGINT=%d SIGTSTP=%d SIGTERM=%d\n",
           label,
           sigismember(&set, SIGINT),
           sigismember(&set, SIGTSTP),
           sigismember(&set, SIGTERM));
}

int main() {
    sigset_t block_set;
    sigemptyset(&block_set);

    print_mask("Initial");

    // 1. SIG_SETMASK 直接設定
    sigaddset(&block_set, SIGINT);
    sigaddset(&block_set, SIGTSTP);
    sigprocmask(SIG_SETMASK, &block_set, NULL);
    print_mask("After SETMASK INT+TSTP");

    // 2. SIG_BLOCK 追加
    sigaddset(&block_set, SIGTERM);
    sigprocmask(SIG_BLOCK, &block_set, NULL);
    print_mask("After BLOCK TERM");

    // 3. SIG_UNBLOCK 移除
    sigdelset(&block_set, SIGTERM);
    sigprocmask(SIG_UNBLOCK, &block_set, NULL);
    print_mask("After UNBLOCK TERM");

    return 0;
}

動態屏蔽集操作執行截圖

範例二:關鍵區保護——「免打擾」執行特定程式碼

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void handler(int sig) { printf("[Handler] Signal %d caught\n", sig); }
void print_mask(const char *label) { /* 同上 */ }

int main() {
    sigset_t block_set, old_set, pend_mask;

    signal(SIGINT, handler);
    sigemptyset(&block_set);
    sigaddset(&block_set, SIGINT);
    sigaddset(&block_set, SIGTSTP);

    printf("PID: %ld\n", (long)getpid());

    // 1. 進入關鍵區:阻塞 SIGINT/SIGTSTP,保存舊屏蔽集
    sigprocmask(SIG_SETMASK, &block_set, &old_set);
    print_mask("Inside critical section (blocked)");

    // 2. 模擬關鍵操作(5 秒內不被信號打擾)
    sleep(5);

    // 3. 檢查是否有待決信號
    sigpending(&pend_mask);
    if (sigismember(&pend_mask, SIGINT))
        printf(">>> SIGINT was pending during critical section\n");

    // 4. 離開關鍵區:恢復原始屏蔽集
    sigprocmask(SIG_SETMASK, &old_set, NULL);
    print_mask("After restoring mask");
    printf("SIGINT unblocked, handler will run if pending\n");

    sleep(6);  // 給 handler 執行機會
    return 0;
}

進階用法:sigpending 檢查待決信號

int sigpending(sigset_t *set);
// 將目前「已送達但被阻塞」的信號集複製到 set
// 配合 sigismember() 判斷特定信號是否待決

sigprocmask vs pthread_sigmask

函數適用範圍標準
sigprocmask()單執行緒進程、主執行緒POSIX.1-2001
pthread_sigmask()多執行緒(每執行緒獨立屏蔽集)POSIX.1-2001

多執行緒程式必須使用 pthread_sigmask(),sigprocmask() 行為未定義。

常見錯誤避坑

  1. 未初始化 sigset_t:必須先 sigemptyset() 或 sigfillset()
  2. 混淆 SIG_BLOCK/UNBLOCK/SETMASK:SETMASK 是覆寫,BLOCK/UNBLOCK 是增減
  3. 忽略 oldset:關鍵區保護需保存舊屏蔽集以便精確恢復
  4. 在信號處理函數中呼叫 sigprocmask:雖允許,但易造成邏輯混亂,建議主迴圈統一管理

最佳實務模式:關鍵區保護巨集

#define BLOCK_SIGNALS(mask, old) do { \
    sigset_t _block; \
    sigemptyset(&_block); \
    sigaddset(&_block, SIGINT); \
    sigaddset(&_block, SIGTERM); \
    sigprocmask(SIG_SETMASK, &_block, &(old)); \
} while(0)

#define RESTORE_SIGNALS(old) sigprocmask(SIG_SETMASK, &(old), NULL)

// 使用範例
sigset_t old_mask;
BLOCK_SIGNALS(mask, old_mask);
// ... 關鍵操作 ...
RESTORE_SIGNALS(old_mask);

延伸閱讀

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