Linux——SIGCHLD信号——第17号信号

目录

什么是僵尸进程?

僵尸进程的目的?

如何避免子进程成为僵尸进程?

小贴士:


提到SIGCHLD信号,就不得不先说一个有关僵尸状态的进程知识点了。

什么是僵尸进程?


        首先内核会释放终止进程(调用了exit系统调用)所使用的所有存情区,关闭所有打开的文件等,但内核为每一个终止子进程保存了一定量的信息,这些信息至少包括进程ID,进程的终止状态,以及该近程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息。
        而僵尸进程就是指:一个进程执行了exit系统调用函数后退出,而其父进程并没有为它收尸(调用wait或watpid来获得它的结束状态)的进程。任何一个子进程(init除外)在exit()后并非马上就消失,而是留下僵尸进程的数据结构,等待父进程处理,这是每个子进程都必需经历的阶段。另外子进程退出的时候会向其父进程发送一个SIGCHLD信号,通知父进程来收尸。——这个是关键,父进程能够及时回收子进程资源全凭借子进程退出时发送的这个信号来感知!

僵尸进程的目的?


        设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息至少包括进程ID,进程的终止状态,以及该进程使用的CPU时间,所以当终止子进程的父进程调用wait或waitpid时就可以得到这些信息,如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1 (init进程)。继承这些子进理的init进程将wait它们(也就是说init进程将wait它们,从而去除它们的僵尸状态)。 

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

void Count(int cnt){    //定时器
    while(cnt)fprintf("cnt: %d\r");
    fflush(stdout);
    cnt--;
}

void myhandler(int signo){
    printf("%d号信号已被捕况到,表明子进机退出后发送了SIGCHLD信号给父进程\n",signo);
}

int main(){
    printf("我是父进程,我的pid:%d\n",getpid());
    //信号捕捉
    signal(SIGCHLD,myhandler);
    pid_t id=fork();    //创建子进程

    //信号捕捉
    signal(SIGCHLD,myhandler);
                       
    if(id==0)(         //id==0 子进程
    printf("我是子进程,我的pid:%d 我的ppid:%d\n",getpid(),getppid());
    Count(5);
    printf("子进程已退\n");
    exit(1);   //退出子进程,子进程比父进任先退出后成为信,进和,需要父进和进行回收才行

    //那么父进程怎么个会知道子进程何时退出呢? 一子进程一定会发送信号给父进程,
    //父进任收到该信号就会对子进程进行资源的回收

    //id>0 为父进程
    else{
        while(1)
            sleep(1);
        }
        return 0;
    }

运行结果:

        从上图结果中可知:子进程退出后发送了一个信号给父进程,而我在代码中使用signal函数对SIGCHLD信号进行捕获,发现确实捕获到了,论证了子进程退出后会发送SIGCHLD信号给父进程,父进程正因为收到了该信号,才会回收子进程资源。

如何避免子进程成为僵尸进程?

        父进程可以采用阻塞等待的方式等子进程结束(方法1),也可以采用非阻塞地查询是否有子进程结束等待清理(也就是轮询的方式)——方法2;


        若采用第一种方式,父进程阻塞了就不能处理自己的工作了(父进程只能干等着,啥时候子进程退出了,啥时候父进程才能继续干自己的事情);而采用第二种方式,父进程在处理自己的工作的同时还要记得时不时地轮询一下(干着一件事还得想着另一件事),程序实现复杂,而且程序运行效率就较低了。 

        父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait()函数会导致父进程阻塞。我们可以在waitpid()函数中通过传递WNOHANG参数使父进程不阻塞立即返回,实现了第二种轮询非阻塞的方式;


        如果父进程很忙,可以用signal()信号捕捉函数,只有当子进程退出发送SIGCHLD信号后,父进程就会立刻捕捉到该信号,我们可以在信号处理函数中使用wait()/waitpid()去回收子进程资源,这样避免了父进程轮询非阻塞的方式去等待子进程退出,效率相比第二种方式更高一些。

其实还有一种两全其美的办法,如下:

       1.也是通过使用signal信号捕捉函数,但在该函数的参数处指定对SIGCHLD信号进行忽略递达操作,如:signal(SIGCHLD,SIGIGN),这种方式表示父进程对子进程的结束不关心,操作由内核系统进行回收。如果不想让父进程挂起,可以在父进程中加入一条语句: siqnal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。

代码如下:

        采用了忽略的方式去让父进程递达处理子进程退出时发送的SIGCHLD信号,那么它就再也不需要阻塞和非阻塞的方式去等待子进程结束,父进程完全不用管子进程的死活,专心做自己的事情,等到子进程退出后发送的SIGCHLD信号,父进程只接收信号和通知内核系统过来处理,系统会帮忙自动清理回收掉,不会产生僵尸进程。十分方便!

        通过两次调用fork。父进程首先调用fork()创建一个子进程然后waitpid()等待子进程退出,子进程再fork()一个孙子进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程由于其“父进程”(子进程)已经退出,所以子进程成为一个孤儿进程,那么孤儿进程会由init进程(也相当于是内核——进程的鼻祖)接管,子进程结束后,init进程会等待回收。

小贴士:

        使用signal信号捕捉方法忽略递达SIGCHLD信号,这常用于并发服务器的性能的一个技巧,因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可以内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。
  

猜你喜欢

转载自blog.csdn.net/weixin_69283129/article/details/131667628