0%

Linux僵尸进程相关问题

[TOC]

僵尸进程的产生:

Linux下进程有种状态叫做僵尸状态

原因是父进程fork()出来的子进程结束之后,内核会给父进程发送一个SIGCHLD信号

这个SIGCHLD信号的作用是,及时通知父进程子进程的退出,让父进程通过一系列手段将他回收

回收的意义:

  1. 避免僵尸进程的产生,内存泄漏
  2. 且使得父进程能拿到子进程的退出状态,进行相应处理

如果父进程没有忽略这个信号(设置SIG_IGN),也没有等待(wait)子进程,而且结束的也比子进程晚(不能托管被init1号进程领养),并且也没有handler捕捉这个信号,那子进程就会进入僵尸状态; 维护者自己的task_struct开销,这也是一种内存泄露;

解决方式:

  1. 父进程阻塞调用wait()。

    调用细节:进程一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

    缺点:显然wait的调用是非常不合理的,阻塞时等待会让父进程干不了别的事情。

  2. 父进程非阻塞调用waitpid()。

    根据源码的观察,不难发现,其实wait就是waitpid的一种封装。

    缺点:waitpid虽然不会阻塞时等待,但是也需要占用cpu资源,定期的轮训waitpid看看有没有子进程处于僵尸状态,以便将他回收,也是有一定开销的。

  3. SIGCHLD信号。

    其实我们的子进程每次终止时内核都会给父进程发送一个SIGCHLD信号,如果父进程没有处理这个信号,也没有等待(wait)子进程,那他就会进入僵尸状态

    既然这样,我们可以用signal()函数+waitpid()接口处理一下这个信号,答到一个子进程退出之后,主动通知父进程将其回收的异步通知作用!(显然效率是比父进程主动wait和waitpid这种同步处理高!)

    注意,handler中使用while循环处理回收任务的原因是:SIGCHILD是个非可靠信号!

    一个父进程可能有很多子进程,如果多个子进程同时退出,SIGCHILD会出现信号丢失(只存在1个)。如果用if处理,也就只处理一个子进程。剩下的子进程也就变成了僵尸进程!

    因此我们发现有子进程退出,就用while循环来处理,如果恰好多个子进程退出,即便信号丢失,我们也能while通过waitpid的返回值不断释放他们,即便只有一个子进程退出,第二次循环waitpid会返回0,我们也会跳出循环!

  4. SIG_IGN信号。

    解决僵尸进程还有一个方式,就是让他变为“孤儿进程”,也就是init1号进程领养托管,进而释放这个子进程的资源!

    除了让父进程退出的比子进程早之外,我们可以通过如下操作,将SIGCHLD信号的处理方式设置为SIG_IGN–>忽略,这样代表父进程忽视了这个信号,子进程相当于被弃养,会被init1号进程领养。

    这样可让内核把僵尸子进程转交给init1号进程去处理释放,省去了父进程wait这个子进程的麻烦。

    这个小技巧常常被用于提升并发服务器的性能!

参考资料

[1]:https://blog.csdn.net/wtl666_6/article/details/129513009 “参考资料1”