Linux僵尸进程
一、僵尸进程简介
- 如果父进程比子进程先退出,子进程将被1号进程托管(这也是一种让程序在后台运行的方法)。
- 如果子进程比父进程先退出,而父进程没有处理子进程退出的信息,那么,子进程将成为僵尸进程。
- 示例:
#include <iostream>
#include <cstring>
#include <unistd.h>
using namespace std;
int main()
{
// 父进程没有退出之前,子进程退出
// 子进程将变成僵尸进程
if (fork() == 0) return 0;
while (true)
{
cout << "子进程持续运行" << endl;
sleep(1);
}
}
二、僵尸进程的危害
- 内核为每个子进程保留了一个数据结构,包括进程编号、终止状态、使用
CPU
时间等。 - 父进程如果处理了子进程退出的信息,内核就会释放这个数据结构,父进程如果没有处理子进程退出的信息,内核就不会释放这个数据结构,子进程的进程编号将一直被占用。
- 系统可用的进程编号是有限的,如果产生了大量的僵尸进程,将因为没有可用的进程编号而导致系统不能产生新的进程。
三、避免僵尸进程的方法
- 子进程退出的时候,内核会向父进程发送
SIGCHLD
信号,如果父进程用signal(SIGCHLD,SIG_IGN)
通知内核,表示自己对子进程的退出不感兴趣,那么子进程退出后会立即释放数据结构。
- 示例:
#include <iostream>
#include <cstring>
#include <signal.h>
#include <unistd.h>
using namespace std;
int main()
{
// 父进程没有退出之前,子进程退出
// 忽略由于子进程退出发出的信号,将不会产生僵尸进程
signal(SIGCHLD, SIG_IGN);
if (fork() == 0) return 0;
while (true)
{
cout << "子进程持续运行" << endl;
sleep(1);
}
}
- 父进程通过
wait()/waitpid()
等函数等待子进程结束,在子进程退出之前,父进程将被阻塞等待。
pid_t wait(int *stat_loc);
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid_t wait3(int *status, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage);
- 头文件:
<sys/types.h>
,<sys/wait.h>
- 返回值
pid
是子进程的编号。 stat_loc
是子进程终止的信息:
a)如果是正常终止,宏WIFEXITED(stat_loc)
返回真,宏WEXITSTATUS(stat_loc)
可获取终止状态;
b)如果是异常终止,宏WTERMSIG(stat_loc)
可获取终止进程的信号。- 示例
1
(通过exit(x)
或者return x
正常退出):
#include <iostream>
#include <cstring>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
if (fork() > 0)
{
int stat_loc;
// 阻塞父进程,直至子进程终止,获取子进程编号
pid_t pid = wait(&stat_loc);
// 正常终止
if (WIFEXITED(stat_loc))
{
cout << "子进程:" << pid << "正常终止,终止状态是" << WEXITSTATUS(stat_loc) << endl;
}
// 异常终止
else
{
cout << "子进程:" << pid << "异常终止,终止信号是:" << WTERMSIG(stat_loc) << endl;
}
}
else
{
while (true)
{
cout << "子进程即将退出..." << endl;
sleep(5);
exit(3);
}
}
}
- 示例
2
(通过信号异常退出-内存泄漏):
#include <iostream>
#include <cstring>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
if (fork() > 0)
{
int stat_loc;
// 阻塞父进程,直至子进程终止,获取子进程编号
pid_t pid = wait(&stat_loc);
// 正常终止
if (WIFEXITED(stat_loc))
{
cout << "子进程:" << pid << "正常终止,终止状态是:" << WEXITSTATUS(stat_loc) << endl;
}
// 异常终止
else
{
cout << "子进程:" << pid << "异常终止,终止信号是:" << WTERMSIG(stat_loc) << endl;
}
}
else
{
while (true)
{
// 内存泄漏导致子进程异常终止
int* pi = 0;
*pi = 100;
exit(0);
}
}
}
- 示例
3
(通过信号异常退出-接收信号):
#include <iostream>
#include <cstring>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
int main()
{
if (fork() > 0)
{
int stat_loc;
// 阻塞父进程,直至子进程终止,获取子进程编号
pid_t pid = wait(&stat_loc);
// 正常终止
if (WIFEXITED(stat_loc))
{
cout << "子进程:" << pid << "正常终止,终止状态是:" << WEXITSTATUS(stat_loc) << endl;
}
// 异常终止
else
{
cout << "子进程:" << pid << "异常终止,终止信号是:" << WTERMSIG(stat_loc) << endl;
}
}
else
{
while (true)
{
sleep(50);
exit(0);
}
}
}
- 如果父进程很忙,可以捕获
SIGCHLD
信号,在信号处理函数中调用wait()/waitpid()
,就是将对子进程退出的处理放在了信号处理函数中实现。
#include <iostream>
#include <cstring>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
// 子进程终止的信号处理函数
void func(int sig)
{
int stat_loc;
pid_t pid = wait(&stat_loc);
if (WIFEXITED(stat_loc))
{
cout << "子进程:" << pid << "正常终止,终止状态是:" << WEXITSTATUS(stat_loc) << endl;
}
else
{
cout << "子进程:" << pid << "异常退出,终止信号是:" << WTERMSIG(stat_loc) << endl;
}
}
int main()
{
// 子进程终止的信号处理
signal(SIGCHLD, func);
if (fork() > 0)
{
while (true)
{
cout << "父进程忙于执行任务....." << endl;
sleep(1);
}
}
else
{
while (true)
{
sleep(3);
int *pi = 0;
*pi = 10;
}
}
}