目录
1 信号掩码介绍
2 sigprocmas函数
3 sigsuspend函数阻塞等待信号
1 信号掩码介绍
信号掩码(Signal Mask)是操作系统中用于控制进程接收信号的一种机制。每个进程都有一个或多个信号掩码,它们定义了哪些信号在特定时间被阻塞(即暂时忽略),哪些信号可以被进程接收。
向信号掩码中添加一个信号,通常有如下几种方式:
- 当应用程序调用 signal()或 sigaction()函数为某一个信号设置处理方式时,进程会自动将该信号添加到信号掩码中, 这样保证了在处理一个给定的信号时,如果此信号再次发生,那么它将会被阻塞;对于 sigaction(),需要根据 sigaction()函数是否设置了 SA_NODEFER 标志决定该信号添加到信号掩码中;当信号处理函数结束返回后,会自动将该信号从信号掩码中移除。
- 使用 sigaction()函数为信号设置处理方式时,可通过 sa_mask 参数进行设置该组信号是否自动添加到信号掩码中。
- 除了以上两种方式之外,还可以使用 sigprocmask()系统调用,随时可以显式地向信号掩码中添加/移除信号。
2 sigprocmas函数
sigprocmask()
函数用于管理进程的信号屏蔽字(signal mask),即控制哪些信号可以被进程接收,哪些信号被暂时阻塞。函数原型如下所示:
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
-
how
:指定了如何修改信号屏蔽字,可以是以下宏之一:SIG_BLOCK
:将set
指向的信号集中的信号添加到当前信号屏蔽字中。SIG_UNBLOCK
:将set
指向的信号集中的信号从当前信号屏蔽字中移除。SIG_SETMASK
:将当前信号屏蔽字设置为set
指向的信号集。
-
set
:指向sigset_t
结构的指针,该结构定义了一组信号,根据how
参数的值,这些信号将被添加到、移除或设置为当前的信号屏蔽字。 -
oldset
:如果非空,sigprocmask()
将当前的信号屏蔽字存储在oldset
指向的sigset_t
结构中。 - 返回值:成功时返回 0,失败时返回 -1,并设置
errno
以指示错误。
以下是使用 sigprocmask()
的示例代码:
#include <stdio.h>
#include <signal.h>
#include <errno.h>
int main()
{
sigset_t set, oldset, pending;
// 将SIGINT添加到信号集中
sigemptyset(&set);
sigaddset(&set, SIGINT);
// 阻塞SIGINT信号
if (sigprocmask(SIG_BLOCK, &set, &oldset) < 0) {
perror("sigprocmask SIG_BLOCK");
return 1;
}
// ... 执行其他任务,SIGINT信号被阻塞 ...
// 检查SIGINT是否在待处理信号集中
sigpending(&pending);
if (sigismember(&pending, SIGINT)) {
printf("SIGINT is pending.\n");
}
// 恢复原始信号掩码
if (sigprocmask(SIG_SETMASK, &oldset, NULL) < 0) {
perror("sigprocmask SIG_SETMASK");
return 1;
}
printf("SIGINT is no longer blocked.\n");
return 0;
}
程序首先创建并初始化了一个信号集 set
,然后将 SIGINT
信号添加到这个信号集中。接着使用 sigprocmask()
函数调用,通过 SIG_BLOCK
选项来阻塞 SIGINT
信号,并保存旧的信号掩码到 oldset
中。之后,使用 sigpending()
函数来检查 SIGINT
是否在待处理信号集中。最后,我们使用 sigprocmask()
函数调用,通过 SIG_SETMASK
选项来恢复原始的信号掩码,从而解除对 SIGINT
信号的阻塞。运行结果如下:
3 sigsuspend函数阻塞等待信号
sigsuspend()
函数会替换当前进程的信号掩码为函数参数sigmask
指向的信号掩码,并立即挂起调用进程的执行。进程会一直挂起直到有信号到达,并且该信号不在当前的信号屏蔽字中,或者有信号被 sigpending()
函数标记为“待处理”。当信号被处理后,进程恢复执行,并恢复到之前的信号掩码。
可以用一句话进行理解,暂停当前进程直到有指定信号掩码之外的信号触发处理完成。函数原型如下所示:
#include <signal.h>
int sigsuspend(const sigset_t *mask);
- mask: 参数 mask 指向一个信号集。
- 返回值: sigsuspend()始终返回-1,并设置 errno 来指示错误(通常为 EINTR) ,表示被信号所中断,如果调用失败,将 errno 设置为 EFAULT。
调用 sigsuspend()函数相当于以不可中断的方式执行以下操作:
sigprocmask(SIG_SETMASK, &mask, &old_mask);
pause();
sigprocmask(SIG_SETMASK, &old_mask, NULL);
通过下面的的程序来理解函数,以下是使用 sigsuspend()
的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
// 静态信号处理函数定义
static void sig_handler(int sig) {
printf("执行信号处理函数...\n");
}
int main(void) {
struct sigaction sa; // 使用更简洁的变量名
sigset_t new_mask, old_mask, wait_mask;
/* 初始化信号集 */
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT);
sigemptyset(&wait_mask); // 这里可以不用再次初始化,因为sigsuspend不会修改wait_mask
/* 注册信号处理函数 */
sa.sa_handler = sig_handler;
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
/* 向信号掩码中添加信号 */
if (sigprocmask(SIG_BLOCK, &new_mask, &old_mask) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
/* 执行保护代码段 */
puts("执行保护代码段");
/* 挂起、等待信号唤醒 */
// sigsuspend的返回值检查可以省略,因为标准行为是永远不返回
sigsuspend(&wait_mask);
/* 恢复信号掩码 */
if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
perror("sigprocmask");
exit(EXIT_FAILURE);
}
// 正常退出程序
return 0;
}
程序首先声明一个静态的信号处理函数 sig_handler
,该函数在接收到信号时被调用,并打印一条消息。在 main
函数中,程序初始化了三个信号集:
new_mask
用于定义需要被阻塞的信号,old_mask
用于存储当前的信号掩码,- 而
wait_mask
用作sigsuspend
的参数。
使用 sigaction
函数注册了 sig_handler
函数来处理 SIGINT
信号。然后,程序通过 sigprocmask
函数设置 new_mask
为当前信号掩码,以阻塞 SIGINT
信号。接下来,程序执行一个保护代码段,之后调用 sigsuspend
挂起,直到接收到 SIGINT
信号。一旦信号到达,sig_handler
函数被执行,然后程序从 sigsuspend
返回并恢复原始的信号掩码,最后正常退出。