什么是僵尸进程,什么是孤儿进程,会带来什么问题,如何解决?
基本概念
子进程由父进程创建,而子进程和父进程的运行是异步的,谁也不确定谁先运行,当子进程完成工作时,父进程需要使用
wait
或 waitpid
来获取子进程终止状态
孤儿进程 👶
产生原因
父进程先于子进程退出(没有使用 wait
或
waitpid
),子进程还在运行,则这些子进程会变成孤儿进程,将被
init 进程(pid 为 1)收养,并由 init 对其进行状态收集
危害
孤儿进程由 init 进程收养,并不会有什么危害
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
// 对于 fork,父进程返回子进程 pid,子进程返回 0,失败返回 -1
pid_t pid = fork();
if (pid < 0) {
perror("Fork Error");
exit(1);
} else if (pid == 0) { // 子进程
printf("I'm the child process\n");
printf("pid: %d\tppid: %d\n", getpid(), getppid());
sleep(3); // 确保此时父进程已退出,被 init 进程收养
printf("pid: %d\tppid: %d\n", getpid(), getppid());
printf("child process is exited\n");
} else { // 父进程
printf("I'm the father process\n");
sleep(1); // 确保子进程先输出当前的 ppid
printf("father process is exited\n");
}
return 0;
}
I'm the father process
I'm the child process
pid: 27788 ppid: 27787
father process is exited
pid: 27788 ppid: 1
child process is exited
僵尸进程 🧟♀️
产生原因
子进程退出,父进程没有调用 wait
或 waitpid
获取子进程状态,则子进程的进程描述符会一直占用,被称为僵尸进程
危害
僵尸进程的进程描述符会一直占用,如产生大量僵尸进程会导致可用的 pid 大量减少,甚至不能产生新进程,且会占用空间
解决方案
僵尸进程的罪魁祸首是不回收子进程的父进程,可以 kill
发送
SIGKILL
或 SIGTERM
来枪毙他,让 init
进程回收这些孤儿进程,使这些僵死的孤儿进程瞑目
代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
// 对于 fork,父进程返回子进程 pid,子进程返回 0,失败返回 -1
pid_t pid = fork();
if (pid < 0) {
perror("Fork Error");
exit(1);
} else if (pid == 0) { // 子进程
printf("I'm the child process\n");
printf("pid: %d\tppid: %d\n", getpid(), getppid());
printf("child process is exited\n");
exit(0);
}
printf("I'm the father process\n");
sleep(1); // 确保子进程先输出当前的 ppid
system("ps -o pid,ppid,state,tty,command | grep zombie | grep -v grep");
printf("father process is exited\n");
return 0;
}
I'm the father process
I'm the child process
pid: 30196 ppid: 30195
child process is exited
30195 11875 S+ ttys008 /Users/world/code/tmp/zombie
30196 30195 Z+ ttys008 (zombie)
father process is exited
大量产生僵尸进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
while (1) {
// 对于 fork,父进程返回子进程 pid,子进程返回 0,失败返回 -1
pid_t pid = fork();
if (pid < 0) {
perror("Fork Error");
exit(1);
} else if (pid == 0) { // 子进程
printf("I am a child process.\nI am exiting.\n");
exit(0);
}
sleep(1); // 确保子进程先输出当前的 ppid
system("ps -o pid,ppid,state,tty,command | grep zombie | grep -v grep");
}
return 0;
}
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
30983 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
30983 30960 Z+ ttys008 (zombie2)
30992 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
30983 30960 Z+ ttys008 (zombie2)
30992 30960 Z+ ttys008 (zombie2)
31001 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
30983 30960 Z+ ttys008 (zombie2)
30992 30960 Z+ ttys008 (zombie2)
31001 30960 Z+ ttys008 (zombie2)
31014 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
30960 11875 S+ ttys008 /Users/world/code/tmp/zombie2
30965 30960 Z+ ttys008 (zombie2)
30974 30960 Z+ ttys008 (zombie2)
30983 30960 Z+ ttys008 (zombie2)
30992 30960 Z+ ttys008 (zombie2)
31001 30960 Z+ ttys008 (zombie2)
31014 30960 Z+ ttys008 (zombie2)
31023 30960 Z+ ttys008 (zombie2)
I am a child process.
I am exiting.
^C
最后使用 Ctrl-C 退出,僵尸进程被 init 收养释放,也可以使用
kill -9 <pid>
总结
无论是孤儿进程还是僵尸进程产生的原因都是父进程没有 wait
或
waitpid
,区别在于孤儿进程是父进程先走,僵尸进程是子进程先走