一、什么是僵尸进程和孤儿进程(概念、危害及解决办法)
僵尸进程:
一个进程使用 fork 创建子进程,如果子进程退出而父进程并没有调用 wait() 或者 waitpid() 获取子进程信息,
那么子进程的描述符仍然保存在系统中。这种进程就被称为僵尸进程 -------- 即 Z 进程
危害及解决办法:
一个进程会定期的产生一些子进程,这些子进程由于处理的事情很少并且处理完后会退出,即生命周期短。但是这些子进程退出之后,
它们的状态信息还会保留在系统中,它们的父进程只管生成新的子进程,并没有去调用 wait() 或者 waitpid() 去获取子进程的状态信息,
系统运行一段时间之后就会有大量的僵死进程,用 ps 命令来查看的话就会看到许多状态为 z 的进程。
危害:占用资源不放,正常进程可能无法进行创建
解决办法:
我们要解决的话就只能找到那个产生大量僵死进程的父进程,只有杀死掉那个父进程 (通过 kill 发送 SIGTERM 或 SIGKILL)
杀死掉那个父进程之后,那些僵死进程就成了孤儿进程,孤儿进程会被 init 进程接管,init 会 wait 掉这些孤儿进程并且释放它们在系统中
占用的资源这些僵死的孤儿进程就会死去。
继续僵尸进程:
1. 产生原因:
在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)它,那么他将变成一个僵尸进程。
通过ps命令查看其带有defunct的标志。僵尸进程是一个早已死亡的进程,但在进程表 (processs table)中仍占了一个位置(slot)。
但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程。因为每个进程结束的时候,
系统都会扫描当前系统中所运行的所有进程,看看有没有哪个 进程是刚刚结束的这个进程的子进程,如果是的话,就由Init进程来接管他,
成为他的父进程,从而保证每个进程都会有一个父进程。而Init进程会自动 wait其子进程,因此被Init接管的所有进程都不会变成僵尸进程。
2. 原理分析:
每个Unix进程在进程表里都有一个进入点(entry),核心进程执 行该进程时使用到的一切信息都存储在进入点。
当用 ps 命令察看系统中的进程信息时,看到的就是进程表中的相关数据。当以fork()系统调用建立一个新的进程后,
核心进程就会在进程表中给这个新进程分配一个 进入点,然后将相关信息存储在该进入点所对应的进程表内。
这些信息中有一项是其父进程的识别码。
子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。那么会不会因为父进程太忙来不及
wait 子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢?不会。因为UNIX提供了一种机制可以保证,
只要父进程想知道子进程结束时的 状态信息,就可以得到。这种机制就是:当子进程走完了自己的生命周期后,它会执行exit()
系统调用,内核释放该进程所有的资源,包括打开的文件,占用 的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,
退出码exit code,退出状态the terminationstatus of the process,运行时间the amount of CPU time taken by the process等),
这些数据会一直保留到系统将它传递给它的父进程为止,直到父进程通过wait / waitpid来取时才释放。
3.解决方法:
(1) 父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
执行wait()或waitpid()系统调用,则子进程在终止后会立即把它在进程表中的数据返回给父进程,此时系统会立即删除该进入点。
在这种情形下就不会产生defunct进程。
(2) 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler。在子进程结束后,父进程会收到该信号,可以在handler中
调用wait回收。
(3) 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCLD, SIG_IGN)或signal(SIGCHLD, SIG_IGN)通知内核,
自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收,并不再给父进程发送信号
(4)fork两次,父进程fork一个子进程,然后继续工作,子进程fork一个孙进程后退出,那么孙进程被init接管,孙进程结束后,
init会回收。不过子进程的回收还要自己做。
孤儿进程概念:
如果父进程退出而它的一个或多个子进程还在运行,那么这些子进程就被称为孤儿进程孤儿进程最终将被 init 进程 (进程号为 1 的
init进程) 所收养并由 init 进程完成对它们的状态收集工作。
孤儿进程说明:
孤儿进程是没有危害的,孤儿进程是没有父进程的子进程,当孤儿进程没有父进程时,内核就会init设置为孤儿进程的父进程,
init进程就会调用wait去释放那些已经退出的子进程,当孤儿进程完成其声明周期之后,init会释放掉其状态信息。
每一个进程(init除外)在执行exit之后,不会马上消失会留下一个称为僵尸进程的数据结构来等父进程处理。父进程没有及时
处理的话,用ps命令来查看就会发现其状态为“z”。父进程及时处理的话,虽然ps之后状态信息不是“z”但并不等于子进程不经过
僵尸状态。
僵尸进程测试程序:
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id > 0)
{
printf("parent[%d] is sleeping ... \n", getpid());
sleep(30);
}
else
{
printf("child[%d] is begin z ... \n", getpid());
exit(EXIT_SUCCESS);
}
//printf("Hello world\n");
return 0;
}
测试结果:
孤儿进程测试程序:
#include
#include
#include
#include
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
printf("I am child, pid: %d\n", getpid());
sleep(10);
}
else
{
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
//printf("Hello world\n");
return 0;
}
孤儿进程测试结果: