在《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客的sigaction的sa_mask成员,它的类型就是一个信号集,下面我们来介绍它
信号集
信号的总数目达64个,所以不能用一个整数表示它们的集合,int类型通常是4字节32位,位数不够。POSIX标准定义了数据类型sigset_t来表示信号集,并且定义了一系列函数来操作信号集
#include<signal.h>
//初始化一个信号集,使其不包括任何信号
int sigemptyset(sigset_t *set);//初始化一个信号集,使其包括所有信号
int sigfillset(sigset_t *set);//向set指定的信号集中添加由signun指定的信号
int sigaddset(sigset_t *set,int signum);//从set指定的信号集中删除由signum指定的信号
int sigdelset(sigset_t *set,int signum);//测试信号signum是否包括在set指定的信号集中
int sigismember(const sigset_t *set,int signum);
前四个函数在执行成功时返回0,失败返回-1.
sigismember返回1表示测试的信号在信号集中,返回0表示信号不在信号集中,出错返回-1.
注意:在使用信号集前,要对信号集调用一次sigemptyset或sigfillset来初始化它。
信号屏蔽
信号屏蔽又称信号阻塞
#include<signal.h>
int sigprocmask(int how,const sigset_t *set,sigset *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
sigprocmask函数
每一个进程都有一个信号屏蔽码,调用sigprocmask可以修改进程的信号屏蔽码。
当信号被屏蔽时,如果有一个或多个屏蔽的信号到达,它们会被挂起,直到信号解除屏蔽。信号解除屏蔽后,挂起的信号会被递送给进程,但如果信号在解除屏蔽前多次到达,不可靠信号只会递送一次,可靠信号会按次数递送。
how
:此参数确定如何更改信号掩码。它可以取以下值之一:
SIG_BLOCK
:将集合中的信号添加到当前信号掩码。SIG_UNBLOCK
:从当前信号掩码中移除集合中的信号。SIG_SETMASK
:将信号掩码设置为指定的集合。
set
:这是一个指向sigset_t
对象的指针,表示要阻塞、解除阻塞或设置的信号集,具体取决于how
参数的值。
oldset
:这是一个可选的指向sigset_t
对象的指针,用于存储先前的信号掩码。如果为NULL
,则不保存先前的信号掩码。
sigprocmask
的返回值在成功时为 0,在失败时为 -1,同时设置errno
以指示错误。
sigpending函数
函数sigpending函数用来获取调用进程因被阻塞但已经产生的未决信号集。该信号集通过参数set返回。
成功返回0,有错误发生返回-1,错误代码存入errno。
示例程序1
该程序演示了sigprocmask和sigpending的用法
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
//自定义的错误处理函数,这个以前都讲过
void my_err(const char *err_string,int line){fprintf(stderr,"line:%d ",line);perror(err_string);exit(1);
}
//SIGINT的信号处理函数
void handler_sigint(int signo){printf("recv SIGINT\n");
}
int main(int argc,char **argv){//定义了几个信号集sigset_t newmask,oldmask,pendmask;//安装信号处理函数if(signal(SIGINT,handler_sigint)==SIG_ERR)//安装失败的错误处理,这个SIG_ERR在signal这一节也讲过my_err("signal",__LINE__);//这里可以发送SIGINT信号测试一下,这时候是可以响应的sleep(10);sigemptyset(&newmask);//初始化newmasksigaddset(&newmask,SIGINT);//将SIGINT添加进去if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)//尝试将SIGINT添加到进程的屏蔽屏蔽码中去,用的是SIG_BLOCKmy_err("sigprocmask",__LINE__);else printf("SIGINT blocked\n");sleep(10);//睡一会//获取未决的信号队列,我们操作的时候应该在睡眠的时候发送一次SIGINT信号来测试if(sigpending(&pendmask)<0)my_err("sigpending",__LINE__);//用sigismember查看未决信号集里是否有SIGINTswitch (sigismember(&pendmask,SIGINT)){case 0:printf("SIGINT is not in pending queue\n");break;case 1:printf("SIGINT is in pending queue\n");break;case -1:my_err("sigismember",__LINE__);break;default:break;}//再解除对SIGINT的屏蔽,就是把原先的信号屏蔽码又设置回去if(sigprocmask(SIG_SETMASK,&oldmask,nullptr)<0)my_err("sigprocmask",__LINE__);else printf("SIGINT unblocked\n");while(1);return 0;
}
看运行结果,一开始我发送SIGINT,信号处理函数有用,然后SIGINT被阻塞了,这时候我发了四个SIGINT,根据不可靠信号的阻塞,最后只有一个信号会被递送,所以程序只打印了一次recv SIGINT,之后阻塞解除,我再发送SIGINT也可以正常接收。
sigsuspend函数
sigsuspend
函数用于临时修改信号屏蔽字(signal mask)并挂起进程,直到收到一个信号为止。函数的作用可以分为两步:
- 临时替换当前信号屏蔽字为
mask
。- 挂起进程,直到接收到一个信号。
当进程接收到一个信号后,
sigsuspend
会恢复原始的信号屏蔽字,并返回。这个函数通常用于实现原子操作,即在某些关键操作期间阻塞特定的信号,以确保该操作不会被信号中断。
这个函数总是返回-1,并将errno置为EINTR
示例程序2
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<unistd.h>
void my_err(const char *err_string,int line){fprintf(stderr,"line:%d ",line);perror(err_string);exit(1);
}
void handler_sigint(int signo){printf("\nrecv SIGINT\n");
}
int main(int argc,char **argv){sigset_t newmask,oldmask,zeromask;if(signal(SIGINT,handler_sigint)==SIG_ERR)my_err("signal",__LINE__);sigemptyset(&newmask);sigemptyset(&zeromask);sigaddset(&newmask,SIGINT);if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)my_err("sigprocmask",__LINE__);else printf("SIGINT blocked\n");//前面和上一个示例都是一样的/*临界区*///使用sigsuspend取消所有信号的屏蔽并等待信号的触发,因为zeromask是空的信号集if(sigsuspend(&zeromask)!=-1)//sigsuspend总是返回-1my_err("sigsuspend",__LINE__);else printf("recv a signo,return from sigsuspend\n");/*------------------------------//使用sigprocmask加上pause可能会出现错误if(sigprocmask(SIG_SETMASK,&oldmask,nullptr)<0)my_err("sigprocmask",__LINE__);pause();---------------------------*///下面也是一样,恢复屏蔽字if(sigprocmask(SIG_SETMASK,&oldmask,nullptr)<0)my_err("sigprocmask",__LINE__);else printf("SIGINT unblocked\n");while(1);return 0;
}
程序首先用sigprocmask屏蔽掉信号SIGINT,然后使用sigsuspend()取消对所有信号的屏蔽,并挂起等待信号的触发。
然后随便发送一个信号
这是按书上的步骤,不过我觉得问题很大,书上随便发了个SIGRTMIN的信号,但是由于我们没有给这个信号注册信号处理函数,所以进程默认直接结束了。这句话也没有执行 : else printf("recv a signo,return from sigsuspend\n");
这回我们发SIGINT信号
虽然进程一开始屏蔽了SIGINT信号,但是我们调用了sigsuspend函数,并且这个例子里的sigsuspend函数的信号集是空的,它会取消对所有信号的屏蔽,然后挂起,所以我们发送SIGINT进程这时是可以接收的。可以看到进程首先调用了SIGINT的处理函数,之后sigsuspend函数返回-1(函数返回前会把屏蔽字恢复成原来的情况,也就是SIGINT被阻塞),执行了之前没有执行的打印语句。
如果使用注释掉的那段代码,会有风险。如果信号发生在sigprocmask之后pause之前,则这个信号可能就会丢失了,如果信号只发生一次,程序将永远挂起在pause上。