进程和程序(三)

信号

信号的特点:简单、不能带大量信息,满足特定条件发生。

信号也叫软件产生的中断,有可能会有延迟。(比如运行大量的进程,CPU资源被占据,导致信号延迟。)

每个进程收到的所有信号,都是由内核负责发送的,内核处理。

信号的产生方式:
1、按键产生:ctrl + c 、ctrl + z 、ctrl +
2、调用函数:kill、raise、abort
3、定时器:alarm、setitimer
4、命令产生kill
5、硬件异常、段错误、浮点型错误、总线错误、SIGPIPE

信号的状态:
1、产生
2、递达:递送并且到达进程。
3、未决:产生和递达之间的状态,主要由于信号被阻塞(屏蔽)所导致。

信号的默认处理方式:
1、忽略
2、执行默认动作
3、捕获
其中,9、19号信号不能捕获,不能忽略,甚至不能阻塞。

在这里插入图片描述
在这里插入图片描述
信号的四要素:
1、编号
2、事件
3、名称
4、默认的处理动作

默认的处理动作可以通过命令 man 7 signal来查看,有以下5个。
Term:终止
Ign:忽略
Core:终止+Core
Stop:暂停
Cont:继续
在这里插入图片描述
信号的编号

可以使用kill –l命令查看当前系统可使用的信号有哪些。
在这里插入图片描述
不存在编号为0的信号。其中1-31号信号称之为常规信号(也叫普通信号或标准信号),34-64称之为实时信号,驱动编程与硬件相关。名字上区别不大。而前32个名字各不相同。

阻塞信号集、未决信号集、信号产生

Linux内核的进程控制块PCB是一个结构体,task_struct,除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将被推后(解除屏蔽后)。

未决信号集:

1、信号产生,未决信号集中描述该信号的位立刻翻转为1,表示信号处于未决状态。当信号被处理,对应位翻转回为0。这一时刻往往非常短暂(内核产生内核处理)。

2、信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。

阻塞信号集的作用实际上就是影响未决信号集的。阻塞信号集相当于未决信号集的前面设置了一堵墙。

“集” 理解为位图。如下图。
在这里插入图片描述

信号产生

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
练习:弑父

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc,char **argv)
{
    int i;
    for(i=0;i<5;i++)
    {
        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork err");
            exit(1);
        }
        else if(pid==0)
        {
            break;
        }
    }

    if(i==2)
    {
        printf("I will kill father after 5s.\n");
        sleep(5);
        kill(getppid(),SIGKILL);
        while(1)
        {
            sleep(1);
        }
    }
    else if(i==5)
    {
        //parent
        while(1)
        {
            printf("I am father.\n ");
            sleep(1);
        }
    }
    return 0;
}

练习:父杀子

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc,char **argv)
{
    int i;
    pid_t pid3;
    for(i=0;i<5;i++)
    {
        pid_t pid = fork();
        if(pid < 0)
        {
            perror("fork err");
            exit(1);
        }
        else if(pid==0)
        {
            break;
        }
        if(i==2)
        {
            pid3 = pid;
        }
    }

    if(i<5)
    {
        while(1)
        {
            printf("I am chile_%d,pid=%d,ppid=%d.\n",i,getpid(),getppid());
            sleep(2);
        }
    }
    else if(i==5)
    {
        printf("I am father.I will kill pid3 after 5s.\n");
        sleep(5);
        kill(pid3,SIGKILL);
        while(1)
        {
            printf("I am father.\n ");
            sleep(1);
        }
    }
    return 0;
}

raise:自己给自己发信号。

练习:自杀

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc,char **argv)
{
    printf("I will die!.\n");
    sleep(2);
    raise(SIGKILL);//相当于执行kill(getpid(),sig);
    return 0;
}

abort:直接自己给自己发送一个异常信号SIGABRT,然后终止程序运行。

练习

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc,char **argv)
{
    printf("I will die!.\n");
    sleep(2);
    abort();
    return 0;
}

运行结果:
在这里插入图片描述

time函数

time函数获得日历时间。日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区,在同一时刻对同一个标准时间点来说,日历时间都是一样的。

#include <time.h>
time_t time(time_t *tloc);

在这里插入图片描述
是当前相对与过去的某个时间点所走过的时间。
在这里插入图片描述

时钟信号产生:alarm

在这里插入图片描述
设定几秒后发送信号SIGALRM。有点像定闹钟。
函数返回值是上次闹钟剩余的秒数。
特别的,如果传入参数为0,则代表取消闹钟。

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

int main()
{
    alarm(6);
    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
}

6秒后,该进程给自己发送一个SIGALRM信号,然后终止正在运行的进程。
在这里插入图片描述
看看返回值。

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

int main()
{
    int ret = alarm(6);//定时六秒,ret = 0.
    printf("ret = %d.\n",ret);
    sleep(3);
    ret = alarm(2);//经过三秒,还剩下3秒,ret = 3,又重新定时2秒.
    printf("ret = %d.\n",ret);
    sleep(1);
    ret = alarm(0);//又经过1秒,还剩下1秒,取消定时.
    printf("ret = %d.\n",ret);
    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
}

在这里插入图片描述

时钟信号产生:setitimer

int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//new_value是这一次的时间设置
//old_value是距离上一次的闹钟的定时时间到,还剩多长时间

在这里插入图片描述
1、使用setitimer函数来实现alarm的定时功能

#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>

//int getitimer(int which, struct itimerval *curr_value);
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);
//new_value是这一次的时间设置
//old_value是距离上一次的闹钟的定时时间到,还剩多长时间

//使用setitimer函数来实现alarm的定时功能
#if 0
           Timer values are defined by the following structures:

           struct itimerval {              /* 周期性的时间设置 */
               struct timeval it_interval; /* Interval for periodic timer */
               struct timeval it_value;    /* Time until next expiration */
           };                              /* 下次闹钟的定时时间 */

           struct timeval {
               time_t      tv_sec;         /* seconds */
               suseconds_t tv_usec;        /* microseconds */
           };

#endif

int main()
{
    struct itimerval mytimer={{0,0},{3,0}};
    setitimer(ITIMER_REAL,&mytimer,NULL);

    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
    return 0;
}

注意使用该函数的写法。
在这里插入图片描述
2、使用setitimer函数来实现周期性的时间设置

要想实现周期性的时间设置,需要先了解下信号捕获函数。
在这里插入图片描述
注意函数的参数。

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

//使用setitimer函数来实现周期性时间设置的功能

#if 0
Timer values are defined by the following structures:

struct itimerval {              /* 周期性的时间设置 */
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value;    /* Time until next expiration */
};                              /* 下次闹钟的定时时间 */

struct timeval {
time_t      tv_sec;         /* seconds */
suseconds_t tv_usec;        /* microseconds */
};

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

#endif

//根据函数指针来定义
void catch_sig(int num)
{
    printf("cat %d sig.\n",num);
}

int main()
{
    //通过调用signal这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
    //在这个函数中我们自定义我们想要做的事情。
    //在这里我们抓SIGALRM这个信号。
    signal(SIGALRM,catch_sig);

    struct itimerval mytimer={{3,0},{5,0}};//第一次定时5s,5s之后,发送一次SIGALRM信号.之后,每隔3s发一次SIGALRM信号。
    setitimer(ITIMER_REAL,&mytimer,NULL);

    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
    return 0;
}

运行结果如下:
在这里插入图片描述
3、使用setitimer函数来计算上一次定时还剩多少时间

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>

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

unsigned int myalarm(unsigned int seconds)
{
    struct itimerval oldtimer,newtimer={{0,0},{0,0}};

    newtimer.it_value.tv_sec = seconds;
    setitimer(ITIMER_REAL,&newtimer,&oldtimer);//seconds秒后发送SIGALRM信号
    printf("tv_sec=%ld,tv_mirsec=%ld.\n",oldtimer.it_value.tv_sec,oldtimer.it_value.tv_usec);

    return oldtimer.it_value.tv_sec*1000000+oldtimer.it_value.tv_usec;
}

int main()
{
    int i = 0;
    unsigned int ret = 0;
    ret = myalarm(5);
    printf("ret= %d us\n",ret);
    sleep(3);
    ret = myalarm(3);
    printf("ret= %d us\n",ret);

    while(1)
    {
        printf("Hello %3d.\n",++i);
        sleep(1);
    }
    return 0;
}

输出结果如下:
在这里插入图片描述
为什么不是整整的2秒?原因是:指令的执行时间也有一定的消耗。

信号集处理函数

#include <signal.h>
//清空信号集,信号集(位图)清空变成0
int sigemptyset(sigset_t *set);
//填充信号集,信号集(位图)填充变成1
int sigfillset(sigset_t *set);
//添加某个信号到信号集
int sigaddset(sigset_t *set, int signum);
//从信号集中删除某个信号
int sigdelset(sigset_t *set, int signum);
//是否为集合中的成员
int sigismember(const sigset_t *set, int signum);

//返回值判断
sigemptyset(), sigfillset(), sigaddset(), and sigdelset() return 0 on success and -1 on error.

sigismember() returns 1 if signum is a member of set, 0 if signum is not a member, and -1 on error.

sigprocmask函数

功能:用来设置set为当前进程的阻塞信号集。

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:
int how:
		SIG_BLOCK: 设置阻塞
		SIG_UNBLOCK: 解除阻塞
		SIG_SETMASK: 设置set为新的阻塞信号集
set:  传入参数,传入的信号集
oldset:  传出参数,旧的信号集
设置旧信号集有利于恢复之前的状态。

返回值:
sigprocmask() returns 0 on success and -1 on error.  

sigpending函数

功能:获取当前进程的未决信号集。

#include <signal.h>
int sigpending(sigset_t *set);

参数:
set:传出参数,当前的未决信号集。

返回值:
sigpending() returns 0 on success and -1 on error.  

1、使用sigpending来获取当前进程的未决信号集

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

int main()
{
    int i;
    sigset_t weijue_sigset;

    sigpending(&weijue_sigset);

    for(i=1;i<32;i++)//0-31为标准(普通)信号
    {
        //是否为信号集中的成员
        //不是返回0,是返回1.
        if(sigismember(&weijue_sigset,i) == 1)
            printf("1");
        else
            printf("0");
    }
    printf("\n");

    return 0;
}

输出结果如下:
在这里插入图片描述
我们当前进程中没有未决信号。

2、设置阻塞信号,然后再打印未决信号集。

这里使用终端按键产生信号,然后设置阻塞不同按键产生的信号。
在这里插入图片描述
程序思路:
1】设置阻塞信号集。
方式:使用sigprocmask函数
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
2】添加信号到阻塞信号集。
方式:使用sigaddset函数添加某个信号到信号集。
int sigaddset(sigset_t *set, int signum);
3】获取当前进程的未决信号集。
方式:使用sigpending函数获取
4】打印未决信号集。
方式:通过sigismember判断是否为当前未决信号集合中的成员
int sigismember(const sigset_t *set, int signum);
注】对信号集操作之前先使用sigemptyset函数清空信号集。
方式:int sigemptyset(sigset_t *set);

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

int main()
{
    //定义阻塞信号集.
    sigset_t BlockSigSet;
    //对信号集操作之前先使用sigemptyset函数清空信号集。
    sigemptyset(&BlockSigSet);
    //添加阻塞信号到阻塞信号集.
    sigaddset(&BlockSigSet,SIGINT);//这里阻塞SIGINT信号,SIGINT信号的值为2
    //设置信号集为当前进程的阻塞信号集.
    sigprocmask(SIG_BLOCK, &BlockSigSet, NULL);

    int i;
    sigset_t weijue_SigSet;

    while(1)
    {
        sigpending(&weijue_SigSet);

        for(i=1;i<32;i++)
        {
            //是否为信号集中的成员
            //不是返回0,是返回1.
            if(sigismember(&weijue_SigSet,i) == 1)
                printf("1");
            else
                printf("0");
        }
        printf("\n");
        sleep(1);
    }
    return 0;
}

运行结果如下:
在这里插入图片描述
在这里插入图片描述
前面说9号信号SIGKILL不能被阻塞,我们在程序中验证下。

sigaddset(&BlockSigSet,SIGKILL);//这里阻塞SIGKILL信号,SIGKILL信号的值为9

运行下程序:
在这里插入图片描述
然后,使用9号信号。
在这里插入图片描述
在这里插入图片描述
可以看出还是被杀死了,说明9号信号是不能被阻塞的。

信号捕捉函数

作用:可以防止进程意外死亡。
假设在程序执行过程中,突然收到一个异常信号,该信号可能会导致我们的正在运行的程序死亡,这时,如果我们在程序中设置了捕获函数,那么就对该异常信号进行捕获,最后根据捕获的异常信号,执行对应操作,从而确保我们的程序能够正常执行下去。

在上面,我们已经提到了信号捕获函数signal,我们用该函数实现了周期性的时间设置。
我们再来看看这个函数。
在这里插入图片描述
sighandler_t这个数据类型并不是标准的类型。而是自定义的函数指针类型,在程序中如果使用到这个数据类型,就要加上下面这句,
typedef void ( * sighandler_t)(int);

在这里插入图片描述
这个函数的函数名为signal,signal可能在不同平台上的用处不一样,可能会造成冲突,所以我们一般使用另外一个函数sigaction,这个函数的作用也是捕获信号。

sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

在这里插入图片描述

看下struct sigaction这个结构体。
在这里插入图片描述
返回值:

sigaction() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.

练习:使用sigaction函数来实现周期性的时间设置—与上面使用signal函数实现一样的功能。

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
//int setitimer(int which, const struct itimerval *new_value,struct itimerval *old_value);

//使用setitimer函数来实现周期性时间设置的功能

#if 0
Timer values are defined by the following structures:

struct itimerval {              /* 周期性的时间设置 */
struct timeval it_interval; /* Interval for periodic timer */
struct timeval it_value;    /* Time until next expiration */
};                              /* 下次闹钟的定时时间 */

struct timeval {
time_t      tv_sec;         /* seconds */
suseconds_t tv_usec;        /* microseconds */
};

#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

#endif

//根据函数指针来定义
void catch_sig(int num)
{
    printf("cat %d sig.\n",num);
}

int main()
{
    //通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
    //在这个函数中我们自定义我们想要做的事情。
    //在这里我们抓SIGALRM这个值为14的信号。
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = catch_sig;
    //我们捕捉这个函数的期间,没有要屏蔽的信号。
    //int sigemptyset(sigset_t *set);
    sigemptyset(&act.sa_mask);

    //int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
    sigaction(SIGALRM, &act,NULL);

    struct itimerval mytimer={{3,0},{5,0}};//第一次定时5s,5s之后,发送一次SIGALRM信号.之后,每隔3s发一次SIGALRM信号。
    setitimer(ITIMER_REAL,&mytimer,NULL);

    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
    return 0;
}

程序运行结果如下:
在这里插入图片描述
我们试着使用kill命令来给这个进程发14号信号,看看能否被捕获。
在这里插入图片描述
在这里插入图片描述
可以看出14号信号被捕获了。

信号的捕捉特性

1、进程正常运行时,默认pcb中有一个信号屏蔽字,假定为☆,它决定了进程自动屏蔽哪些信号,当注册了某个信号捕捉函数,捕捉到该信号以后,要调用该函数,而该函数有可能执行很长时间,在这期间所屏蔽的信号不由☆来指定,而是用sa_mask来指定,执行完信号处理函数,再次恢复为☆。

2、xxx信号捕捉函数执行期间(可能执行时间比较长),xxx信号会自动被屏蔽。(再产生该信号时,该信号会被阻塞,但该阻塞的常规信号不支持排队,虽然产生多次但只记录一次。(后32个实时信号支持排队))

验证第2点特性:

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>

//根据函数指针来定义
void catch_sig(int num)
{
    printf("catch %d sig begin.\n",num);
    sleep(3);//模拟该函数执行时间很长
    printf("catch %d sig end.\n",num);
}

int main()
{
    //通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
    //在这个函数中我们自定义我们想要做的事情。
    //在这里我们抓SIGALRM这个值为14的信号。
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = catch_sig;
    //我们捕捉这个函数的期间,没有要屏蔽的信号。
    //int sigemptyset(sigset_t *set);
    sigemptyset(&act.sa_mask);

    //int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
    sigaction(SIGINT, &act,NULL);

    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
    return 0;
}

测试输出结果如下图所示。
在这里插入图片描述
验证第1点特性:

在捕获函数的动作执行期间,临时屏蔽 ctrl+\ 信号。但等该当作执行完后,由于屏蔽信号集中没有屏蔽该信号,所以在动作执行结束之后会继续响应该信号。

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>

//根据函数指针来定义
void catch_sig(int num)
{
    printf("catch %d sig begin.\n",num);
    sleep(3);//模拟该函数执行时间很长
    printf("catch %d sig end.\n",num);
}

int main()
{
    //通过调用sigaction这个函数,表示告诉系统当signum这个信号产生的时候,调用指针函数指向的那个函数。
    //在这个函数中我们自定义我们想要做的事情。
    //在这里我们抓SIGALRM这个值为14的信号。
    struct sigaction act;
    act.sa_flags = 0;
    act.sa_handler = catch_sig;
    //我们捕捉这个函数的期间,没有要屏蔽的信号。
    //int sigemptyset(sigset_t *set);
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask,SIGQUIT);//临时屏蔽 ctrl+\ 信号

    //int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
    sigaction(SIGINT, &act,NULL);

    while(1)
    {
        printf("Hello.\n");
        sleep(1);
    }
    return 0;
}

内核实现信号捕捉过程:

在这里插入图片描述

SIGCHLD信号

SIGCHLD信号的产生条件

1、子进程终止时。

2、子进程接收到SIGSTOP信号停止时。

3、子进程处于停止态,接收到SIGCONT信号后被唤醒时。

我们在man 7 signal中可以看下。
在这里插入图片描述
当子进程暂停或者终止时,会发出SIGCHLD这个信号,但是内核对这个信号的默认处理动作是忽略

我们学过了信号捕获函数,可以和这个信号结合起来,来实现对子进程资源的回收,从而可以避免父进程一直使用wait函数等待,不停的去判断。

下面按照这个思路来实现一下。

注意:这里让每个子进程睡上相应的i秒之后,才让他死去。

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

void huishou_pid(int num)
{
    pid_t wpid = waitpid(-1,NULL,WNOHANG);

    if(wpid >0)
    {
        printf("wait child %d ok.\n",wpid);
    }
}

int main()
{
    int i = 0;

    pid_t pid;
    for(i=0;i<10;i++)
    {
        pid = fork();
        if(pid == 0)
        {
            //fork son ok.
            break;
        }
    }

    if(i==10)
    {
        //parent
        struct sigaction act;
        act.sa_flags = 0;
        sigemptyset(&act.sa_mask);
        act.sa_handler = huishou_pid;
        sigaction(SIGCHLD,&act,NULL);
        while(1)
        {
            sleep(1);
        }
    }
    else if(i < 10)
    {
        printf("I am %d child,pid = %d.\n",i,getpid());
        sleep(i);
    }
    
    return 0;
}

程序运行一下:
在这里插入图片描述
都给回收了。
再通过命令ps aux来看下。
在这里插入图片描述
子进程都被回收了,无僵尸进程产生。

如果让每个子进程都只是打印一句话,就死去。也就是将sleep(i)这句代码给屏蔽了。

再运行一下:
在这里插入图片描述
可以看出,10个进程只回收了7个。
再通过命令ps aux来看下。
在这里插入图片描述
果然,产生了三个僵尸进程。

那是什么原因造成的呢?

分析下,在前面,我们提到了

捕获信号的特性2
“xxx信号捕捉函数执行期间(可能执行时间比较长),再产生该信号时,该信号会被阻塞,但该阻塞的常规信号不支持排队,虽然产生多次但只记录一次。”

也就是说当多个子进程在几乎同一时间死去时,我们的回收函数来不及反应,最终只回收一个。从而造成僵尸进程的出现。

那怎么解决这个问题呢?

我们需要对我们的捕获函数进行修改,不让他回收一个就撤,而是使用while来一次性能回收多个。

void huishou_pid(int num)
{
    pid_t wpid;

    while((wpid = waitpid(-1,NULL,WNOHANG))>0)
    {
        printf("wait child %d ok.\n",wpid);
    }
}

程序运行结果如下:
在这里插入图片描述
再看下是否还有僵尸进程吧。
在这里插入图片描述
果然,都回收了。

特别的,需要注意下面这种有可能出现的极端情况,要避免。

当捕获函数运行之前,子程序就全部死光了呢?

比如,在父进程中注册捕获函数之前,先让它睡几秒。

if(i==10)
{
   	//parent
   	sleep(2);
   	struct sigaction act;
   	act.sa_flags = 0;
  	 sigemptyset(&act.sa_mask);
   	act.sa_handler = huishou_pid;
  	 sigaction(SIGCHLD,&act,NULL);
  	 while(1)
  	 {
       	sleep(1);
   	 }
}

再运行下程序。
在这里插入图片描述
一个都没回收。都变成了僵尸进程。
在这里插入图片描述
那应该怎么避免这种情况出现呢?

在子进程被fork之前,将SIGCHLD加入阻塞信号集,在捕获函数注册完成后,将SIGCHLD从阻塞信号集中解除。

这可能需要回顾对阻塞信号集概念的理解。

阻塞信号集(信号屏蔽字): 将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将被推后(解除屏蔽后)。

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

void huishou_pid(int num)
{
    pid_t wpid;

    while((wpid = waitpid(-1,NULL,WNOHANG))>0)
    {
        printf("wait child %d ok.\n",wpid);
    }
}

int main()
{
    int i = 0;

    pid_t pid;

    //在子进程被fork之前,将SIGCHILD加入阻塞信号集
    sigset_t myBlockSet;
    sigset_t oldset;

    sigemptyset(&myBlockSet);
    sigaddset(&myBlockSet,SIGCHLD);

    //sigprocmask用来设置set为当前的阻塞信号集
    //int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    sigprocmask(SIG_BLOCK,&myBlockSet,&oldset);

    for(i=0;i<10;i++)
    {
        pid = fork();
        if(pid == 0)
        {
            //fork son ok.
            break;
        }
    }
    if(i==10)
    {
        //parent
        sleep(2);
        struct sigaction act;
        act.sa_flags = 0;
        sigemptyset(&act.sa_mask);
        act.sa_handler = huishou_pid;
        sigaction(SIGCHLD,&act,NULL);

        //使用SIG_SETMASK参数恢复阻塞信号集
        sigprocmask(SIG_SETMASK,&oldset,NULL);
        while(1)
        {
            sleep(1);
        }
    }
    else if(i < 10)
    {
        printf("I am %d child,pid = %d.\n",i,getpid());
//      sleep(i);
    }

    return 0;
}

运行一下程序:
在这里插入图片描述
再使用ps aux命令来看下。
在这里插入图片描述
测试成功。

作业

利用SIGUSR1和SIGUSR2在父子进程之间进行消息传递,实现父子进程交替报数(间隔1s)。

提示:
1、使用kill(pid,sig)发送信号。
2、父子进程,捕获信号。
在这里插入图片描述
pid > 0那一方即父进程的逻辑总是先执行的。
但当父进程执行后,把SIGUSR2发出后,子进程捕获不到该信号。
解决方法:
1、加上延时函数usleep
2、将SIGUSR2加入阻塞信号集。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>

pid_t pid;
int count = 0;
int flag = 0;

void son_catch_signal(int num)
{
    sleep(1);
    flag=1;
    printf("%d\n",count);
    count += 2;
}

void parent_catch_signal(int num)
{
    sleep(1);
    flag=1;
    printf("%d\n",count);
    count += 2;
}

int main()
{
     //在子进程被fork之前,将SIGUSR2加入阻塞信号集
    sigset_t myBlockSet;
    sigset_t oldset;

    sigemptyset(&myBlockSet);
    sigaddset(&myBlockSet,SIGUSR2);

    //sigprocmask用来设置set为当前的阻塞信号集
    //int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
    sigprocmask(SIG_BLOCK,&myBlockSet,&oldset);

    pid = fork();
    if(pid == 0)
    {
        //son
        count = 1;
        signal(SIGUSR2,son_catch_signal);
        pid_t ppid = getppid();

         //使用SIG_SETMASK参数恢复阻塞信号集
        sigprocmask(SIG_SETMASK,&oldset,NULL);

        while(1)
        {
            if(flag==1)
            {
                kill(ppid,SIGUSR1);
                flag = 0;
            }
        }
    }
    else if(pid > 0)
    {
        //parent
//      usleep(10);//保证子进程先运行(当取消注释时,可不将SIGUSR2加入阻塞信号集)
        count = 2;
        signal(SIGUSR1,parent_catch_signal);
        kill(pid,SIGUSR2);//必须先给子进程一个信号才能开始

        while(1)
        {
            if(flag==1)
            {
                kill(pid,SIGUSR2);
                flag = 0;
            }
        }
    }
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xuechanba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值