在 Linux 系统中,内核为每一个进程维护了一个信号掩码(实际上是一个信号集),即一组信号的集合。当进程接收到一个在其信号掩码中定义的信号时,该信号将被阻塞,而不会立即传递给进程进行处理。这个阻塞行为意味着信号不会被丢弃,而是会被保留,直到该信号从信号掩码中移除,内核才会将其传递给进程进行处理。
向信号掩码中添加信号的三种方式:
-
通过
signal()或sigaction()函数: 当应用程序调用signal()或sigaction()函数为某一信号设置处理方式时,该信号通常会自动添加到进程的信号掩码中。这样做的目的是确保在处理某个信号时,如果该信号再次发生,它将被阻塞,以避免信号处理函数的重入问题。- 对于
sigaction()函数来说,是否自动将信号添加到信号掩码中,还取决于是否设置了SA_NODEFER标志。如果设置了SA_NODEFER,信号将不会被自动阻塞。 - 当信号处理函数执行完毕并返回后,该信号将自动从信号掩码中移除,允许其再次传递。
- 对于
-
通过
sigaction()的sa_mask参数: 使用sigaction()为信号设置处理方式时,还可以通过sa_mask参数指定一组额外的信号。这些信号将在调用信号处理函数时被自动添加到信号掩码中,并在处理函数结束后移除。这种方式允许在处理某一信号时,临时阻塞其他相关的信号,以避免干扰。 -
通过
sigprocmask()系统调用: 除了上述两种方法,Linux 系统还提供了sigprocmask()系统调用,允许程序员在任何时候显式地向信号掩码中添加或移除信号。这种方法非常灵活,适用于需要精细控制信号屏蔽行为的场景。
本篇文章主要介绍**sigprocmask()**函数向信号掩码中添加信号的方式。
sigprocmask() 的函数原型如下:
cpp
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
how: 这个参数指定了对信号掩码的操作行为,有以下三种可能的值:SIG_BLOCK:将set所指向的信号集中的所有信号添加到当前的信号掩码中(即将信号掩码设置为当前掩码与set的并集)。SIG_UNBLOCK:从当前信号掩码中移除set所指向的信号集中的所有信号。SIG_SETMASK:将当前的信号掩码直接设置为set所指向的信号集。
-
set: 指向一个信号集,表示需要添加到(或移除自)信号掩码中的信号。如果set为NULL,则不改变当前的信号掩码。 -
oldset: 如果oldset不为NULL,则在修改信号掩码之前,会将当前的信号掩码保存到oldset指向的信号集中。这对于需要临时修改信号掩码并在之后恢复原来状态的操作非常有用。 -
返回值: 如果调用成功,函数返回
0;如果失败,则返回-1并设置errno来指示错误原因。
以下代码展示了如何使用 sigprocmask() 将信号 SIGINT 添加到进程的信号掩码中,并在之后将其移除。
cpp
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
int main() {
int ret;
sigset_t sig_set;
// 初始化信号集为空
sigemptyset(&sig_set);
// 向信号集中添加 SIGINT 信号
sigaddset(&sig_set, SIGINT);
// 将 SIGINT 添加到进程的信号掩码中(阻塞 SIGINT)
ret = sigprocmask(SIG_BLOCK, &sig_set, NULL);
if (ret == -1) {
perror("sigprocmask error");
exit(EXIT_FAILURE);
}
printf("SIGINT blocked\n");
// 从信号掩码中移除 SIGINT 信号(解除阻塞)
ret = sigprocmask(SIG_UNBLOCK, &sig_set, NULL);
if (ret == -1) {
perror("sigprocmask error");
exit(EXIT_FAILURE);
}
printf("SIGINT unblocked\n");
return 0;
}
信号掩码是 Linux 进程信号处理机制中的一个关键概念。通过 signal()、sigaction() 和 sigprocmask() 等函数,程序可以精确控制哪些信号应该被阻塞、哪些信号应该被传递。理解和灵活运用这些函数,可以帮助开发人员编写更加健壮的信号处理代码,避免信号干扰导致的潜在问题。