在Linux环境下,当进程收到信号时,如何优雅地处理并确保程序的正常运行?这就需要借助信号集和信号掩码的功能。本文将为你揭开信号集和信号掩码的神秘面纱,并通过生动的代码示例,让你彻底掌握在C++程序中使用它们的技巧。
一、信号集:表示信号的数据结构
信号集(signal set)是一种用于表示进程当前阻塞了哪些信号的数据结构,它本质上是一个数组 bitmap,使用sigset_t结构体来表示。每一种信号对应一个位,如果该位被置位(值为1),则表示该信号被阻塞,否则(值为0)表示未被阻塞。
我们可以使用以下函数来操作信号集:
- sigemptyset(&set): 将set中所有位清零,即不阻塞任何信号
- sigfillset(&set): 将set中所有位置1,即阻塞全部信号
- sigaddset(&set, sig): 将信号sig对应的位置1,即阻塞该信号
- sigdelset(&set, sig): 将信号sig对应的位清零,即解除对该信号的阻塞
- sigismember(&set, sig): 检查信号sig是否被set阻塞
示例代码:
#include <signal.h>
#include <iostream>
int main() {
sigset_t set;
// 初始化为空集
sigemptyset(&set);
// 阻塞所有信号的传递
sigset_t newset;
sigfillset(&newset);
// 添加SIGINT(Ctrl+C)信号
sigaddset(&set, SIGINT);
// 检查SIGINT是否在集合中
if (sigismember(&set, SIGINT)) {
std::cout << "SIGINT is blocked" << std::endl;
}
// 移除SIGINT
sigdelset(&set, SIGINT);
return 0;
}
二、信号掩码:阻塞信号的机制
1、信号掩码(signal mask)
信号掩码(signal mask)是Linux内核为每个进程维护的一个信号集,用于暂时阻塞该进程接收某些信号。除了SIGKILL和SIGSTOP这两个特殊信号外,其他信号都可以被阻塞。
内核会为每一个进程都维护一个信号掩码,也就是一组信号,阻塞其针对该进程的传递,直到进程从信号掩码中将该信号移除。
我们可以通过sigprocmask系统调用来修改进程的当前信号掩码:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
- how 参数指定如何修改掩码
- SIG_BLOCK: 将set指向的信号集并入当前掩码
- SIG_UNBLOCK: 将set指向的信号集从当前掩码中移除
- SIG_SETMASK: 使用set指向的信号集作为新的信号掩码
- oldset 用于保存修改前的信号掩码,如果不需要可设为NULL
例如,阻塞SIGINT(Ctrl+C)信号:
#include <signal.h>
#include <iostream>
int main() {
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
// 阻塞SIGINT信号
sigprocmask(SIG_BLOCK, &set, NULL);
// 此时按下Ctrl+C将不会终止程序
while (true) {
std::cout << "Program running..." << std::endl;
sleep(1);
}
return 0;
}
2、系统的默认行为
前面我们提到过,当前进程正在调用 SIGX 的信号处理函数,那么紧接着而来的 SIGX 信号将会被阻塞,直到上一个信号处理函数结束,这一现象我们可以用一个简单的例子证明 :
void handler(int signum)
{
printf("Got a SIGINT\n");
sigset_t currentset;
sigprocmask(SIG_BLOCK,NULL,¤tset);
int res=sigismember(¤tset,SIGINT);
printf("SIGINT is blocked ?:%d\n",res);
}
我们用 SIGINT 作为捕获信号,当我们键入 Ctrl-C 时,将会发现 SIGINT 信号的确是在当前进程的信号掩码中。
引发对处理器程序调用的信号将自动添加到进程信号掩码中。这意味着,当正在执行处理器程序时,如果同一个信号 实例第二次抵达,信号处理器程序将不会递归中断自己。