我们知道在unix/linux中,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。如果一个子进程结束了,但是他的父进程没有等待他, 那么这个子进程将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程,,因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init(进程号为1)来接管他,成为他的父进程,此时称这个进程为孤儿进程,其状态收集工作由init进程负责。
OK,首先说简单的结论:没有父进程的进程就是孤儿进程,孤儿进程会被init领养,成为一个准守护进程。如果进程他爹活着,但是不给子进程收尸(wait、waitpid),子进程就会变成僵尸(zombie)。守护进程(Daemon)是在一类脱离终端在后台执行的程序, 通常以 d 结尾, 随系统启动, 其父进程 (ppid) 通常是 init 进程。
下面详细介绍一下各个进程。
一、孤儿进程
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
实例:以下是一个孤儿进程的示例程序,在此程序中,让父进程先退出,然后子进程再次打印自己的父进程号:
#include
#include
#include
#include
int main()
{
pid_t pid;
//创建一个进程
pid = fork();
//创建失败
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//子进程
if (pid == 0)
{
printf("I am the childprocess.\n");
//输出进程ID和父进程ID
printf("pid:%d\tppid:%d\n",getpid(),getppid());
printf("I will sleep fiveseconds.\n");
//睡眠5s,保证父进程先退出
sleep(5);
printf("pid:%d\tppid:%d\n",getpid(),getppid());
printf("child process isexited.\n");
}
//父进程
else
{
printf("I am fatherprocess.\n");
//父进程睡眠1s,保证子进程输出进程id
sleep(1);
printf("father process is exited.\n");
}
return 0;
}
注:getpid函数可以获得当前进程的pid,getppid函数可以获得当前进程的父进程号。
以上程序编译运行后的输出为:
可以看出子进程1091后来被init进程接管(pid=1)。
二、僵尸进程
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
如果我们了解过linux进程状态及转换关系,我们应该知道进程这么多状态中有一种状态是僵死状态,就是进程终止后进入僵死状态(zombie),等待告知父进程自己终止后才能完全消失。但是如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程。僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息。一旦父进程得到想要的信息,僵尸进程就会结束。
1. 僵尸进程怎样产生的:
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了, 那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是 为什么系统中有时会有很多的僵尸进程。
2. 怎么查看僵尸进程:
利用命令ps,可以看到有标记为Z的进程就是僵尸进程。
3. 怎样来清除僵尸进程:
改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用 wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。
注:僵尸进程将会导致资源浪费,而孤儿则不会。
实例1:以下是一个孤儿进程的示例程序,在此程序中,子进程先退出,父进程不调用wait()或waitpid()清理子进程信息。
#include
#include
#include
#include
int main()
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process.I amexiting.\n");
exit(0);
}
printf("I am father process.I willsleep two seconds\n");
//等待子进程先退出
sleep(2);
//输出进程信息
system("ps -opid,ppid,state,tty,command");
printf("father process isexiting.\n");
return 0;
}
测试结果如下所示:
实例2:父进程循环创建子进程,子进程退出,造成多个僵尸进程。
#include
#include
#include
#include
int main()
{
pid_t pid;
//循环创建子进程
while(1)
{
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am a childprocess.\nI am exiting.\n");
//子进程退出,成为僵尸进程
exit(0);
}
else
{
//父进程休眠20s继续创建子进程
sleep(20);
continue;
}
}
return 0;
}
程序测试结果如下所示:
三、僵尸进程解决办法
1. 通过信号机制
子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。测试程序如下所示:
#include
#include
#include
#include
#include
static voidsig_child(int signo);
int main()
{
pid_t pid;
//创建捕捉子进程退出信号
signal(SIGCHLD,sig_child);
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am child process,pid id%d.I am exiting.\n",getpid());
exit(0);
}
printf("I am father process.I willsleep two seconds\n");
//等待子进程先退出
sleep(2);
//输出进程信息
system("ps -opid,ppid,state,tty,command");
printf("father process isexiting.\n");
return 0;
}
static voidsig_child(int signo)
{
pid_t pid;
int stat;
//处理僵尸进程
while ((pid = waitpid(-1, &stat, WNOHANG))>0)
printf("child %dterminated.\n", pid);
}
测试结果如下所示:
2. 两次fork()
《Unix 环境高级编程》8.6节说的非常详细。原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。测试程序如下所示:
#include
#include
#include
#include
int main()
{
pid_t pid;
//创建第一个子进程
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一个子进程
else if (pid == 0)
{
//子进程再创建子进程
printf("I am the first childprocess.pid:%d\tppid:%d\n",getpid(),getppid());
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一个子进程退出
else if (pid >0)
{
printf("first procee isexited.\n");
exit(0);
}
//第二个子进程
//睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里
sleep(3);
printf("I am the second childprocess.pid: %d\tppid:%d\n",getpid(),getppid());
exit(0);
}
//父进程处理第一个子进程退出
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
exit(0);
return 0;
}
测试结果如下图所示:
四、守护进程
同样我们需要了解一下什么是守护进程,守护进程就是在后台运行,不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务。习惯上守护进程的名字通常以d结尾(sshd),但这些不是必须的。
守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端,是为了避免进程在执行过程中的信息在任何终端上显示,并且进程也不会被任何终端所产生的终端信息所打断。
守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。
由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程。
下面介绍一下创建守护进程的步骤:
#include
#incldue
#include
#include
#include
#include
#include
#include
#include
#include
#incldue
#include
int main(void)
{
pid_t pid;
int i;
pid = fork(); //创建一个新进程,将来会是守护进程
if(pid == -1)
{
return -1;
}
else if(pid != 0){ //父进程调用exit,保证子进程不是进程组长
exit(EXIT_SUCCESS);
}
if(setsid() == -1) //创建新的会话区
{
return -1;
}
if(chdir("/") == -1) //将当前目录改成根目录
{
return -1;
}
for(i = 0;i < NR_OPEN;i++)
{
close(i);
}
open("/dev/null",O_RDWR); 重定向
dup(0);
dup(0);
return 0;
}
执行结果:
disda 26217 1 0 06:59 ? 00:00:00 ./dm01_demon 则出现了守护进程!
补充:
1. 孤儿进程有危害吗?
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
2. 僵尸进程有危害吗?
僵尸进程危害场景:“擒贼先擒王”。
例如有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程 退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程(父进程)之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程 就能瞑目而去了。这就是守护进程的作用,如果发生大量的僵尸进程,守护进程就会查找其父进程,然后无情的kill掉!
3. 为什么要尽量避免僵尸进程?
首先要明白,僵尸进程不是活着的进程,可以说就是一个数据结构,它是已经完成的任务的进程,但是不是它完成任务后就会烟消云散的,他会留下一点东西,这个东西就是他的进程Id,他的结束状态等,为什么了留下这个东西呢?因为这个是用来向他的父进程报告自己的完成状况用的,想想父进程为什么会创建一个进程,是用来完成任务的,父进程需要知道子进程的完成情况,所有出现这样的机制,对于僵尸进程只有父进程自己可以清理掉,调用wait等命令。就可以了。但是父进程不清理咋办,那么就说明僵尸进程存在,浪费了进程Id,进程的id是一种有限资源,用一个少一个啊,所以如果大量的僵尸进程存在的话,解决方法可以是杀掉无良的爹,孩子就可以被收养了。所以说,系统中的进程数量是有限的,虽然僵尸进程占用的资源和内存都比较少,但是它却占领着数字,可能会导致系统无法再创建新的进程,因此及时清除僵尸进程很重要!
参考来源:
http://www.cnblogs.com/Anker/p/3271773.html
http://blog.sina.com.cn/s/blog_4c6d89b20100h2od.html
http://www.cnblogs.com/wannable/p/6021617.html
http://blog.sina.com.cn/s/blog_58b48d1e0100fy6f.html