《Linux C编程实战》笔记:信号的屏蔽

在《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客的sigaction的sa_mask成员,它的类型就是一个信号集,下面我们来介绍它

信号集

信号的总数目达64个,所以不能用一个整数表示它们的集合,int类型通常是4字节32位,位数不够。POSIX标准定义了数据类型sigset_t来表示信号集,并且定义了一系列函数来操作信号集

#include
//初始化一个信号集,使其不包括任何信号
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
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
#include
#include
#include
//自定义的错误处理函数,这个以前都讲过
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);//初始化newmask
    sigaddset(&newmask,SIGINT);//将SIGINT添加进去
    if(sigprocmask(SIG_BLOCK,&newmask,&oldmask)<0)//尝试将SIGINT添加到进程的屏蔽屏蔽码中去,用的是SIG_BLOCK
        my_err("sigprocmask",__LINE__);
    else printf("SIGINT blocked\n");
    sleep(10);//睡一会
    //获取未决的信号队列,我们操作的时候应该在睡眠的时候发送一次SIGINT信号来测试
    if(sigpending(&pendmask)<0)
        my_err("sigpending",__LINE__);
    //用sigismember查看未决信号集里是否有SIGINT
    switch (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;
}

《Linux C编程实战》笔记:信号的屏蔽_第1张图片

看运行结果,一开始我发送SIGINT,信号处理函数有用,然后SIGINT被阻塞了,这时候我发了四个SIGINT,根据不可靠信号的阻塞,最后只有一个信号会被递送,所以程序只打印了一次recv SIGINT,之后阻塞解除,我再发送SIGINT也可以正常接收。

sigsuspend函数

sigsuspend 函数用于临时修改信号屏蔽字(signal mask)并挂起进程,直到收到一个信号为止。

函数的作用可以分为两步:

  1. 临时替换当前信号屏蔽字为 mask
  2. 挂起进程,直到接收到一个信号。

当进程接收到一个信号后,sigsuspend 会恢复原始的信号屏蔽字,并返回。这个函数通常用于实现原子操作,即在某些关键操作期间阻塞特定的信号,以确保该操作不会被信号中断。

这个函数总是返回-1,并将errno置为EINTR

示例程序2

#include
#include
#include
#include
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总是返回-1
        my_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()取消对所有信号的屏蔽,并挂起等待信号的触发。

《Linux C编程实战》笔记:信号的屏蔽_第2张图片

然后随便发送一个信号

这是按书上的步骤,不过我觉得问题很大,书上随便发了个SIGRTMIN的信号,但是由于我们没有给这个信号注册信号处理函数,所以进程默认直接结束了。这句话也没有执行 :  else printf("recv a signo,return from sigsuspend\n");

这回我们发SIGINT信号

《Linux C编程实战》笔记:信号的屏蔽_第3张图片

虽然进程一开始屏蔽了SIGINT信号,但是我们调用了sigsuspend函数,并且这个例子里的sigsuspend函数的信号集是空的,它会取消对所有信号的屏蔽,然后挂起,所以我们发送SIGINT进程这时是可以接收的。可以看到进程首先调用了SIGINT的处理函数,之后sigsuspend函数返回-1(函数返回前会把屏蔽字恢复成原来的情况,也就是SIGINT被阻塞),执行了之前没有执行的打印语句。

如果使用注释掉的那段代码,会有风险。如果信号发生在sigprocmask之后pause之前,则这个信号可能就会丢失了,如果信号只发生一次,程序将永远挂起在pause上。

你可能感兴趣的:(笔记,c语言,linux)