linux:信号之阻塞信号(sigprocmask&sigpending)&捕捉信号(sigaction&signal&pause)

在上一篇博客中,我们认识信号,在此篇博客中我们具体的来看看信号
上一篇博客链接:https://blog.csdn.net/dangzhangjing97/article/details/79969176

一、阻塞信号

1.信号的相关概念

信号递达:实际执行信号的处理动作就是信号递达
信号未决:信号从产生到递达之间的状态就是信号未决(未决就是没有解决)
进程可以选择阻塞某个信号,此信号如果产生的话,那就直接将保持在未决状态,直到进程解除对此信号的阻塞,才会执行递达操作

2.阻塞和忽略的区别

阻塞是在递达之前,只要信号被阻塞,就不会被递达(递达就是处理),
忽略是在递达之后,它是在处理信号时的一种可选动作

3.信号在内核中的表示示意图:

这里写图片描述
ps:每个信号都有两个标志位,分别表示阻塞和未决,还有一个函数指针表示处理动作
这里写图片描述
这里写图片描述
这里写图片描述

4.如果在进程解除对某信号的阻塞之前,这种信号产生过多次,该如何处理?

普通信号(编号为1-31的信号):产生多次只计一次
实时信号(编号为34-64的信号):产生多次时,会依次存放在一个队列里,一个一个来处理

5.收到一个信号的处理过程是:

收到某信号后,把未决信号集中的此信号置为1(1表示未解决的信号),然后去阻塞信号集中查看此信号有没有被阻塞,如果没有被阻塞的话,那就去信号捕捉函数数组中查看该如何处理该信号

6.sigset_t

(1)sigset_t是什么?

是一种数据类型,也是一个信号集

(2)为什么需要sigset_t?

在阻塞信号集中,阻塞和未阻塞用一个bit表示就可以,在未决信号集中也是一样,因此阻塞和未决可以用相同的数据类型sigset_t来存储

(3)sigset_t表示每个信号的“有效”和“无效”,“有效”和“无效”是什么?

1.在阻塞信号集中,有效表示的是该信号被阻塞,无效表示的是该信号没有被阻塞
2.在未决信号集中,有效表示的是该信号没有解决,无效表示的是该信号处于解决状态

6.信号集相关函数

头文件:#include<signal.h>
1.int sigemptyset(sigset_t* set);
功能:初始化信号集(把set所指向的信号集中的所有信号的对应bit清零,表示该信号集不包含任何有效信号)
返回值:成功返回0,失败-1

2.int sigfillset(sigset_t* set);
功能:初始化信号集(把set所指向的信号集中的所有信号的对应bit置为1,表示该信号集包含所有的有效信号)
返回值:成功返回0,失败-1

3.int sigaddset(sigset_t* set,int signo);
功能:添加有效信号signo(把编号为signo的信号在位图中的bit从0变为1)
返回值:成功返回0,失败-1

4.int sigdelset(sigset_t* set,int signo);
功能:删除有效信号signor(把编号为signo的信号在位图中的bit从1变为0)
返回值:成功返回0,失败-1

5.int sigismember(const sigset_t* set,int signo);
功能:用于判断信号集中的有效信号是否包含编号为signor的信号
返回值:包含则返回1,不包含则返回0,出错返回-1

ps:在使用sigset_t类型的变量之前,一定要调用sigemptyset()/sigfillset()做初始化,使信号集处于确定的状态

(1)sigprocmask()(操作阻塞信号集的函数)

功能:可以读取或更改进程的阻塞信号集(信号屏蔽字)
头文件:#include<signal.h>
返回值:成功返回0,失败返回-1
int sigprocmask(int how,const sigset_t* set,sigset_t* oset);

参数说明:
how

想要如何操作信号屏蔽字,此参数有三个可选值:
1.SIG_BLOCK:就是把对应信号的bit位改为1,即就是阻塞该信号
2.SIG_UNBLOCK:就是把对应信号的bit位改为0,即就是使该信号不阻塞
3.SIG_SETMASK:设置当前信号屏蔽字为set所指向的值

set/oset

如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出
如果set是非空指针,则更改当前进程的信号屏蔽字,参数how指示给如何更改
如果set和oset都是非空指针,则oset中保存的是进程原来的信号屏蔽字,然后参数set和how来操作信号屏蔽字 

ps:如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在此函数返回前,至少将其中一个信号递达

(2)sigpending()(操作未决信号集的函数)

功能:读取当前进程的未决信号集,通过参数set传出
头文件:#include<signal.h>
返回值:成功返回0,失败返回-1
int sigpending(sigset_t* set);

示例

1.阻塞2号信号

#include<stdio.h> 
#include<signal.h>

void printpending(sigset_t* set)
{
    int i=1;
    //打印未决信号集
    for(;i<32;i++)
    { 
        //ismember()是判断某信号是否在目标集合中
        if(sigismember(set,i))
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }   
        printf(" ");
    }
    printf("\n");
}


int main()
{
    sigset_t s,p;//定义信号集对象
    sigemptyset(&s);//清空信号集,即对信号集做初始化
    sigaddset(&s,SIGINT);//给信号集中添加2号信号SIGINT
    sigprocmask(SIG_BLOCK,&s,NULL);//设置阻塞信号集
    while(1)
    {
        sigpending(&p);//读取未决信号集
        printpending(&p);//打印未决信号集
        sleep(1);//每隔一秒把所有信号的状态都打印一次
    }
    return 0;
}

运行结果:
这里写图片描述
结果解析:2号信号的作用是中止进程,但是现在阻塞了2号信号,所以当程序在运行时,按下ctrl -c不起作用,中止不了进程,此时没有阻塞SIGQUIT,所以ctrl -\可以中止程序

2.阻塞2号信号和3号信号

#include<stdio.h> 
#include<signal.h>

void printpending(sigset_t* set)
{
    int i=1;
    //打印未决信号集
    for(;i<32;i++)
    { 
        //ismember()是判断某信号是否在目标集合中
        if(sigismember(set,i))
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }   
        printf(" ");
    }
    printf("\n");
}


int main()
{
    sigset_t s,p;//定义信号集对象
    sigemptyset(&s);//清空信号集,即对信号集做初始化
    sigaddset(&s,SIGINT);//给信号集中添加2号信号SIGINT
    sigaddset(&s,SIGQUIT);//给信号集中添加3号信号SIGQUIT
    sigprocmask(SIG_BLOCK,&s,NULL);//阻塞2号信号
    while(1)
    {
        sigpending(&p);//读取未决信号集
        printpending(&p);//打印未决信号集
        sleep(1);//每隔一秒把所有信号的状态都打印一次
    }
    return 0;
}

结果解析:
这里写图片描述
结果解析:此时阻塞了SIGINT和SIGQUIT,这个信号的作用都是中止进程,此时分别按下这两个信号都不起作用,所以只能采用kill来中止进程了

3.阻塞2号信号、3号信号、9号信号

#include<stdio.h> 
#include<signal.h>

void printpending(sigset_t* set)
{
    int i=1;
    //打印未决信号集
    for(;i<32;i++)
    { 
    //ismember()是判断某信号是否在目标集合中
        if(sigismember(set,i))
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }   
    printf(" ");
    }
    printf("\n");
}


int main()
{
    sigset_t s,p;//定义信号集对象
    sigemptyset(&s);//清空信号集,即对信号集做初始化
    sigaddset(&s,SIGINT);//给信号集中添加2号信号SIGINT
    sigaddset(&s,SIGQUIT);//给信号集中添加3号信号SIGINT
    sigaddset(&s,SIGKILL);//给信号集中添加9号信号SIGINT
    sigprocmask(SIG_BLOCK,&s,NULL);//设置阻塞信号集
    while(1)
    {
        sigpending(&p);//读取未决信号集
        printpending(&p);//打印未决信号集
        sleep(1);//每隔一秒把所有信号的状态都打印一次
    }
    return 0;
}

运行结果:
这里写图片描述
结果解析:我们理论上是阻塞SIGINT、SIGQUIT、SIGKILL,但从执行结果来看SIGKILL没有被阻塞,因为信号SIGKILL不能被阻塞或者忽略,故可以用SIGKILL来结束进程

二、捕捉信号

如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号

1.信号被捕捉的流程

这里写图片描述
ps:主控制流程(例如main函数)和信号自定义的处理函数是使用不同的堆栈空间的,它们之间不存在调用和被调用的关系,是两个独立的控制流程

2.相关函数

(1)sigaction()

头文件:#include<signal.h>
功能:读取和修改与指定信号相关联的处理动作
返回值:成功返回0,失败返回-1
int sigaction(int signo,
              const struct sigaction*act,
              struct sigaction* oact     );
参数说明:
1.signo:是指定信号的编号
2.act:指向sigaction结构体,若此指针非空,则根据act修改信号的处理动作
3.oact:指向sigaction结构体,若此指针非空,则通过oact传出该信号原来
        的处理动作

结构体sigaction

struct sigaction{
void (*sa_handler)(int);
void (*sa_sigaction)(int,siginfo_t*,void*);//是实时信号的处理函数
sigset_t sa_mask;//在信号被处理的过程中,指定那些信号被阻塞
                 //注意:默认当前信号本身被阻塞
int sa_flags;//一般设为0

sa_handler

sa_handler有三种形式:
1.给sa_handler赋值为常数SIG_ING传给sigaction函数表示忽略信号

2.给sa_handler赋值为常数SIG_DFL表示执行系统默认动作

3.给sa_handler赋值为一个函数指针的话,表示用户自定义捕捉信号后者说是向
内核注册了一个信号处理函数,此函数的返回值是void,可以一个int参数,通过
参数可以得知当前信号的编号,这样就可以使用一个函数处理多个信号
ps:此函数不是被main函数调用的,而是被系统调用

示例1

#include<stdio.h>
#include<signal.h>
void handler(int sig)
{
    printf("hello\n");
}
int main()
{
    struct sigaction act;
    act.sa_handler=handler;//自定义信号处理方法
    act.sa_flags=0;
    alarm(1);//设置闹钟
    sigaction(SIGALRM,&act,NULL);//捕捉闹钟信号
    while(1)
    {
        printf("haha\n");
        usleep(100000);
    }
    return 0;
}

运行结果:
这里写图片描述
解析:给程序设置了闹钟信号,对信号的处理方式是自定义的,当1秒钟过后,处理此信号,输出“hello”

示例2

#include<stdio.h>
#include<signal.h>
int main()
{
    struct sigaction act;
    act.sa_handler=SIG_DFL;//对此信号的处理采取默认行为
    act.sa_flags=0;
    alarm(1);//设置闹钟
    sigaction(SIGALRM,&act,NULL);//捕捉闹钟信号
    while(1)
    {
        printf("haha\n");
        usleep(100000);
    }
    return 0;
}

运行结果:
这里写图片描述
解析:此信号的默认处理行为是当alarm函数设置的时间过后,直接终止进程

(2)signal()

功能:设置信号的处理方式

头文件:#include<signal.h>
void(* signal(int signum,void(*hander)(int))(int)
对于此函数,我们经常这样使用:
    typedef void(*sighandler_t )(int);
    sighandler_t signal(int signum,sighandler_t handler);
参数说明:
signum:是要进行处理的信号的编号
handler:有三种形式的取值
  (1)SIG_IGN:忽略指定的信号
  (2)SIG_DFL:对指定的信号采取默认行为
  (3)对指定的信号采取自定义的处理办法

返回值

signal返回一个函数指针void(*)(int),即signal函数对指定的信号自定义
处理函数handler,如果定义成功,则会返回处理函数的指针

示例1:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void Hander(int sig)
{
    printf("hahahha\n");
    exit(1);
}
int main()
{
    signal(SIGINT,Hander);
    while(1)
    {
        printf("hello\n");
        usleep(100000);
    }
    return 0;
}

运行结果:
这里写图片描述
解析:signal函数对SIGINT的处理方式自定义方法,输出”hahahha“,当我们按键产生2号信号—>SIGINT时,此信号就会被捕捉,从而输出我们想要的结果,并且终止进程

示例2:

#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main()
{
    signal(SIGINT,SIG_IGN);
    while(1)
    {
        printf("hello\n");
        usleep(100000);
    }
    return 0;
}

运行结果:
这里写图片描述
解析:我们对信号SIGINT的处理方法是忽略,故当按键产生此信号时,操作系统并不会处理此信号,当然,我们可以使用SIGQUIT信号来终止进程,因为此信号并没有被忽略哦

sigaction()与signal()的区别:

1.sigaction是UNIX早期的函数,sigaction的设计意图是取代signal;
2.saignal只能修改信号处理的动作,sigaction既能修改也能读取信号处理的动作;
3.sigaction接口更丰富,可以设置更多的选项,功能更强大

(3)pause()

头文件:#include<unistd.h>
功能:使调用进程挂起直到有信号递达
     (使当前进程暂停(进入睡眠状态),直到被信号中断)
int pause(void)

返回值:

   1.如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;

   2.如果信号的处理动作是忽略,则进程继续处于挂起状态
     pause函数不返回;

   3.如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1, 
     errno设置为EINTR

故pause函数只有出错的返回值

模拟实现sleep()

#include<stdio.h>
#include<signal.h>
void sig_alrm(int sig)
{
    printf("haha\n");
}
int mysleep(int second)
{
    struct sigaction new,old;
    new.sa_handler=sig_alrm;
    new.sa_flags=0;
    sigaction(SIGALRM,&new,&old);//注册信号处理函数
    alarm(second);
    pause();
    sigaction(SIGALRM,&old,NULL);//恢复信号的默认处理动作
    return;
}
int main()
{
    printf("before sleep\n");
    mysleep(2);
    printf("after sleep\n");
    return 0;
}

这里写图片描述

如果不注册信号处理函数,会出现以下的情况:

#include<stdio.h>
#include<signal.h>
int mysleep(int second)
{
    struct sigaction new,old;
    new.sa_handler=SIG_DFL;
    new.sa_flags=0;
    sigaction(SIGALRM,&new,&old);//注册信号处理函数
    alarm(second);
    pause();
    sigaction(SIGALRM,&old,NULL);//恢复信号处理函数
    return;
}
int main()
{
    printf("before sleep\n");
    mysleep(2);
    printf("after sleep\n");
    return 0;
}

运行结果:
这里写图片描述

解析:如果不注册信号处理函数,而是执行信号的默认的处理方式,就会打印
Alarm sleep,不会执行mysleep()后面的代码,而是直接结束程序,因为
SIGALRM信号的默认处理方式是直接终止进程

猜你喜欢

转载自blog.csdn.net/dangzhangjing97/article/details/79981181
今日推荐