文章目录引言:前面的系列文章介绍了进程的基础概念和相关常用的 API,本文将介绍两种比较特殊进程:孤儿进程与僵尸进程,进一步加深对进程的了解,避免进程使用过程中的一些坑点。
- 孤儿进程
- 僵尸进程
- 僵尸进程介绍
- 僵尸进程解决办法
- 总结
什么是孤儿进程?
父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程(Orphan Process)
每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。 因此孤儿进程并不会有什么危害。
总之:孤儿进程就是父进程退出了,但子进程还在执行。
示例:
#include#include #include #include int main() { pid_t pid = -1; // 创建子进程 pid = fork(); if (pid < 0) // 没有创建成功 { perror("fork"); return 1; } // 父进程 if (pid > 0) { printf("父进程休息3秒后退出。。。n"); printf("父进程: pid:%dn", getpid()); sleep(1); printf("父进程等太累了,现退出了。。。n"); exit(0); } while (1) { printf("子进程不停的工作,子进程:pid:%d,父进程:ppid:%dn", getpid(), getppid()); sleep(1); } return 0; }
运行结果:
yxm@192:~$ gcc test.c -o test yxm@192:~$ ./test 父进程休息3秒后退出。。。 父进程: pid:32075 子进程不停的工作,子进程:pid:32076,父进程:ppid:32075 父进程等太累了,现退出了。。。 子进程不停的工作,子进程:pid:32076,父进程:ppid:32075 yxm@192:~$ 子进程不停的工作,子进程:pid:32076,父进程:ppid:1 # 终端可以输入,同时有数据在输出 子进程不停的工作,子进程:pid:32076,父进程:ppid:1 子进程不停的工作,子进程:pid:32076,父进程:ppid:1
-
一般情况下,运行一个程序时,默认会切换到后台运行,当有输出的时候再切换到前台。
如上面的运行结果所示:创建子进程后,子进程复制了父进程内核部分的某些数据(比如标准输入、标准输出、标准错误),所以父进程和子进程的标准输出都是当前终端。又因为父进程是前台进程,所以会占用当前终端,但是父进程死亡后,终端占用被解除,但是子进程(变成孤儿进程)没有死亡,其标准输出依旧是当前终端。最终形成了,终端可以输入,同时有数据在输出的特殊情况。
-
【注意】ubuntu 系统中,字节界面中,产生的孤儿进程会被 1 号( 即 init 进程)进程收养,但是在图形界面中孤儿进程会被非1号进程收养。
每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉(子进程残留资源(PCB)存放于内核中),需要父进程去释放,进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免。
总之:僵尸进程就是子进程结束了,但父进程没有回收其资源。
#include#include #include #include int main() { int i = 0; pid_t pid = -1; // 创建子进程 pid = fork(); if (-1 == pid) // 没有创建成功 { perror("fork"); return 1; } // 子进程 if (0 == pid) { for (int i = 0; i < 5; i++) { printf("子进程做事%dn", i); sleep(1); } printf("子进程想不开,结束了自己。。。。n"); exit(0); } else if (pid > 0) { while(1) { printf("父进程休眠了, pid : %d, ppid : %dn", getpid(), getppid()); sleep(1); } } return 0; }
yxm@192:~$ gcc test.c -o test yxm@192:~$ ./test 父进程休眠了, pid : 33087, ppid : 30344 子进程做事0 父进程休眠了, pid : 33087, ppid : 30344 子进程做事1 子进程做事2 父进程休眠了, pid : 33087, ppid : 30344 父进程休眠了, pid : 33087, ppid : 30344 子进程做事3 子进程做事4 父进程休眠了, pid : 33087, ppid : 30344 父进程休眠了, pid : 33087, ppid : 30344 子进程想不开,结束了自己。。。。 父进程休眠了, pid : 33087, ppid : 30344 ^C yxm@192:~$ ps -aux ... ... yxm 33087 0.0 0.0 4516 756 pts/0 S+ 00:10 0:00 ./test yxm 33088 0.0 0.0 0 0 pts/0 Z+ 00:10 0:00 [test]僵尸进程解决办法#僵尸进程 yxm 33125 0.0 0.0 7476 832 ? S 00:10 0:00 sleep 180 yxm 33180 0.0 0.1 37860 3420 pts/1 R+ 00:10 0:00 ps -aux
方式一:僵尸进程的产生是因为父进程没有 wait() 子进程。所以如果我们自己写程序的话一定要,最好在父进程中通过 wait() 和 waitpid() 来避免僵尸进程的产生。
方式二:当系统中出现了僵尸进程时,我们是无法通过 kill 命令把它清除掉的。但是我们可以杀死它的父进程,让它变成孤儿进程,并进一步被系统中管理孤儿进程的进程收养并清理。具体步骤如下:
-
首先,需要确定僵尸进程的相关信息,比如父进程 ppid、僵尸进程的 pid 以及命令行等信息。可以执行如下命令:
ps -e -o stat,ppid,pid,cmd | egrep '^[Zz]'
参数说明:
- -e:参数用于列出所有的进程;
- -o:参数用于设定输出格式,这里只输出进程的stat(状态信息)、ppid(父进程pid)、pid(当前进程的pid),cmd(进程的可执行文件);
- egrep:是linux下的正则表达式工具:
- ‘^’:这是正则表达式,表示第一个字符的位置
- [Zz],表示 z 或者大写的 Z 字母,即表示第一个字符为 Z 或者 z 开头的进程数据,因为僵尸进程的状态信息以 Z 或者 z 字母开头。
-
然后,可以 kill -9 父进程 pid。kill 之后,僵尸进程将被 init 进程收养并清理
【补充】现在大多数 linux 系统,会将僵尸进程标识为 defunct,所以也可以通过如下命令来获取僵尸进程信息:
ps -ef | grep "defunct"
推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:服务器课程
孤儿进程与僵尸进程是两种特殊的进程,一种是父进程先退出,子进程变成孤儿,这种进程没有危害;一种是子进程先退出,父进程没有回收资源导致子进程变成僵尸,会占用系统资源。他们都发生过在父子进程之间。