先上代码:
僵尸状态&僵尸进程
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello\n");
pid_t ret = fork();
if(ret < 0)
{
perror("fork");
return 0;
}
else if(ret == 0)
{
//child
while(1)
{
printf("i am child, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
else
{
//father
while(1)
{
printf("i am father, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
输出结果为:
由上图可知pid为5577的为子进程,pid为5576的为父进程,pid为5576的为父进程的父进程。
1. 创建子进程的代码,使用pstack命令可以查看程序的调用堆栈,注意pstack获取的是程序一瞬间的代码,或许下一个就不会再执行当前代码。
举例:pstack 5577,查看pid为5577进程的调用情况,由下图可知代码在19行调用,对应子进程。
pstack 5576,查看pid为5576进程的调用情况,右下图可知代码在28行调用,对应父进程。
2. 僵尸状态
修改代码如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello\n");
pid_t ret = fork();
if(ret < 0)
{
perror("fork");
return 0;
}
else if(ret == 0)
{
//child
//while(1)
{
printf("i am child, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(20);
}
}
else
{
//father
while(1)
{
printf("i am father, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
运行结果:
ps aux | grep ./test,查看当前进程状态。由下图可以看到,当前进程中只有一个父进程。
ps aux | grep test,再次查看当前进程状态。右下图可以看到,当前进程中有父进程和子进程。
原理:子进程先于父进程退出,父进程来不及回收子进程的资源,导致子进程变成僵尸进程,而子进程对应的task_struct还在内核当中,双向链表管理着,对于子进程而言,程序员已经退出了,并不能任何执行任何用户写的代码了;子进程的状态会变成Z状态:刀枪不入!!!
3. 产生僵尸进程之后,僵尸进程对应的状态是Z,并且使用kill命令或者强杀命令kill -9,都结束不了僵尸进程。
4. 僵尸进程的危害:
产生僵尸进程之后,僵尸进程是不能被强杀杀死的,原因是僵尸进程已经死了,意味着在操作系统内核双向链表还维护着僵尸进程的task_struct。内核维护这样的结构体,是需要消耗内存的;程序员还结束不了僵尸进程,操作系统就会产生内存泄漏。
5. 如何预防僵尸进程的产生
- 子进程不要先于父进程退出,但可能性并不是很大 ---- 不可取的。
- 进程等待,在后续博客再详细说明 ---- 可取,较为优雅。
- 可以将父进程kill掉,该进程就变成了孤儿进程,然后让僵尸进程被1号进程领养,1号进程回收僵尸级进程的资源(杀敌一千自损800 ---- 不可取)
孤儿状态
1. 原理:
父进程先于子进程退出,子进程会被1号进程所领养,1号进程被称为init进程。1号进程会在子进程退出时,回收子进程的退出信息,防止子进程变成僵尸进程。
ps aux | grep 1,查看1号进程,1号进程是Linux操作系统当中启动的第一个进程,该进程会启动若干个独立的子进程。
2. 现象
继续修改代码如下,修改后父进程先退出,子进程后退出:
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello\n");
pid_t ret = fork();
if(ret < 0)
{
perror("fork");
return 0;
}
else if(ret == 0)
{
//child
//while(1)
{
printf("i am child, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(20);
}
}
else
{
//father
//while(1)
{
printf("i am father, pid=%d, ppid=%d\n", getpid(), getppid());
sleep(5);
}
}
return 0;
ps -ef | grep test,查看子进程和父进程的pid和ppid,第一次查看如下:
第二列是pid,第三列是ppid,可知9041是当前进程,9040是当前进程的父进程,4289是当前进程中父进程的父进程也就是终端bash。下面是第二次查看:
由于父进程提前退出,所以子进程会被1号进程领养。因此当前进程的父进程变成了1号进程。我们称这样的进程为“孤儿进程”。
3. 注意:孤儿进程不是一种状态!!!而是一种进程种类的名称。
4. 子进程和父进程代码共享,数据独有!
修改代码如下:
#include <stdio.h>
#include <unistd.h>
int g_val = 100;
int main()
{
printf("hello~\n");
pid_t ret = fork();
if(ret < 0)
{
perror("fork");
return 0;
}
else if(ret == 0)
{
//child
//while(1)
{
printf("i am child pid=[%d], ppid=[%d]\n", getpid(), getppid());
g_val -= 50; //50
printf("child, g_val:%d\n", g_val);
sleep(20);
}
}
else
{
//ret > 0 : father
while(1)
{
printf("i am father pid=[%d], ppid=[%d]\n", getpid(), getppid());
g_val += 50; // 150
printf("father, g_val:%d\n", g_val);
sleep(1);
}
}
return 0;
}
由上图结果能看出来,父进程和子进程结果相互独立,互补干扰。