共计 4028 个字符,预计需要花费 11 分钟才能阅读完成。
linuxc 僵尸进程的产生和清除
什么是僵尸进程
僵尸进程是指它的父进程已经退出 (父进程没有等待(调用 wait/waitpid) 它),而该进程 dead 之后没有进程接受,就成为僵尸进程,也就是 (zombie) 进程。
僵尸进程是怎么样产生
一个进程在调用 exit 命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程 (Zombie) 的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
在 Linux 进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸。
如果他的父进程没安装 SIGCHLD 信号处理函数调用 wait 或 waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了,那么 init 进程自动会接手这个子进程,为它收尸,它还是能被清除的。
但是如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是为什么系统中有时会有很多的僵尸进程。系统所能使用的进程号是有限的, 如果大量的产生僵死进程, 将因为没有可用的进程号而导致系统不能产生新的进程.
如下代码,子进程先于父进程退出,在父进程执行的 5s 内,子进程将为僵尸:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main() {
// 子进程的 pid
int c_pid;
int pid;
if ((pid = fork())) {
// 父进程
c_pid = pid;
printf("The child process is %d\n", c_pid);
sleep(5);
exit(0);
} else {
// 子进程
printf("I'm a child.\n");
exit(0);
}
}
如何快速找出僵尸程序
打开终端并输入下面命令:
ps aux | grep Z
会列出进程表中所有僵尸进程的详细内容。
怎么干掉这些僵尸程序
用 SIGCHLD 信号来杀死僵尸进程
正常情况下我们可以用 SIGKILL 信号来杀死进程,但是僵尸进程已经死了,你不能杀死已经死掉的东西。因此你需要输入的命令应该是:
ps -ef | grep pid
首先查看僵尸进程的父进程 pid,输出结果的第三列 ppid 就是父进程的 pid
kill -s SIGCHLD ppid
将这里的 pid 替换成父进程的进程 id,这样父进程就会删除所有以及完成并死掉的子进程了。
程序中避免僵尸进程的产生
- 父进程调用 wait/waitpid。
- 父进程先于子进程退出,子进程自动托管到 init 进程。
- fork()两次,调用孙子进程。
- 绑定 SIGCHLD 信号处理函数,在信号处理函数中调用 wait。
- 绑定 SIGCHLD 信号处理函数 SIG_IGN。(不推荐)
父进程调用 wait/waitpid
父进程调用 waitpid()等函数来接收子进程退出状态。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
// 子进程的 pid
int c_pid;
int pid;
if ((pid = fork())) {
// 父进程
c_pid = pid;
printf("The child process is %d\n", c_pid);
// 阻塞等待子进程
int status;
if ((pid = wait(&status)) != -1 && pid == c_pid) {
// 成功回收子进程
printf("The child exit with %d\n", WEXITSTATUS(status));
fflush(stdin);
}
printf("Now , The child has been exit , and I will sleep.\n");
sleep(20);
exit(0);
} else {
// 子进程
printf("I'm a child.\n");
sleep(5);
exit(0);
}
}
父进程先 exit, 子进程托管到 init 进程
父进程先结束,子进程则自动托管到 Init 进程(pid = 1)。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
// 子进程的 pid
int c_pid;
int pid;
if ((pid = fork())) {
// 父进程
printf("I'm father id = %d.The child process is %d\n", getpid(), pid);
exit(0);
} else {
// 子进程
printf("I'm a child.\n");
sleep(5);
exit(0);
}
}
fork()两次,调用孙子进程
父进程一次 fork()后产生一个子进程随后立即执行 waitpid(子进程 pid, NULL, 0)来等待子进程结束,然后子进程 fork()后产生孙子进程随后立即 exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给 Init 进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为 Init,Init 进程在其子进程结束时会自动收尸,这样也就不会产生僵尸进程了。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
// 子进程的 pid
int c_pid;
int pid;
if ((pid = fork())) {
// 父进程
c_pid = pid;
printf("I'm father id = %d.The child process is %d\n", getpid(), c_pid);
// 阻塞等待子进程
int status;
if ((pid = wait(&status)) != -1 && pid == c_pid) {
// 成功回收子进程
printf("The child exit with %d\n", WEXITSTATUS(status));
fflush(stdin);
}
exit(0);
} else {if ((pid = fork())) {
// 子进程
printf("I'm a child pid = %d. my child pid = %d. exit.\n", getpid(), pid);
exit(0);
} else {
// 孙子进程
sleep(20);
printf("I'm a grandson pid = %d. exit\n", pid);
exit(0);
}
}
}
绑定 SIGCHLD 信号处理函数,在信号处理函数中调用 wait
阻塞等待,那么父进程就无法做其他事;
父进程退出,在创建守护进程时候会有,但是我们并不想让父进程退出;
fork()两次,孤儿进程不易由我们程序来管理;
man wait, 查看 NOTES 章节,可以找到:子进程退出的时候,会发送 SIGCHLD 信号,默认的 POSIX 不响应
所以设置处理 SIGCHLD 信号的函数,可以用于异步回收 fork 子进程。
注意:信号产生触发信号处理函数时,会中断某些函数,例如 poll、select、epoll 等。
详情见《unix 网络编程》5.8 小节,POSIX 信号处理
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
void sig_chld(int num) {
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) // 循环处理完所有的信号
printf("child %d terminated\n", pid);
return;
}
int main() {
// 子进程的 pid
int c_pid;
int pid;
signal(SIGCHLD, sig_chld);
if ((pid = fork())) {
// 父进程
c_pid = pid;
printf("The child process is %d\n", c_pid);
// 父进程不用等待,做自己的事情吧~
for (int i = 0; i < 10; i++) {printf("Do parent things.\n");
sleep(1);
}
exit(0);
} else {
// 子进程
printf("I'm a child.\n");
sleep(2);
exit(0);
}
}
绑定 SIGCHLD 信号处理函数 SIG_IGN
不推荐使用,因为 POSIX 标准中未规定 SIG_IGN,会破坏程序的可移植性。
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
// 子进程的 pid
int c_pid;
int pid;
signal(SIGCHLD,SIG_IGN);
if ((pid = fork())) {
// 父进程
c_pid = pid;
printf("The child process is %d\n", c_pid);
// 父进程不用等待,做自己的事情吧~
for (int i = 0; i < 10; i++) {printf("Do parent things.\n");
sleep(1);
}
exit(0);
} else {
// 子进程
printf("I'm a child.\n");
sleep(2);
exit(0);
}
}