信号量一般用于进程之间通信,在c++关于信号量及其相关函数的定义均声明在signal.h
中(linux系统)。信号集是信号量的集合,可以使用sigset_t
进行定义,我们一般需要关注的信号集为阻塞集与未决集,阻塞集指明将哪些信号阻塞,集合中对应位置1,表示阻塞该信号。未决集指明哪些信号还未处理,对应位为1,指该信号还未从处理。
sigaction()函数
该函数的作用对象为单个信号,功能是对信号作出相应的处理动作,函数原型为
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
- 第一个参数是要捕获的信号,比如
SIGCHLD
就是子进程死亡之后发出的信号,填入之后,父进程就会监视并捕获该信号以作出相应的动作。 - 第二个参数是一个结构体(下文会详细解释该结构体),其主要的作用就是指明捕获到信号之后要执行的动作。
- 第三个参数是保存与该信号之前的动作,如果不保存可以直接置空。
sigaction
的结构体声明如下:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void*);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
对于上述的结构体我们只关注三个属性:
-
void (*sa_handler)(int)
:该函数指针指向与信号绑定的动作函数,即只要信号被捕获,就执行该函数指针指向的函数。 -
sigset_t sa_mask
:设置阻塞集,表示执行信号动作时需要阻塞哪些信号,在c++里默认新到来的信号会阻塞之前的信号,也就是说如果进程正在执行信号A的处理函数,此时又捕获到一个信号B,B会将A阻塞,进程会暂停A的处理函数转而执行B的处理函数,之后再回到断口处,继续执行A的处理函数。如果希望在处理A的处理函数时不被打断,就需要设置阻塞集,表示阻塞那些函数,如果不需要阻塞,可以使用sigemptyset()
将阻塞集置空就好。 -
int sa_flags
:特殊标志,一般置0,如果置1,会选择另一种结构体,不予考虑。
信号集相关函数
上节描述了如何使用sigaction
捕获信号并触发相应的动作,但是在一个进程中往往不止有一个信号,这就涉及信号的阻塞和排队问题,默认情况下,新捕获的信号会打断旧信号的处理函数的执行,当然,signal.h
头文件中定义了一系列关于信号集的操作,来帮助我们顺畅的管理信号集。
int sigemptyset(sigset_t *set)
该函数将传入的信号集所有位初始化为0,一般用于信号集的初始化,不做修改将不阻塞而任何信号。
int sigfillset(sigset_t *set)
该函数将传入的信号集所有初始化为1,一般用于初始化,不做修改将阻塞所有信号。
int sigaddset(sigset_t *set, int signum)
该函数将指定信号加入信号集,参数一为传入的信号集,参数二signum
为指定的要加入到set
中的信号。
int sigdelset(sigset_t *set, int signum)
该函数将指定信号从信号集中删除,即将信号signum
从信号集set
中删除
int sigismember(const sigset_t *set, int signum)
该函数判断传入的信号集中上是否包含某特定信号,即判断信号集set
中是否包含信号signum
sigprocmask
上一小节中介绍了信号集与其基本的操作(包括初始化、插入、删除等),我们创建信号集的目的是为了帮助流畅的捕获信号,所以我们需要把创建好的信号集作为一种配置来控制信号的捕获顺序,以免信号的默认中断机制打断我们对某一信号的持续捕获。signal.h
允许我们使用sigprocmask函数将自定义信号集作为阻塞的配置。该函数原型为:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset)
参数解析:
-
int how
:该参数表示操作类型,即要对传入的信号集做什么操作,是将信号集加入阻塞,还是为该信号集中的信号清除阻塞状态,该参数有三个值可选:SIG_BLOCK
--表示求set
与原生的信号集的并集作为阻塞集,即将set
中的信号加入阻塞;SIG_UNBLOCK
--表示求原生信号集与set
的差集作为阻塞集使用,即将set
中的信号清除阻塞状态。SIG_SETMASK
--将set
信号集将原生的阻塞信号集覆盖,直接作为阻塞集使用(用的很少)。 -
const sigset_t *set
:传入的自定义的信号集 -
sigset_t *oldset
:该参数保存原生的信号阻塞集,不保存可以置空
代码演示
#include <unistd.h>
#include <iostream>
#include <signal.h>
#include <wait.h>
void alprint(int num){ //信号处理函数
for(int i=0;i<num;++i){
sleep(1);
std::cout<<num<<" "<<i<<std::endl;
}
}
int main(){
sigset_t set;//创建信号集
sigemptyset(&set);//将信号集初始化为空
sigaddset(&set,SIGINT);//将键盘中断信号(ctrl-c)加入信号集
sigprocmask(SIG_BLOCK,&set,NULL);//加入阻塞集
struct sigaction act; //定义信号处理结构体
act.sa_handler = alprint; //绑定信号处理函数
sigemptyset(&act.sa_mask); //清空阻塞集
act.sa_flags = 0;
int num; //要传入的信号
std::cin>>num;
sigaction(num,&act,NULL); //捕获信号,并执行处理程序
raise(num); //发出信号
return 0;
}
在上述程序中,我们将键盘中断信号(ctrl-c
)加入阻塞集,然后对我们键入的数字num
进行捕捉并打印num
次num
的值,并且打印期间不会被ctrl-c
打断。实验步骤:运行上面的程序(linux下),输入一个数字(稍微大点,好观察)然后回车,发现终端在打印刚刚输入的数字,此时按下ctrl-c
,发现无法打断程序的执行,实验结束(可以按ctrl-z
来结束程序)。