Linux应用---信号

写在前面:在前面的学习过程中,我们学习了进程间通信的管道以及内存映射的方式。这次我们介绍另外一种应用较为广泛的进程间通信的方式——信号。信号的内容比较多,是学习的重点,大家一定要认真学,多多思考。

一、信号概述

        信号是linux进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,他是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。

        发往进程的诸多信号,通常都是源于内核,引发内核为进程产生信号的各类事件如下:(产生的几种方式)
        1、对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号,比如ctrl+c通常会给进程发送一个中断信号;
        2、硬件发生异常,即硬件检测到一个错误条件并通知内核,随机再有内核发送相应的信号给相关的进程,
        3、比如执行一条异常的机器语言指令,诸如被0除,或者引用了无法访问的内存区域。
        4、系统状态变化,比如alarm定时器到期将引起SIGALM信号,进程执行CPU时间超限,或者该进程的某个子进程退出。
        5、运行kill命令,调用kill函数。

        使用信号的两个目的:1、让进程知道已经发生了的一个特定的事情;2、强迫进程执行它自己代码中的信号处理程序。

信号作为进程间通信的特点:
   简单、不能携带大量信息、满足某些特定条件才发送、优先级比较高。

查看系统定义的信号列表:kill –l

共有62个信号,其中前 31 个信号为常规信号,其余为实时信号。

 结合上表,我们总结出信号的5种默认处理动作:

1、 Term:终止进程;

2、 Ign:当前进程忽略掉这个信号;

3、 Core:终止进程,并生成一个core文件。core文件保存进程异常退出的信息。调试的时候会产生错误的信息。

4、stop:暂停当前进程;

5、count:继续执行当前被暂停的进程。

信号的几种状态:产生、未达、递达;

二、信号相关的函数

 2.1 kill()函数

 int kill(pid_t pid ,int sig);

涉及的头文件:

      #include <sys/types.h>

      #include <signal.h>

功能:

        给任何的进程或者进程组pid,发送任意1个信号sig;把信号发送给进程。

参数:     

        -pid:需要发送给的进程的id;

                pid>0,将信号发送给指定id的进程;

                pid=0,将信号发送给当前的进程组的所有进程;

                pid=-1,将信号发送给每一个有权限接收这个信号的进程。

                pid<-1,这个pid就等于某个进程组的id取反。

        -sig:需要发送的信号的编号,或者是宏值。如果是0就不发送任何信号。

返回值:

              成功返回0;

              失败返回-1;

案例:创建一个子进程,在子进程中每一秒打印一句“child process!\n”共计5次。在父进程中打印"parent process!\n",并在两秒后打印"kill child process now\n",然后关闭子进程。

#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
int main()
{
    pid_t pid =fork();
    if(pid ==0)
    {
        //子进程
        int i;
        for(i=0;i<5;i++)
        {
            printf("child process!\n");
            sleep(1);
        }
    }
   else if(pid>0)
   {
        //父进程
        printf("parent process!\n");
        sleep(2);
        printf("kill child process now\n");
        kill(pid,SIGINT);        
   }
    return 0;
}

运行结果:

结果分析

         在上述代码中,子进程并没有按照要求打印出5次,而是两秒后只打印了两次,这是因为在父进程中使用了kill函数,两秒后,将SIGINT信号发送给子进程,SIGINT的作用就是终止进程,所以有了上述结果。

2.2 raise()函数、abort()函数

int raise(int sig)

涉及的头文件:

      #include <signal.h>

功能:

        给当前进程发送信号。

参数: 

        sig:要发送的信号;   

 返回值:

                成功返回0;

                失败返回非0;

void abort(void);

功能:

        发送SIGABRT信号给当前的进程,杀死当前的进程。并产生core文件;

2.3 alarm()函数

unsigned int alarm(unsigned int seconds);

涉及的头文件:

      #include <unistd.h>

功能:

        设置定时器(闹钟),函数调用后,开始倒计时,当倒计时为0时,函数会给当前的进程发送一个信号:SIGALRM;

参数: 

        seconds:倒计时的时长,单位是s,如果参数为0,定时器无效(不进行倒计时,不发送信号);

 返回值:

                之前没有定时器,返回0;(第一次使用alarm函数);

                之前有定时器,返回之前定时器剩余的秒数;

注:

      1、  SIGALARM:该信号作用,默认终止当前的进程。

      2、    每一个进程都有且只有唯一的一个定时器。当多个定时器时,会将前面的定时器进行覆盖。例如:

        alarm(10);

        过了1秒;

        alarm(5);定时5秒,把之前的覆盖了。

      3、alarm(100):该函数是不阻塞的。

案例:利用alarm函数通过定时器实现进程的结束,并且验证多个定时器的覆盖现象;

#include <unistd.h>
#include <stdio.h>

int main()
{
    int seconds = alarm(5);
    printf("seconds=%d\n",seconds);
    sleep(2);

    seconds= alarm(2);
    printf("seconds=%d\n",seconds);

    while(1)
    {

    }

    return 0;
}

运行结果:

结果分析:

        首先在进程中创建一个定时器为5秒的alarm函数,并且打印其返回值,由于是第一次创建,其返回值为0;两秒后,创建第二个定时器,时间为2秒,并打印其返回值,返回值为前一次定时器的剩余秒数为3秒。然后两秒后,第二次定时器发送SIGALRM信号,结束进程。       

 2.4 setitimer()函数

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

涉及的头文件:

         #include <sys/time.h>

功能:

      设置定时器,可以代替alarm函数,精度比alarm高,精度在us,可以实现周期性定时;什么叫周期性定时呢?就是反复的有周期性的发送信号。 

参数: 

       - which定时器以什么时间计时;

            ITIMER_REAL:真实时间,时间到达,发送SIGALRM信号   常用;

            ITIMER_VIRTUAL:用户时间,时间到达,发送SIGVTALRM;

            ITIMER_PROF:以该进程在用户态和内核态下所消耗的时间计算,时间到达,发送SIGPROF;        

       -new_value设置定时器的属性, 也就是定时器间隔的时间以及定时的时间;       

                struct itimerval

                             {                       //定时器的结构体

                            struct timeval it_interval; //每个阶段的时间,间断的时间;

                            struct timeval it_value;   //延时多长时间执行定时器

                            };

                 struct timeval { //时间的结构体

                            time_t      tv_sec;        //秒数

                            suseconds_t tv_usec;       //微秒

                        };

          例如:过10秒后,每隔2秒定时一次;  it_interval =2; it_value =10;

        -old_value:记录上次的定时的时间参数信息,一般不使用,指定NULL;

返回值:

            成功返回0,失败返回-1,并设置错误号;

:setitime()页非阻塞,而且第一次间隔时间到就发送信号;

案例:

利用setitimer函数进行定时,过3秒后开始,每2秒定一次(发送一次信号);

 #include <sys/time.h>
    #include <stdio.h>
    #include <stdlib.h>
    //过3秒后,每隔两秒定时一次;到3s后,第一次定时。
    int main()
    {
        struct itimerval new_value;
        new_value.it_interval.tv_sec=2;
        new_value.it_interval.tv_usec=0;

        new_value.it_value.tv_sec=3;
        new_value.it_value.tv_usec=0;
    
       int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
       if(ret==-1)
       {
            perror("setitimer");
            exit(0);
       }
        getchar();//让程序停留在这
        return 0;
    }

运行结果: 

结果分析:

         在3s后setitimer函数进行第一次定时,发送信息,然后结束进程,并没有收到周期性的信号。

set与alarm的区别,

        alarm:只能定一次时,时间一到,终止当前的进程。

        setitimer:可以周期性的定时。但是前面我们并没有看到周期性信号,因为在第一次定时的时候,进程就已经终止了,如果我们要看到周期性的信号就需要进行信号捕捉,自己处理信号。

2.5 signal()函数

        前面我们说了setitimer函数能够进行周期性的定时,也就是周期性的发送信号,但是在程序测试的时候,第一次信号到来之后,进程就已经结束了,这就无法让我们看到后面的周期性信号。那如何解决这个问题?就要用到信号的捕捉行为,当捕捉到信号后,不让进程结束,而是干其他的事情,这样就能检测到周期性信号的问题。

sighandler_t signal(int signum, sighandler_t handler);

涉及的头文件:

        #include <signal.h>

功能:

      设置某个信号的捕捉行为。

参数: 

            -signum:要捕捉的信号;例如:SIGALRM;

            -handler:捕捉到信号要如何处理

                        -SIG_IGN:忽略信号;

                        -SIG_DFL:使用信号默认的行为;

                        -回调函数:这个函数是内核调用,程序员只负责写,捕捉到信号后,如何处理函数。                   

返回值:

            成功了,返回前一次注册的信号处理函数的地址。第一调用返回NULL;

            失败,返回SIG_ERR设置错误号。

: 

1、回调函数:需要程序员实现,提前准备好,函数的类型根据实际需求,看函数指针的定义。不是程序员调用,当信号产生,由内核调用。函数指针是实现回调的手段,函数实现后,将函数名放到函数指针的位置。

2、 SIGKILL and SIGSTOP :不能被捕捉,不能被忽略;

案例:

        利用setitimer函数进行定时,3秒后开始,然后每隔2秒后进行定时,定时会发送SIGALRM信号,如果不进行信号捕获就会结束进程,然后我们利用signal函数进行信号捕获,这函数回调中进行打印"捕捉到信号的编号是:%d\n",num;

    #include <sys/time.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    //过3秒后,每隔两秒定时一次;到3s后,第一次定时。

    void myalarm(int num)
{
    printf("捕捉到信号的编号是:%d\n",num);
    printf("xxxxx\n");
}
    int main()
    {
        //注册信号捕捉
           // signal(SIGALRM,SIG_DFL);
           // signal(SIGALRM,SIG_IGN);
           //  void (*sighandler_t)(int);函数指针;int类型参数,表示捕捉到的信号的值;
         signal(SIGALRM,myalarm);

        struct itimerval new_value;
        new_value.it_interval.tv_sec=2;
        new_value.it_interval.tv_usec=0;

        new_value.it_value.tv_sec=3;
        new_value.it_value.tv_usec=0;
    
       int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
       printf("定时器开始:\n");
       if(ret==-1)
       {
            perror("setitimer");
            exit(0);
       }
        getchar();

        return 0;
    }

运行结果:

 结果分析:

        在由setitimer定时器发送SIGALRM信号后,利用signal函数进行信号的捕捉,在3秒后第一次捕捉,然后以2秒为周期进行周期性捕捉,并将捕捉的结果打印出来;

在信号捕捉注册时,共有三种选择,我们本次选择第三种,回调函数,并在回调函数中打印信号,也可以使用其他两种:

        -SIG_IGN:忽略信号;

        -SIG_DFL:使用信号默认的行为;

在信号捕获方面,还有另外一个信息捕捉函数:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

但是这个函数涉及到信号集的知识,因此我们先对其不进行介绍,先介绍信号集的相关知识。

三、信号集及相关函数

3.1信号集

        许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。可以简单认为是一个有特殊意义的数组。

        在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为“未决信号集” 。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数来对 PCB 中的这两个信号集进行修改。

        信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。

        信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

        信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作;

例如:

       1、 当用户通过键盘ctrl+c,会产生一个2号信号SIGINT(信号被创建);

       2、  信号产生但是没被处理(其处于未决状态);
                 -在内核中将所有的没有被处理的信号存储在一个集合中(未决信号集)
                    -SIGINT信号状态被存储在第二位。
        这个标志位为0说明信号不是未决状态,这个标志位的值为1,说明信号处于未决状态。

       3、这个未决状态的信号,需要被处理,处理之前需要和另一个信号集--阻塞信号集。进行一个比较阻塞信号集默认是不阻塞的,但是如果用户可以通过系统调用API进行某个信号的阻塞。(具体如何设置信号的阻塞,后面两节我们在细说)。

        4、在处理的时候,需要和阻塞信号集中的标志位进行查询,看是不是对该信号设置阻塞了。
            -如果没有阻塞,这个信号就被处理
            -如果阻塞这个信号继续处于“未决”状态,直到阻塞解除,这个信号被处理。

        这样来看,阻塞就相当于开关,而未决相当于一种状态。

3.2相关函数

以下信号集相关的函数,都是对自定义的信号集进行操作;

涉及头文件:

         #include <signal.h>

函数: int  sigemptyset(sigset_t *set);

        功能:清空信号集中的数据,将信号集中的所有的标志位置为0;

        参数:sigset_t *set 传出参数,需要操作的信号集;

        返回值:成功返回0;失败返回-1;

函数: int sigfillset(sigset_t *set);       

        功能:填充信号集中的数据,将信号集中的所有的标志位置为1;

        参数:sigset_t *set 传出参数,需要操作的信号集;

        返回值:成功返回0;失败返回-1;

函数: int sigaddset(sigset_t *set, int signum); 

        功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号;

        参数:sigset_t *set 传出参数,需要操作的信号集;

                   signum:需要设置阻塞的信号//也可以用信号编号表示;

        返回值:成功返回0;失败返回-1;

函数: int sigdelset(sigset_t *set, int signum);        

        功能:设置信号集中的某一个信号对用的标志位为0,表示不阻塞这个信号;

        参数:sigset_t *set 传出参数,需要操作的信号集;

                   signum:需要设置不阻塞的信号;

        返回值:成功返回0;失败返回-1;

函数:int sigismember(const sigset_t *set, int signum);

        功能:判断某个信号是否被阻塞;

        参数:set:需要操作的信号集;signum:需要判断的信号。

        返回值:返回1,被阻塞;返回0:不阻塞;返回-1:失败;

案例:

        我们创建一个新的信号集,然后清空信号集,然后查看里面的信号,然后添加信号,查看信号,删除信号,再查看信号集,将上面所述的五个函数都包含在内;

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

int main()
{
    //创建一个信号集;
    sigset_t set;
    //清空信号集中的内容;
    sigemptyset(&set);
    //判断SIGINT是否在信号集set里面;
   int ret = sigismember(&set, 2);
    if(ret==0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if(ret==1)
    {
        printf("SIGINT 阻塞\n");
    }
    //添加几个信号到信号集中
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGQUIT);

     //判断SIGINT是否在信号集set里面;
     ret = sigismember(&set, 2);
    if(ret==0)
    {
        printf("SIGINT 不阻塞\n");
    }
    else if(ret==1)
    {
        printf("SIGINT 阻塞\n");
    }

    ret = sigismember(&set, SIGQUIT);
    if(ret==0)
    {
        printf("SIGQUIT 不阻塞\n");
    }
    else if(ret==1)
    {
        printf("SIGQUIT 阻塞\n");
    }
    //从信号集中删除一个信号;
    sigdelset(&set, SIGQUIT);
    ret = sigismember(&set, SIGQUIT);
    if(ret==0)
    {
        printf("SIGQUIT 不阻塞\n");
    }
    else if(ret==1)
    {
        printf("SIGQUIT 阻塞\n");
    }
    return 0;
}

运行结果:

结果分析:

        先创建一个信号集,然后清空,查询是否有SIGINT信号,其不阻塞表明不存在;

        添加两个信号“SIGINT”"SIGOUIT";

        查询这两个信号,在信号集中都存在,即阻塞。

        删除“SIGINT”信号;

        查询“SIGINT”信号;在信号集中存在,即阻塞。

3.3 sigprocmask()函数、sigpending()函数

        在上面我们介绍了对自定义信号集的操作,主要是上面的五个函数,在内核中有两个重要的信号集,接着我们对内核中的信号集进行操作,如何实现呢?
    答案:只能用系统提供的API去操作。通过系统调用改变系统的信号集。
        未决信号集:不能被修改,只能被获取;
        阻塞信号集:可以设置;通过API函数;

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

涉及的头文件:

          #include <signal.h>

功能:

        将自定义信号集中的数据设置到内核信号中(设置阻塞,解除阻塞,替换);

参数:

        how:如何对内核阻塞信号集进行处理;有三种选择:

                -SIG_BLOCK:将用户设置的阻塞信号添加内核中,内核中原来的数据不变;

                -SIG_UNBLOCK:根据用户设置的数据,对内核中的数据解除阻塞。 

                -SIG_SETMASK:覆盖内核中的值,用set直接全部替换。   

        set:已经初始化好的用户自定义的信号集;

        oldset:记录的设置之前的内核中的阻塞信号的状态,可以是NULL;

返回值:

        成功 0;失败 -1,并设置错误号;

注:

        SIG_BLOCK:采用的是按位或的方式(添加阻塞信号):

例如:

   内核中默认的阻塞信号是mask:10010;

   我们创建的自定义信号集set :00101

    则最终的信号集为mask:101111

        SIG_UNBLOCK:采用的是先取反,再按位与的方式(解除阻塞信号):

例如:

        内核中默认的阻塞信号是mask:10111;

           我们创建的自定义信号集set :00101

                  则最终的信号集为mask:10010

 int sigpending(sigset_t *set);

涉及的头文件:

          #include <signal.h>

功能:

        获取内核中的未决信号集;

 参数:set:传出参数:保存的是内核中未决信号集的信息;

       返回值:成功 0;失败 -1;设置错误号:

案例:

      编写一个程序,将所有的常规信号(1-31)的未决状态打印到屏幕;设置某些信号是阻塞的,通过键盘产生这些信号;

#define  _DEFAULT_SOURCE
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    int num=0;
    //创建一个信号集,设置2号信号和3号信号阻塞;
    sigset_t set;
    sigemptyset(&set);//清空信号集
    sigaddset(&set, SIGINT);//添加信号
    sigaddset(&set, 3);//添加信号

    //修改系统中内核阻塞集
     sigprocmask(SIG_SETMASK,&set,NULL);

    while(1)
    {
        num++;
        //获取当前的未决信号集的数据;
        sigset_t pendingest;
        sigemptyset(&pendingest);
        sigpending(&pendingest);

        //遍历前32位
        for(int i=1;i<=32;i++)
        {
            if(sigismember(&pendingest, i)==1)
            {
                printf("1");
            }
            else if(sigismember(&pendingest, i)==0)
                 printf("0");
            else if(sigismember(&pendingest, i)==-1)
            {
                perror("sigismember");
                exit(0);
            }
        }
        sleep(1);
        printf("\n");
        if(num==10)
        {
            sigprocmask(SIG_UNBLOCK,&set, NULL);
        }
    }
    return 0;
}

 运行结果:

结果分析:

         首先创建一个用户自己的信号集set,然后对其进行清空,再添加两个信号,然后利用sigprocmask()函数,将自定义信号集中的数据设置到内核中。那这会内核中的阻塞信号集的2、3号就会阻塞。

        然后在创建一个用户信号集pendingest,清空该信号集,用sigpending函数将内核中的未决信号集的值传递出来给pendingest。

        然后开始打印pendingest信号集的值,也就是未决信号集的值,打印10次,刚开始都为0,因为没有信号产生,然后再键盘中输入ctrl+c,产生2号信号,2号信号产生后并不会被处理,因为它要同阻塞信号集进行比较,此时阻塞信号集中2号信号被阻塞,那么2号信号就不能被处理,处于一个未决状态,打印出来的未决信号集的值就为:01000000000000000000000000000000;

        最后等10次后,重新利用sigprocmask()函数,将之前的阻塞信号进行解除,此时2号信号就被执行,执行结果就是进程结束。 

3.4 sigaction函数

        前面我们介绍,在信号捕获方面,有两个捕捉函数:

sighandler_t signal(int signum, sighandler_t handler);

还有另外一个信息捕捉函数:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

        由于牵扯到信号集的相关内容,所以我们没有介绍这个函数,现在我们已经学习了信号集,下面我们看看这个函数的使用:

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

涉及的头文件:

        #include <signal.h>

功能:

        检查或者改变信号的处理,信号捕捉;

参数: 

         -signum:需要捕捉的信号的编号或者宏值;

                        (建议用信号名称)不同架构的信号宏值相同,编号可能不同。

                      SIGKILL and SIGSTOP.不能被捕捉;

         -act:捕捉到信号之后的处理动作。

         -oldact:上一次对信号捕捉相关设置,一般不使用传递NULL;

返回值:

        成功返回0,失败返回-1;

act详解:

        

const struct sigaction *act;

                struct sigaction {

                //函数指针,指向的函数就是信号捕捉到之后的处理函数;

                        void     (*sa_handler)(int);

                //函数指针,不常用;

                        void     (*sa_sigaction)(int, siginfo_t *, void *);

                //临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。

                        sigset_t   sa_mask;

                //使用那个信号处理对捕捉到的信号进行处理

                //这个值可以是0使用sa_handler,也可以是SA_SIGNFO表示使用sa_sigaction;

                        int        sa_flags;

                //被废弃掉了。不需要使用NULL;

                        void     (*sa_restorer)(void);

                    };

案例:

        利用setitimer函数进行定时,3秒后开始,然后每隔2秒后进行定时,定时会发送SIGALRM信号,然后利用sigaction函数进行信号的捕获,捕获到信号后,进入处理函数,在处理函数内部打印捕捉到信号的编号;

#define _XOPEN_SOURCE
    #include <sys/time.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <signal.h>
    //过3秒后,每隔两秒定时一次;到3s后,第一次定时。
    void myalarm(int num)
{
    printf("捕捉到信号的编号是:%d\n",num);
    printf("xxxxx\n");
}
    int main()
    {

     struct sigaction act;
        act.sa_flags=0;
        act.sa_handler=myalarm;
       // sigemptyset(&act,sa_mask);//清空临时阻塞信号集;
        //注册信号捕捉
    
        sigaction(SIGALRM,&act,NULL);
        struct itimerval new_value;
        new_value.it_interval.tv_sec=2;
        new_value.it_interval.tv_usec=0;

        new_value.it_value.tv_sec=3;
        new_value.it_value.tv_usec=0;
    
       int ret = setitimer(ITIMER_REAL,&new_value,NULL);//非阻塞
       printf("定时器开始:\n");
       if(ret==-1)
       {
            perror("setitimer");
            exit(0);
       }
       while(1);
        return 0;
    }

运行结果:

结果分析: 

    在由setitimer定时器发送SIGALRM信号后,利用sigaction函数进行信号的捕捉,在3秒后第一次捕捉,然后以2秒为周期进行周期性捕捉,并将捕捉的结果打印出来;

 signal与 sigaction功能是相同的,但是其标准是不同的,因此建议使用,sigaction。

内核实现信号捕捉的过程: 

3.5 sigchld信号 

        在最前面我们介绍信号的时候,里面的编号17号信号是——SIGCHLD信号,而产生此信号的条件有3种,分别为:

            1、子进程结束;

            2、子进程暂停;

            3、子进程继续运行;

           都会给父进程发送信号,父进程默认忽略信号。那我们可以利用SIGCHLD信号解决僵尸进程的问题。

僵尸进程:父进程产生子进程,子进程结束后,父进程如果没有及时回收子进程的相关资源,则子进程就会成为僵尸进程,如果僵尸进程存在太多会对内存造成影响;

僵尸进程的回收需要用到wait()函数或者waitpid()函数;

案例:

        在子进程结束后,会向父进程发送sigchid信号,我们可以借此在收到信号后,完成子进程的回收避免僵尸进程的产生;

        在父进程中创建20个子进程,子进程结束,父进程还在执行,没有对子进程的资源进行回收,则会产生僵尸进程。

下面用sigchld信号进行回收:

#define _XOPEN_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <signal.h>
#include <sys/wait.h>

void mufun(int num)
{
    printf("捕捉到的信号:%d\n",num);
        //回收子进程PCB的资源;
//while(1){wait(NULL);}    
while(1)
{
    int ret=waitpid(-1,NULL,WNOHANG);
    if(ret>0)
    {
            printf("child sie ,pid=%d\n",ret);
    }
    else if(ret==0)
    {
        //说明还有子进程活着;
        break;
    } 
    else if(ret==-1)
    {
        break;
    }
}
}

int main()
{
//提前设置好阻塞信号集,阻塞SIGCHLD,因为有可能子进程很快结束,父进程还没有注册完捕捉;
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGCHLD);
    sigprocmask(SIG_BLOCK,&set,NULL);

    //1、创建一些子进程;
    pid_t pid;
    for(int i=0;i<20;i++)
    {
        pid=fork();
        if(pid==0)
        {
            break;
        }
    }
        if(pid>0)
        {
            //父进程
            //捕捉子进程死亡时发送的SIGCHID信号
            struct  sigaction act;
            act.sa_flags=0;
            act.sa_handler=mufun;
            sigemptyset(&act.sa_mask);   
            sigaction(SIGCHLD,&act,NULL);
            //注册完信号捕捉以后,解除阻塞
            sigprocmask(SIG_UNBLOCK,&set,NULL);

            while(1)
            {
                printf("parent process pid:%d\n",getpid());
                sleep(2);
            }
        }
        else if(pid==0)
        {
            printf("child process pid:%d\n",getpid());
        }
    }

运行结果:

       以上便是进程间通信中,信号的相关内容,大家阅读后有问题可以将在评论区进行交流。

        创作不易,感谢大家点赞支持!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777437.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ASP.NET Core----基础学习01----HelloWorld---创建Blank空项目

文章目录 1. 创建新项目--方式一&#xff1a; blank2. 程序各文件介绍&#xff08;Project name &#xff1a;ASP.Net_Blank&#xff09;&#xff08;1&#xff09;launchSettings.json 启动方式的配置文件&#xff08;2&#xff09;appsettings.json 基础配置file参数的读取&a…

Vue 前端修改页面标题无需重新打包即可生效

在public文件夹下创建config.js文件 index.html页面修改 其他页面的标题都可以用window.title来引用就可以了&#xff01;

【算法】(C语言):冒泡排序、选择排序、插入排序

冒泡排序 从第一个数据开始到第n-1个数据&#xff0c;依次和后面一个数据两两比较&#xff0c;数值小的在前。最终&#xff0c;最后一个数据&#xff08;第n个数据&#xff09;为最大值。从第一个数据开始到第n-2个数据&#xff0c;依次和后面一个数据两两比较&#xff0c;数值…

商务办公优选!AOC Q27E3S2商用显示器,打造卓越新体验!

摘要&#xff1a;助办公室一族纵横职场&#xff0c;实现高效舒适办公&#xff01; 在日常商务办公中&#xff0c;对于办公室一族来说总有太多“难难难难难点”&#xff1a;工作任务繁琐&#xff0c;熬夜加班心力交瘁、长时间伏案工作导致颈椎、眼睛等出现问题&#xff0c;职业…

【吊打面试官系列-MyBatis面试题】为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?

大家好&#xff0c;我是锋哥。今天分享关于 【为什么说 Mybatis 是半自动 ORM 映射工具&#xff1f;它与全自动的区别在哪里&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 为什么说 Mybatis 是半自动 ORM 映射工具&#xff1f;它与全自动的区别在哪里&#xf…

[从0开始轨迹预测][NMS]:NMS的应用(目标检测、轨迹预测)

非极大值抑制&#xff08;Non-Maximum Suppression&#xff0c;简称NMS&#xff09;是一种在计算机视觉中广泛应用的算法&#xff0c;主要用于消除冗余和重叠的边界框。在目标检测任务中&#xff0c;尤其是在使用诸如R-CNN系列的算法时&#xff0c;会产生大量的候选区域&#x…

Redis基础教程(九):redis有序集合

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; &#x1f49d;&#x1f49…

华为仓颉可以取代 Java 吗?

大家好&#xff0c;我是君哥。 在最近的华为开发者大会上&#xff0c;华为亮相了仓颉编程语言&#xff0c;这是华为历经 5 年&#xff0c;投入大量研发成本沉淀的一门编程语言。 1 仓颉简介 按照官方报告&#xff0c;仓颉编程语言是一款面向全场景智能的新一代编程语言&#…

使用JAR命令打包JAR文件使用Maven打包使用Gradle打包打包Spring Boot应用

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

XLSX + LuckySheet + LuckyExcel + Web Worker实现前端的excel预览

文章目录 功能简介简单代码实现web worker 版本效果参考 功能简介 通过LuckyExcel的transformExcelToLucky方法&#xff0c; 我们可以把一个文件直接转成LuckySheet需要的json字符串&#xff0c; 之后我们就可以用LuckySheet预览excelLuckyExcel只能解析xlsx格式的excel文件&a…

机器学习——随机森林

随机森林 1、集成学习方法 通过构造多个模型组合来解决单一的问题。它的原理是生成多个分类器/模型&#xff0c;各自独立的学习和做出预测。这些预测最后会结合成组合预测&#xff0c;因此优于任何一个单分类得到的预测。 2、什么是随机森林&#xff1f; 随机森林是一个包含…

Midjourney 预设

使用命令/settings 进入预设,根据点击不同选项来配置。 🌹 1. 设置工作所使用的模型版本。 1️⃣ MJ Version 1 2️⃣ MJ Version 2 3️⃣ MJ Version 3 4️⃣ MJ Version 4 5️⃣ MJ Version 5 5️⃣ MJ Version 5.1 🔧Raw Mode 🌈 Niji Version 4 🍎 Niji Versio…

【pytorch16】MLP反向传播

链式法则回顾 多输出感知机的推导公式回顾 只与w相关的输出节点和输入节点有关 多层多输入感知机 扩展为多层感知机的话&#xff0c;意味着还有一些层&#xff08;理解为隐藏层σ函数&#xff09;&#xff0c;暂且设置为 x j x_{j} xj​层 对于 x j x_{j} xj​层如果把前面的…

Vue联调Java后台操作性强教程

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

C++入门 容器适配器 / stack queue模拟实现

目录 容器适配器 deque的原理介绍 stack模拟实现 queue模拟实现 priority_queue模拟实现 仿函数 容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结)&#xff0c;该种模式是将一个类的接口转换成客户希望…

关于GIS的概念方面在前端编程中的理解

关于GIS的概念方面在前端编程中的理解 一. 什么是gis二. 关于地球的建模(了解)三. GIS坐标系表现形式四.GIS的数据4.1 矢量数据4.2 栅格数据4.3 矢量数据和栅格数据的不同 一. 什么是gis 地理坐标系统&#xff0c;其目的就是通过地理坐标系可以确定地球上任何一点的位置。 二. …

介绍 pg_later:受 Snowflake 启发的 Postgres 异步查询#postgresql认证

#PG培训#PG考试#postgresql培训#postgresql考试 为什么要使用异步查询&#xff1f; 想象一下&#xff0c;您启动了一项长期维护工作。您在执行过程中离开&#xff0c;但回来后发现&#xff0c;由于笔记本电脑关机&#xff0c;该工作在几个小时前就被中断了。您不希望这种情况…

web基础与HTTP协议(企业网站架构部署与优化)

补充&#xff1a;http服务首页文件在/var/www/html下的&#xff0c;一定是index.html命名的文件。才会显示出来。 如果该路径下没有相应的文件&#xff0c;会显示/usr/share/httpd/noindex下的index.html文件。 如果/usr/share/httpd/noindex没有index.html文件&#xff0c;会…

Spring MVC 获取请求数据的四种方式,以及获取请求头数据,获取Cookie 的数据,设置Spring MVC 的字符集编码过滤器

1. Spring MVC 获取请求数据的四种方式&#xff0c;以及获取请求头数据&#xff0c;获取Cookie 的数据&#xff0c;设置Spring MVC 的字符集编码过滤器 文章目录 1. Spring MVC 获取请求数据的四种方式&#xff0c;以及获取请求头数据&#xff0c;获取Cookie 的数据&#xff0c…

昇思MindSpore学习笔记4-01生成式--CycleGAN图像风格迁移互换

摘要&#xff1a; 记录了昇思MindSpore AI框架用循环对抗生成网络模型CycleGAN实现图像匹配的方法、步骤。包括环境准备、数据集下载、数据加载和预处理、构建生成器和判别器、优化、模型训练和推理等。 1.模型介绍 1.1模型简介 CycleGAN(Cycle Generative Adversarial Netwo…