exit()
或者完成了main()
函数),但其父进程尚未通过wait()
或waitpid()
系统调用获取其退出状态的进程。fork()
创建一个子进程。exit()
终止。wait()
或waitpid()
读取子进程的退出状态,系统从进程表中移除僵尸进程。wait()
或waitpid()
:当子进程终止时,父进程未能及时调用这些系统调用来获取子进程的退出状态,导致子进程信息无法被清理。SIGCHLD
信号,通知其有子进程已终止。如果父进程忽略或未处理该信号,子进程可能无法被及时回收。init
进程(PID为1的进程)收养。init
会定期调用wait()
来清理任何孤儿进程,包括僵尸进程。使用ps
命令可以查看系统中的僵尸进程。例如:
ps aux | grep Z
在输出中,僵尸进程通常会显示为状态Z
,例如:
username 1234 0.0 0.0 0 0 ? Z 10:00 0:00 [process_name] <defunct>
1. 确保父进程调用wait()
或waitpid()
在编写多进程程序时,父进程应确保在合适的时机调用wait()
或waitpid()
来回收子进程。这可以通过以下方式实现:
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
// 子进程执行
printf("子进程PID: %d\n", getpid());
exit(0);
}
else {
// 父进程等待子进程结束
int status;
waitpid(pid, &status, 0);
printf("子进程已结束,PID: %d\n", pid);
}
return 0;
}
2. 处理SIGCHLD信号
父进程可以通过设置信号处理函数来自动处理子进程的终止,从而避免僵尸进程的产生。例如:
#include
#include
#include
#include
#include
#include
void sigchld_handler(int signo) {
(void)signo; // 避免未使用参数的警告
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
// 子进程执行
printf("子进程PID: %d\n", getpid());
exit(0);
}
else {
// 父进程继续执行其他任务
printf("父进程PID: %d,等待子进程终止...\n", getpid());
// 模拟父进程执行
sleep(5);
}
return 0;
}
3. 终止不必要的父进程
如果父进程不再需要对子进程的管理,可以让子进程成为init
(PID为1)的子进程。init
会自动回收子进程,防止僵尸进程的产生。可以通过“双重派生”(double-fork)技术实现:
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
// 第一个子进程
pid_t pid2 = fork();
if (pid2 < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid2 > 0) {
// 第一个子进程退出
exit(0);
}
else {
// 第二个子进程成为孤儿进程,被init收养
printf("孤儿进程PID: %d\n", getpid());
// 执行任务
sleep(10);
exit(0);
}
}
else {
// 父进程等待第一个子进程退出
wait(NULL);
// 父进程继续执行
printf("父进程继续执行...\n");
sleep(15);
}
return 0;
}
1. 创建僵尸进程
以下示例展示了如何产生一个僵尸进程。在该示例中,父进程创建子进程后不调用wait()
,导致子进程在终止后变成僵尸状态。
// zombie_example.cpp
#include
#include
#include
#include
#include
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
// 子进程执行
printf("子进程PID: %d,即将终止。\n", getpid());
exit(0);
}
else {
// 父进程不调用 wait(),睡眠以保留子进程为僵尸状态
printf("父进程PID: %d,子进程PID: %d 已创建。\n", getpid(), pid);
printf("父进程进入睡眠,不调用 wait()。\n");
sleep(30);
}
return 0;
}
编译与运行:
g++ zombie_example.cpp -o zombie_example
./zombie_example
观察僵尸进程:
在父进程睡眠期间,使用ps
命令查看系统中的僵尸进程:
ps aux | grep Z
输出:
username 1234 0.0 0.0 0 0 ? Z 10:00 0:00 [zombie_example] <defunct>
2. 消除僵尸进程
要消除僵尸进程,可以通过以下方法之一:
让父进程调用wait()
或waitpid()
:修改父进程代码,使其在子进程结束后调用wait()
,如下:
// 修改后的父进程部分
else {
printf("父进程PID: %d,子进程PID: %d 已创建。\n", getpid(), pid);
printf("父进程等待子进程终止。\n");
waitpid(pid, NULL, 0);
printf("子进程已被回收。\n");
}
这将确保子进程在终止后不再处于僵尸状态。
SIGCHLD
信号处理函数,自动回收子进程。init
进程收养,init
会调用wait()
回收其退出状态,从而消除僵尸进程。wait()
或waitpid()
回收子进程。理解和正确处理僵尸进程对于维护系统的稳定性和可靠性至关重要,特别是在开发需要频繁创建和管理子进程的应用程序时。