我们来深入学习 rt_sigprocmask
系统调用,请注意,用户空间通常调用的是 sigprocmask
,它是 rt_sigprocmask
的封装。摘要
rt_sigprocmask
(用户空间通常调用其封装函数sigprocmask
)是Linux系统中用于临时控制信号递送的系统调用。它允许进程阻塞特定信号,避免关键代码段被中断。被阻塞的信号会排队等待,直到解除阻塞。函数通过how
参数支持三种操作:SIG_BLOCK
(添加阻塞信号)、SIG_UNBLOCK
(移除阻塞信号)和SIG_SETMASK
(直接设置屏蔽字)。示例代码展示了如何阻塞SIGUSR1
信号,同时保持SIGINT
可用,并演示了三种不同的信号屏蔽操作方式。该机制常用于保护关键代码段的数据一致性。
1. 函数介绍
在 Linux 系统中,信号是进程间通信和通知的重要方式。但有时候,你的程序正在执行一段非常关键的代码(比如正在更新一个复杂的数据结构),你不希望被任何信号打断,因为这可能导致数据不一致或程序崩溃。
rt_sigprocmask
(用户空间通常通过 sigprocmask
调用)就是用来临时控制哪些信号可以被递送到你的进程。你可以告诉内核:“在接下来的一段时间里,请把 SIGINT
(Ctrl+C)和 SIGUSR1
信号暂时‘挡’在外面,等我处理完关键任务后再送进来”。
这个“暂时挡在外面”的过程就叫做阻塞(Blocking)信号。被阻塞的信号并不会丢失,它们会排队等待,直到你解除阻塞(Unblock),它们才会被真正递送并处理。
2. 函数原型
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
3. 功能
检查或修改当前进程的信号屏蔽字(signal mask)。信号屏蔽字是一个列表,定义了当前被阻塞(暂时不递送)的信号。
4. 参数
how
:int
类型。- 指定你想要对信号屏蔽字执行的操作。有三个主要选项:
SIG_BLOCK
: 把set
指向的信号集合添加到当前的屏蔽字中。意思是:“我现在想额外阻塞这些信号”。SIG_UNBLOCK
: 把set
指向的信号集合从当前的屏蔽字中移除。意思是:“我现在想解除对这些信号的阻塞”。SIG_SETMASK
: 把当前的信号屏蔽字直接设置为set
指向的信号集合。意思是:“不管以前怎么样,现在我只阻塞这些信号”。
set
:const sigset_t *
类型。- 一个指向
sigset_t
类型变量的指针,该变量包含了你想要操作(阻塞或解除阻塞)的信号集合。如果你传NULL
,则不修改当前的屏蔽字,只用于查询。
oldset
:sigset_t *
类型。- 一个指向
sigset_t
类型变量的指针。函数调用成功后,会把调用前的旧信号屏蔽字复制到这个变量中。如果你不关心旧的设置,可以传NULL
。
5. 返回值
- 成功: 返回 0。
- 失败: 返回 -1,并设置全局变量
errno
来指示具体的错误原因(例如,how
参数无效)。
6. 相似函数或关联函数
sigset_t
: 用于存储信号集合的数据类型。sigemptyset
: 初始化一个sigset_t
集合,使其不包含任何信号。sigfillset
: 初始化一个sigset_t
集合,使其包含所有可能的信号。sigaddset
: 向一个sigset_t
集合中添加一个特定的信号。sigdelset
: 从一个sigset_t
集合中删除一个特定的信号。sigismember
: 检查一个特定的信号是否属于某个sigset_t
集合。sigpending
: 检查当前有哪些信号是被阻塞且正在等待处理的。
7. 示例代码
下面是一个例子,演示如何使用 sigprocmask
来阻塞和解除阻塞信号。
#define _GNU_SOURCE // 启用 GNU 扩展
#include <stdio.h>
#include <stdlib.h> // 包含 exit
#include <unistd.h> // 包含 sleep
#include <signal.h> // 包含信号处理相关函数
#include <string.h> // 包含 memset
// 一个简单的信号处理函数
void signal_handler(int sig) {
printf("\nCaught signal %d\n", sig);
// 在实际应用中,信号处理函数应尽量简短,并只调用异步信号安全函数
}
int main() {
sigset_t block_set; // 用于设置要阻塞的信号
sigset_t prev_set; // 用于保存之前的信号屏蔽字 (可选)
sigset_t current_set; // 用于检查当前的信号屏蔽字 (可选)
printf("My PID is: %d\n", getpid());
// 1. 设置 SIGUSR1 和 SIGINT (Ctrl+C) 的处理函数
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask); // 处理函数执行时不额外阻塞信号
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL) == -1) {
perror("sigaction SIGUSR1");
exit(EXIT_FAILURE);
}
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction SIGINT");
exit(EXIT_FAILURE);
}
// 2. 创建一个信号集,并添加要阻塞的信号 (SIGUSR1)
sigemptyset(&block_set);
sigaddset(&block_set, SIGUSR1);
// 注意:我们没有阻塞 SIGINT,所以 Ctrl+C 仍然有效
// 3. 使用 sigprocmask 阻塞 SIGUSR1,并保存旧的屏蔽字
printf("Blocking SIGUSR1...\n");
printf("Try sending SIGUSR1 now: 'kill -USR1 %d'\n", getpid());
printf("Try pressing Ctrl+C (SIGINT) now - it should still work.\n");
printf("Sleeping for 10 seconds (signals are blocked/unblocked accordingly)...\n");
if (sigprocmask(SIG_BLOCK, &block_set, &prev_set) == -1) { // 阻塞并保存旧设置
perror("sigprocmask BLOCK");
exit(EXIT_FAILURE);
}
printf("SIGUSR1 is now blocked. Sleeping for 5 seconds...\n");
sleep(5); // 在这5秒内,SIGUSR1会被阻塞,SIGINT不会
// 4. 演示 SIG_SETMASK: 只阻塞 SIGINT,解除对 SIGUSR1 的阻塞
sigemptyset(&block_set);
sigaddset(&block_set, SIGINT); // 现在只阻塞 SIGINT
printf("5 seconds passed. Now blocking only SIGINT (Ctrl+C) using SIG_SETMASK.\n");
printf("Try sending SIGUSR1 now: 'kill -USR1 %d' - it should be caught immediately.\n");
printf("Try pressing Ctrl+C (SIGINT) now - it should be blocked.\n");
if (sigprocmask(SIG_SETMASK, &block_set, NULL) == -1) { // 设置新的屏蔽字
perror("sigprocmask SETMASK");
exit(EXIT_FAILURE);
}
printf("SIGINT is now blocked. Sleeping for 5 more seconds...\n");
sleep(5); // 在这5秒内,SIGINT会被阻塞,SIGUSR1不会
// 5. 演示 SIG_UNBLOCK: 解除对 SIGINT 的阻塞
// 我们解除阻塞的集合就是当前阻塞的集合 (block_set)
printf("5 seconds passed. Now unblocking SIGINT (Ctrl+C) using SIG_UNBLOCK.\n");
printf("Try pressing Ctrl+C (SIGINT) now - it should work and terminate the program.\n");
if (sigprocmask(SIG_UNBLOCK, &block_set, NULL) == -1) { // 解除阻塞
perror("sigprocmask UNBLOCK");
exit(EXIT_FAILURE);
}
printf("SIGINT is now unblocked. Sleeping for 10 more seconds...\n");
printf("The program will end if you press Ctrl+C.\n");
sleep(10); // 最后10秒,所有信号都按正常处理
printf("Program exiting normally after sleep.\n");
return 0;
}
编译和运行:
# 假设代码保存在 sigprocmask_example.c 中
gcc -o sigprocmask_example sigprocmask_example.c
# 终端 1: 运行程序
./sigprocmask_example
# 程序会输出 PID,例如 My PID is: 12345
# 终端 2 (在程序的不同睡眠阶段执行以下命令):
# 阶段 1 (前5秒): SIGUSR1 被阻塞
# kill -USR1 12345
# (观察终端 1,信号处理函数不会立即触发)
# 阶段 2 (中间5秒): SIGINT 被阻塞, SIGUSR1 正常
# kill -USR1 12345
# (观察终端 1,信号处理函数应该立即触发)
# kill -INT 12345 或按 Ctrl+C 在终端 1
# (观察终端 1,Ctrl+C 应该无效)
# 阶段 3 (最后10秒): SIGINT 解除阻塞
# 按 Ctrl+C 在终端 1
# (程序应该退出)
# 或者
# kill -INT 12345
# (程序应该退出)
这个例子演示了 sigprocmask
的三种主要操作:
SIG_BLOCK
:在开始时阻塞SIGUSR1
。SIG_SETMASK
:中间阶段,将屏蔽字设置为只阻塞SIGINT
,从而解除了对SIGUSR1
的阻塞。SIG_UNBLOCK
:最后阶段,解除了对SIGINT
的阻塞。
通过这种方式,你可以精确地控制在程序执行的不同阶段哪些信号可以打扰你的程序。