c++ linux环境编程——僵尸进程(zombie)
目录
注:
本文章内容均来自本人的个人笔记为个人学习总结,禁止他人转载,参考自B站课程:码农论坛《C++环境高级编程》。由于当时方便记笔记,笔记中部分图片来源于原课程视频截图,版权归原作者“码农论坛”及相关权利人所有。对于linux系统,文章中我用的ubuntu,up主用的centos,但原理是相同的,不影响技术学习。
本笔记无任何商业用途(除开csdn官方操作),仅供个人学习交流。感谢原up主的课程分享!
一,啥是僵尸进程?
在 Linux 中,僵尸进程(Zombie Process)是指子进程已结束运行,但父进程尚未调用 wait() 或 waitpid() 回收其退出状态,导致该进程在进程表中保留一个条目,状态显示为 <defunct>。它不再占用CPU,但会占用进程表资源,数量过多可能导致系统无法创建新进程。
先看代码:

这样,父进程先退出,编译运行,查看进程状态:

一个是20s内,一个是20s后,父进程已经退出了。
子进程的父进程之前是7301,现在是1,
修改代码:

让改程序再后台运行。
如何让程序再后台运行?
方法一:加上&符号

这样可以运行,但是影响一点观察。
使用kill test命令停下来:

方法2:使用一行语句:

此时,无需&符号。
笔记:
如果父进程比子进程先退出,子进程将被1号进程托管(这也是一种让程序在后台运行的方法)。
如果子进程比父进程先退出,而父进程没有处理子进程退出的信息,那么,子进程将成为僵尸进程。
代码:

若是子进程,就直接退出,是父进程,就继续运行
编译运行:

因为父进程没有退出,所以在这个界面上不能输其他命令。
查看进程:

注意,下面这个就是子进程,子进程已经退出了,但是它的信息还保存在系统当中。
而且子进程这里显示了defunct,就是指失效了。
这种失效的进程,又叫僵尸进程。
使用top命令:

这里的zombie,就是僵尸进程!
二,僵尸进程的危害:
内核为每个子进程保留了一个数据结构,包括进程编号、终止状态、使用CPU时间等。父进程如果处理了子进程退出的信息,内核就会释放这个数据结构,父进程如果没有处理子进程退出的信息,内核就不会释放这个数据结构,子进程的进程编号将一直被占用。系统可用的进程编号是有限的,如果产生了大量的僵尸进程,将因为没有可用的进程编号而导致系统不能产生新的进程。
三,如何避免僵尸进程???
1)子进程退出的时候,内核会向父进程发头SIGCHLD信号,如果父进程用signal(SIGCHLD,SIG_IGN)通知内核,表示自己对子进程的退出不感兴趣,那么子进程退出后会立即释放数据结构。
代码:

编译运行:

查看进程:

此时,只有父进程了,没有僵尸进程!
这个方法只能让子进程退出,但是父进程得不到子进程退出的信息
要想得到子进程退出信息,要下面两个方法,就是2)3):
2)父进程通过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);
返回值是子进程的编号。
stat_loc是子进程终止的信息:a)如果是正常终止,宏WIFEXITED(stat_loc)返回真,宏WEXITSTATUS(stat_loc)可获取终止状态;b)如果是异常终止,宏WTERMSIG(stat_loc)可获取终止进程的信号。
sts 是 32 位整数,不同位存储不同信息,系统提供宏来解析,核心宏如下:
表格:
|
宏 |
作用 |
示例值含义 |
|
WIFEXITED(sts) |
判断子进程是否正常退出(通过 exit()/return) |
返回 true → 正常退出;false → 异常退出 |
|
WEXITSTATUS(sts) |
提取正常退出的退出码(仅 WIFEXITED(sts) 为 true 时有效) |
0 → 正常结束;1 → 业务逻辑错误等 |
|
WTERMSIG(sts) |
提取导致异常退出的信号编号(仅 WIFEXITED(sts) 为 false 时有效) |
9 → 被 kill -9 终止;11 → 段错误(SIGSEGV) |
第一个wait函数,返回子进程编号,传地址。
代码:
#include <iostream> // C++ 标准输入输出(替代C的printf)
#include <sys/types.h> // 定义 pid_t(进程ID类型)
#include <sys/wait.h> // 包含 wait() 函数和退出状态解析宏
#include <unistd.h> // 包含 fork()、sleep() 等系统调用
using namespace std;
int main()
{
// 1. 创建子进程:fork() 返回值 >0 是父进程,=0 是子进程
if (fork() > 0)
{ // ------------------------ 父进程分支 ------------------------
int sts; // 存储子进程的退出状态(核心:是一个32位的状态码,包含退出类型/退出码/信号等信息)
// 2. 阻塞等待子进程退出:wait(&sts) 会暂停父进程,直到任意子进程结束
// 返回值:结束的子进程PID;如果出错返回-1
pid_t pid = wait(&sts);
// 3. 打印结束的子进程ID
cout << "已终止的子进程编号是: " << pid << endl;
// 4. 解析子进程退出状态(核心:通过系统宏解析sts)
if (WIFEXITED(sts)) {
// 4.1 子进程「正常退出」(通过 exit()/return 退出)
// WEXITSTATUS(sts):提取正常退出时的退出码(0-255)
cout << "子进程是正常退出的,退出状态是: " << WEXITSTATUS(sts) << endl;
} else {
// 4.2 子进程「异常退出」(被信号终止,比如段错误、kill -9)
// WTERMSIG(sts):提取导致子进程终止的信号编号
cout << "子进程是异常退出的,终止它的信号是: " << WTERMSIG(sts) << endl;
}
}
else
{ // ------------------------ 子进程分支 ------------------------
sleep(5); // 子进程休眠5秒(模拟业务逻辑执行)
// 以下两行是「异常退出测试代码」,注释时子进程正常退出,取消注释触发段错误
// int *p = 0; // 空指针(指向内存地址0)
// *p = 10; // 解引用空指针 → 触发段错误(信号SIGSEGV,编号11)
exit(0); // 子进程正常退出,退出码为0(等同于 return 0)
}
return 0;
}
wait(&sts) —— 父进程等待子进程的核心函数
作用:父进程调用后会阻塞(暂停执行),直到任意一个子进程退出,才会继续执行;
参数 sts:输出型参数,存储子进程的退出状态(不是简单的数字,是包含多种信息的 32 位数据);
返回值:成功返回退出的子进程 PID,失败返回 - 1(比如没有子进程);
为什么需要 wait():如果父进程不调用 wait(),子进程退出后会变成「僵尸进程」(占用进程表资源),wait() 会清理僵尸进程并获取退出状态。
编译运行,在这5s内查看进程:

运行结束,查看结果:

子进程编号,3083,刚好显示子进程退出!
这里子进程是正常退出的,退出状态是0
将退出状态改为1(exit(1))也是:

再来查看异常退出的情况;
编译运行,查看进程,并kill子进程:


再次运行程序,查看进程编号,用-9杀死程序:
注意:每次运行进程编号都不同!!!


接着,启用这行代码:

这里直接操作空指针,产生段错误,信号11

3)如果父进程很忙,可以捕获SIGCHLD信号,在信号处理函数中调用wait()/waitpid()。
代码:

父进程是一个死循环,而子进程在运行5s之后异常退出(解引用空指针,出现段错误):
编译运行:

子进程退出后,查看进程:

发现只有父进程,子进程已经退出了。
再次声明:
本文章内容均来自本人的个人笔记为个人学习总结,禁止转载。
参考自B站课程:码农论坛《C++环境高级编程》。本文章保留了原课程的部分代码以及运行结果的截图。
根据《中华人民共和国著作权法》第二十四条规定,本笔记引用上述内容系为个人学习、研究之目的,属于“合理使用”范畴,不影响原作品的正常使用,亦不损害原著作权人的合法权益。
本笔记无任何商业用途,仅供个人学习交流。感谢原up主的课程分享!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)