信号驱动任务指的是通过信号来驱动任务的执行,每发送一次信号,任务就执行一次。实现该目的所需的函数就是 pause 或者 sigsuspend,pause和sigsuspend函数可以暂停当前进程,直至收到信号才会继续运行之后的程序。
目录
1、认识 pause / sigsuspend 函数
(1) pause 函数
(2) sigsuspend 函数
2、信号驱动任务执行的两种方式
(1) sigprocmask + pause
(2) sigsuspend
1、认识 pause / sigsuspend 函数
(1) pause 函数
pause 函数的作用是暂停当前进程(进入休眠状态),直至收到信号(任意信号),才会唤醒当前进程。
因为信号的处理动作有终止、忽略、捕捉、屏蔽,所以也对应了下面四种情况:
- 信号的默认处理动作是终止,进程直接终止。
- 信号的默认处理动作是忽略,进程继续处于挂起状态。
- 信号的默认处理动作是捕捉,进程先调用信号处理函数,然后解除挂起,执行下一步。
- 信号的默认处理动作是屏蔽,进程继续处于挂起状态。
(2) sigsuspend 函数
sigsuspend函数的作用也是暂停当前进程直至收到信号,但 sigsuspend 还可以主动设置屏蔽哪些信号,收到这些被屏蔽的信号时,不会解除挂起。
参数 mask:需要屏蔽的信号集。当前进程的屏蔽字会被替换为当前参数的屏蔽信号集,等到函数返回,会还原当前进程的屏蔽字。
- mask == NULL:sigsuspend达到的效果和pause一样
- mask != NULL:通过设置屏蔽信号集来主动屏蔽某些信号,收到这些被屏蔽的信号时,不会解除挂起
返回值:只会返回 -1。
2、信号驱动任务执行的两种方式
下面就以 2号信号为例,实现信号驱动任务执行。2号信号 SIGINT 可以终止前台进程,我们需要捕获2号信号,并设置相应的信号处理函数来避免当前进程被终止。
(1) sigprocmask + pause
执行任务的时候,我们不希望任务执行到一半被打断,所以在执行任务的之前我们需要调用 sigprocmask函数来屏蔽未来会收到的信号(当前场景下指的就是2号)
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int signum)
{
printf("收到并处理%d号信号\n", signum);
}
int main(){
signal(2, handler); // 捕捉2号信号
sigset_t set;
sigemptyset(&set); // 清空信号集
sigaddset(&set, 2); // 向信号集中添加2号信号
while(1){
sigprocmask(SIG_BLOCK, &set, NULL); // 屏蔽2号信号
printf("------------------\n");
printf("task is running\n");
printf("------------------\n");
sleep(1);
sigprocmask(SIG_UNBLOCK, &set, NULL); // 解除2号信号的屏蔽
pause();
}
return 0;
}
正常情况应该是,解除2号信号的屏蔽以后,pause函数挂起的时候捕捉到了信号,此时会先去调用信号处理函数,然后解除挂起就会重新去打印“task is running” 。然而,从下面的结果可以看出,只执行了信号处理函数,但是pause函数没有解除挂起。
原因就是,由于发送信号较为频繁,在打印“task is running” 的时候收到了2号信号,但是此时2号信号被屏蔽了,解除2号信号的屏蔽以后,立马就去调用信号处理函数了,等到pause函数挂起以后,信号就已经处理完了。
因此,当信号发送较为频繁的时候,不建议使用 pause函数 来驱动任务执行。
(2) sigsuspend
sigsuspend 函数被调用以后,直接进入阻塞等待的状态
- 如果收到了屏蔽信号集中的信号,不予理会,继续挂起
- 如果收到了屏蔽信号集以外的信号,解除挂起,并调用对应的信号处理函数 或者 执行默认处理行为。
假设 sigsuspend 函数的屏蔽信号集中包含了3号信号,那么sigsuspend收到3号信号就不会解除挂起;如果收到了2号信号,那么sigsuspend函数就会解除挂起,并调用对应的信号处理函数。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int signum)
{
printf("收到并处理%d号信号\n", signum);
}
int main(){
signal(2, handler); // 捕捉2号信号
sigset_t set;
sigemptyset(&set); // 清空信号集
sigaddset(&set, 2); // 向信号集中添加2号信号
sigset_t mask; // sigsuspend的屏蔽信号集
sigemptyset(&mask);
sigaddset(&mask, 3); // 向屏蔽信号集添加3号信号
while(1){
sigprocmask(SIG_BLOCK, &set, NULL); // 屏蔽2号信号
printf("------------------\n");
printf("task is running\n");
printf("------------------\n");
sleep(1);
sigsuspend(&mask); // 挂起状态:将进程屏蔽字替换为屏蔽信号集mask
// 解除挂起:恢复原本的进程屏蔽字
}
return 0;
}
sigsuspend之所以不会像pause那样,是因为sigsuspend函数解除挂起以后的动作是原子的,解除挂起以后,再调用信号处理函数,保证了下一次任务的执行;pause采用的方式是先解除屏蔽,再解除挂起,这就存在一个问题,如果信号在解除挂起之前就被处理了,那么pause函数根本就收不到信号。