Linux进程间的通信(5)--------信号通信2 (进程的状态,运行,暂停,僵尸态,孤儿进程)

在上一章说到用kill发送内核信号中的其中一种给进程,进而进行通信
这一章来讨论通过信号量来改变进程的状态
我们单独的建立两个进程,然后用raise函数杀死进程,查看状态

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<signal.h>
int main(void)
{
        pid_t pid;
        pid=fork();
        if(pid>0)
        {
                printf("I am parent process !\n");
                sleep(5);
                while(1);
        }
        if(pid==0)
        {
                printf("I am chlid process !\n");
                printf("raise before !\n");
                raise(SIGTSTP);
                printf("raise after !\n");
                exit(0);
        }
        return 0;
}

其运行结果以及前后进程的状态如下:
在这里插入图片描述
运行前的状态:
在这里插入图片描述
运行后的状态:
在这里插入图片描述
R+代表正在运行当中,T+代表暂停状态,而Z+代表僵尸状态
接下来我们做一点小改动,然后我们来看看结果有什么变化
在父进程中加入判断是否退出 waitpid(pid,NULL,WNOHANG) 第三个参数WNOHANG代表非阻塞
如果没有退出就用kill命令进行终止进程

#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<signal.h>
int main(void)
{
        /*
        printf("raise before !");
        raise(9);//相当于exit(1);一样,库缓存,没有换行符不输出
        printf("raise after !\n");
        */
        pid_t pid;
        pid=fork();
        if(pid>0)
        {
                printf("I am parent process !\n");
                sleep(5);
    
                if(waitpid(pid,NULL,WNOHANG)==0)
                {   
                        printf("子进程没有退出!\n");
                        kill(pid,9);        
                }  
                while(1);
        }
        if(pid==0)
        {
                printf("I am chlid process !\n");
                printf("raise before !\n");
                raise(SIGTSTP);
                printf("raise after !\n");
                exit(0);
        }
        return 0;
}

运行后的状态为:
在这里插入图片描述
我们将没有退出的子进程杀死以后没有回收资源, 用wait(NULL);来回收资源 ,不然子进程就变成了僵尸进程

僵尸进程处理方式
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“defunct”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。所以孤儿进程不会占资源,僵尸进程会占用资源危害系统。我们应当避免僵尸进程的出现。
解决方式如下:
1):一种比较暴力的做法是将其父进程杀死,那么它的子进程,即僵尸进程会变成孤儿进程,由系统来回收。但是这种做法在大多数情况下都是不可取的,如父进程是一个服务器程序,如果为了回收其子进程的资源,而杀死服务器程序,那么将导致整个服务器崩溃,得不偿失。显然这种回收进程的方式是不可取的,但其也有一定的存在意义
2):SIGCHLD信号处理
我们都知道wait函数是用来处理僵尸进程的,但是进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。我们先来看看wait函数的定义

于调用wait之后,就必须阻塞,直到有子进程结束,所以,这样来说是非常不高效的,我们的父进程难道要一直等待你子进程完成,最后才能执行自己的代码吗?难道就不能我父进程执行自己的代码,你子进程什么时候完成我就什么时候去处理你,不用一直等你?当然是有这种方式了。

实际上当子进程终止时,内核就会向它的父进程发送一个SIGCHLD信号,父进程可以选择忽略该信号,也可以提供一个接收到信号以后的处理函数。对于这种信号的系统默认动作是忽略它。我们不希望有过多的僵尸进程产生,所以当父进程接收到SIGCHLD信号后就应该调用 wait 或 waitpid 函数对子进程进行善后处理,释放子进程占用的资源。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<signal.h>
 
void deal_child(int num)
{
	printf("deal_child into\n");
	wait(NULL);
}
 
int main()
{
	signal(SIGCHLD,deal_child);
	pid_t pid=fork();
	int i;
 
	if(pid==0)
	{
		printf("child is running\n");
		sleep(2);
		printf("child will end\n");
	}
	else
	{
		sleep(1);   //让子进程先执行
		printf("parent is running\n");
		sleep(10);    //一旦被打断就不能再进入睡眠
		printf("sleep 10 s over\n");
		sleep(5);
		printf("sleep 5s over\n");
	}
 
	exit(0);
}

进行测试后确定了是在父进程睡眠10s时子进程结束,父进程接收到了SIGCHLD信号,调用了deal_child函数,释放了子进程的PCB后又回到自己本身的代码中执行。我们看看运行结果

在这里插入图片描述
说到这里,我们再来看看signal函数(不是阻塞函数)

signal(参数1,参数2);

参数1:我们要进行处理的信号。系统的信号我们可以再终端键入 kill -l查看(共64个)。其实这些信号是系统定义的宏。

参数2:我们处理的方式(是系统默认还是忽略还是捕获)。

eg: signal(SIGINT ,SIG_ING ); //SIG_ING 代表忽略SIGINT信号

eg:signal(SIGINT,SIG_DFL); //SIGINT信号代表由InterruptKey产生,通常是CTRL +C或者是DELETE。发送给所有ForeGroundGroup的进程。 SIG_DFL代表执行系统默认操作,其实对于大多数信号的系统默认动作是终止该进程。这与不写此处理函数是一样的。

我们也可以给参数2传递一个信号处理函数的地址,但是这个信号处理函数需要其返回值为void,并且默认自带一个int类型参数

这个int就是你所传递的第一个信号参数的值(你用kill -l可以查看)

我们测试了一下,如果创建了5个子进程,但是销毁的时候仍然有两个仍是僵尸进程,这又是为什么呢?

这是因为当5个进程同时终止的时候,内核都会向父进程发送SIGCHLD信号,而父进程此时有可能仍然处于信号处理的deal_child函数中,那么在处理完之前,中间接收到的SIGCHLD信号就会丢失,内核并没有使用队列等方式来存储同一种信号
所以为了解决这一问题,我们需要调用waitpid函数来清理子进程。

void deal_child(int sig_no)
 
{
 
    for (;;) {
 
        if (waitpid(-1, NULL, WNOHANG) == 0)
 
            break;
 
    }  
 
}

最后呢至于什么是孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

子进程死亡需要父进程来处理,那么意味着正常的进程应该是子进程先于父进程死亡。当父进程先于子进程死亡时,子进程死亡时没父进程处理,这个死亡的子进程就是孤儿进程。

但孤儿进程与僵尸进程不同的是,由于父进程已经死亡,系统会帮助父进程回收处理孤儿进程。所以孤儿进程实际上是不占用资源的,因为它终究是被系统回收了。不会像僵尸进程那样占用ID,损害运行系统。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
 
int main()
{
	pid_t pid=fork();
 
	if(pid==0)
	{
		printf("child ppid is %d\n",getppid());
		sleep(10);     //为了让父进程先结束
		printf("child ppid is %d\n",getppid());
	}
	else
	{
		printf("parent id is %d\n",getpid());
	}
 
	exit(0);
}

在这里插入图片描述
从执行结果来看,此时由pid =4168
父进程创建的子进程,其输出的父进程pid = 1,说明当其为孤儿进程时被init进程回收,最终并不会占用资源
这就是为什么要将孤儿进程分配给init进程。

对了还有一个alarm函数忘记介绍了,alarm函数是用于设置闹钟的,到指定时间之后去执行Linux内核捕捉的signal信号的服务函数
老样子上代码:

#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<unistd.h>
void my_fun(int pid_num)
{
        int i=0;
        for(;i<10;i++)
        {
        printf("%d                         the pid_num=%d \n",i,pid_num);
        }
}


int main(void)
{
        int i=0;
        signal(14,my_fun);
        alarm(5);
        for(;i<10;i++)
        {
                printf("%d     \n",i);
                sleep(1);
        }



        return 0;
}

运行结果如下:
在这里插入图片描述
闹钟设置5秒之后去执行服务函数,如果没有用signal函数捕捉,主进程将被终止

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值