僵尸(Zombie)进程
文章目录
- 1.僵尸进程
- 2.产生僵尸进程的原因
- 3.利用 wait 函数销毁僵尸进程
- 4.使用 waitpid 函数销毁僵尸进程
1.僵尸进程
进程完成工作后(执行完 main 函数中的程序后)应被销毁,但有时这些进程将变成僵尸进程,占用系统中的重要资源。这种状态下的进程称作“僵尸进程”,这也是给系统带来负担的原因之一。我们应该消灭这种进程。当然应掌握正确的方法,否则它会死灰复燃。
从下图可以看到,父进程的 PID 是 1166
,子进程的 PID 是 1167
。
如果我们使用 kill -9 1167
命令将子进程干掉,观察父进程会收到什么信号?
从下图可以看到,父进程收到了 SIGCHLD
信号,子进程变成了僵尸进程。
在 Unix 系统中,一个子进程结束了,但是他的父进程还活着,但该父进程没有调用 wait()/waitpid() 函数来进行额外的处置,那么这个子进程就会变成一个僵尸进程。
僵尸进程已经被终止,不干活了,但是依旧没有被内核丢弃掉,因为内核认为父进程可能还需要该子进程的一些信息。作为开发者,坚决不允许僵尸进程的存在。
如何干掉僵尸进程?
- 重启电脑。
- 手动地把僵尸进程的父进程干掉,僵尸进程就会自动消失。
- 一个进程被终止或者停止时,
SIGCHLD
信号会被发送给父进程,所以,对于源码中有 fork() 行为的进程,我们应该拦截并处理SIGCHLD
信号。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>// 信号处理函数
void sig_usr(int signo)
{int status;switch (signo){case SIGUSR1:printf("收到了SIGUSR1信号,进程id=%d!\n", getpid()); break;case SIGCHLD:printf("收到了SIGCHLD信号,进程id=%d!\n", getpid());// waitpid()函数获取子进程的终止状态,这样子进程就不会成为僵尸进程了// 第一个参数为-1,表示等待任何子进程// 第二个参数保存子进程的状态信息// 第三个参数提供额外选项,WNOHANG表示不要阻塞,让这个waitpid()立即返回pid_t pid = waitpid(-1, &status, WNOHANG);if (pid == 0) return; // 子进程没结束,会立即返回这个数字if (pid == -1) return; // 这表示这个waitpid()调用有错误,有错误也立即返回出去return; // 走到这里,表示成功,那也return吧break;}
}int main(int argc, char* const* argv)
{pid_t pid;printf("进程开始执行!\n");// 系统函数,第一个参数是个信号,第二个参数是个函数指针,代表一个针对该信号的捕捉处理函数if (signal(SIGUSR1, sig_usr) == SIG_ERR){printf("无法捕捉SIGUSR1信号!\n");exit(1);}if (signal(SIGCHLD, sig_usr) == SIG_ERR){printf("无法捕捉SIGCHLD信号!\n");exit(1);}pid = fork(); // 创建一个子进程// 要判断子进程是否创建成功if (pid < 0){printf("子进程创建失败,很遗憾!\n");exit(1);}// 现在父进程和子进程同时开始运行了 for (;;){sleep(1);printf("休息1秒,进程id=%d!\n", getpid());}printf("再见了!\n");return 0;
}
2.产生僵尸进程的原因
向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都会传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。也就是说,将子进程变成僵尸进程的正是操作系统。
问:此僵尸进程何时被销毁呢?
答:应该向创建子进程的父进程传递子进程的 exit 参数值或 return 语句的返回值。
问:如何向父进程传递这些值呢?
答:操作系统不会主动把这些值传递给父进程。只有父进程主动发起请求(函数调用)时,操作系统才会传递该值。换言之,如果父进程未主动要求获得子进程的结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态。也就是说,父母要负责收回自己生的孩子。
接下来的示例将创建僵尸进程。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc, char *argv[])
{pid_t pid = fork();if (pid == 0) // if Child Process{puts("Hi, I am a child process");}else{// 输出子进程ID。可以通过该值查看子进程状态(是否为僵尸进程)。printf("Child Process ID: %d\n", pid);// 父进程暂停30秒。如果父进程终止,处于僵尸状态的子进程将同时销毁。因此,延缓父进程的执行以验证僵尸进程。sleep(30); // Sleep 30 sec.}if (pid == 0)puts("End child process");elseputs("End parent process");return 0;
}
编译运行:
gcc zombie.c -o zombie
./zombie
程序开始运行后,将在如上所示状态暂停。跳出这种状态前(30秒内),应验证子进程是否为僵尸进程,该验证在其他控制台窗口进行。
可以看出,PID 为 1387 的进程状态为僵尸进程(Z+)。另外,经过 30 秒的等待时间后,PID 为 1386 的父进程和 PID 为 1387 的僵尸子进程同时销毁。
3.利用 wait 函数销毁僵尸进程
如前所述,为了销毁子进程,父进程应主动请求获取子进程的返回值。
#include <sys/wait.h>pid_t wait(int *statloc);// 成功时返回终止的子进程ID,失败时返回-1
调用此函数时如果已有子进程终止,那么子进程终止时传递的返回值(exit 函数的参数值、main 函数的 return 返回值)将保存到该函数的参数所指内存空间。但函数参数指向的单元中还包含其他信息,因此需要通过下列宏进行分离:
- WIFEXITED:子进程正常终止时返回“真”(true)。
- WEXITSTATUS:返回子进程的返回值。
也就是说,向 wait 函数传递变量 status 的地址时,调用 wait 函数后应编写如下代码:
if (WIFEXITED(status)) // 是正常终止的吗?
{puts("Normal termination!");printf("Child pass num: %d", WEXITSTATUS(status)); // 那么返回值是多少?
}
根据上述内容编写如下示例,在下面示例中,不会再让子进程变成僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, char *argv[])
{int status;// 第11行创建的子进程将在第15行通过main函数中的return语句终止。pid_t pid = fork();if (pid == 0){return 3;}else{printf("Child PID: %d\n", pid);// 第22行中创建的子进程将在第26行通过调用exit函数终止。pid = fork();if (pid == 0){exit(7);}else{printf("Child PID: %d\n", pid);// 调用wait函数。之前终止的子进程相关信息将保存到status变量,同时相关子进程被完全销毁。wait(&status);// 第36行中通过WIFEXITED宏验证子进程是否正常终止。如果正常退出,则调用WEXITSTATUS宏输出子进程的返回值。if (WIFEXITED(status))printf("Child send one: %d\n", WEXITSTATUS(status));// 因为之前创建了2个进程,所以再次调用wait函数和宏。wait(&status);if (WIFEXITED(status))printf("Child send two: %d\n", WEXITSTATUS(status));// 为暂停父进程终止而插入的代码。此时可以查看子进程的状态。sleep(30); // Sleep 30 sec.}}return 0;
}
编译运行:
gcc wait.c -o wait
./wait
输出结果:
可以看出,此时系统中并没有 PID 为 1497 和 1498 的进程,这是因为调用了 wait 函数,完全销毁了该子进程。另外,两个子进程终止时返回的 333 和 777 传递到了父进程。
这就是通过调用 wait 函数消灭僵尸进程的方法。调用 wait 函数时,如果没有已终止的子进程,那么程序将阻塞(Blocking)直到有子进程终止,因此要谨慎调用该函数。
4.使用 waitpid 函数销毁僵尸进程
wait 函数会引起程序阻塞,还可以考虑调用 waitpid 函数。这是防止僵尸进程的第二种方法,也是防止阻塞的方法。
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *statloc, int options);// 成功时返回终止的子进程ID(或0),失败时返回-1
// pid:等待终止的目标子进程的ID,若传递-1,则与wait函数相同,可以等待任意子进程终止
// statloc:与wait函数的statloc参数具有相同含义
// options:传递头文件sys/wait.h中声明的常量WNOHANG,即使没有终止的子进程也不会进入阻塞状态,而是返回0并退出函数
下面介绍调用 waitpid 函数的示例。调用 waitpid 函数时,程序不会阻塞。
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main(int argc, char *argv[])
{int status;pid_t pid = fork();if (pid == 0){// 调用sleep函数推迟子进程的执行。这会导致程序延迟15秒。sleep(15);return 24;}else{// while循环中调用waitpid函数。向第三个参数传递WNOHANG,因此,若之前没有终止的子进程将返回0。while (!waitpid(-1, &status, WNOHANG)){sleep(3);puts("sleep 3sec.");}if (WIFEXITED(status))printf("Child send %d\n", WEXITSTATUS(status));}return 0;
}
编译运行:
gcc waitpid.c -o waitpid
./waitpid
输出结果:
可以看出,第 222222 行共执行了 555 次。另外,这也证明 waitpid 函数并未阻塞。
相关文章:

僵尸(Zombie)进程
文章目录1.僵尸进程2.产生僵尸进程的原因3.利用 wait 函数销毁僵尸进程4.使用 waitpid 函数销毁僵尸进程1.僵尸进程 进程完成工作后(执行完 main 函数中的程序后)应被销毁,但有时这些进程将变成僵尸进程,占用系统中的重要资源。这…...

JS实现:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
题目:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 数列是 1,1,2,3,5,8,13,21....观察可以看出来从第三个数字开始…...

Verilog如何编写一个基础的Testbench
本文将讲述如何使用Verilog 编写一个基础的测试脚本(testbench)。在考虑一些关键概念之前,先来看看testbench的架构是什么样的。架构包括建模时间、initial块(initial block)和任务(task)。此文…...

基于JavaEE社区物业管理系统开发与实现(附源码资料)
文章目录1. 适用人群2. 你将收获3.项目简介4.技术栈5.测试账号6.部分功能模块展示6.1.管理员6.2.业主1. 适用人群 本课程主要是针对计算机专业相关正在做毕业设计或者是需要实战项目的Java开发学习者。 2. 你将收获 提供:项目源码、项目文档、数据库脚本、软件工…...

问一下ChatGPT:DIKW金字塔模型
经常看到这张DIKW金字塔模型图,还看到感觉有点过份解读的图,后面又加上了insight,impact等内容。 Data:是数据,零散的、无规则的呈现到人们眼前,如果你只看到这些数字,如果没有强大的知识背景&a…...
javaScript基础面试题 ---闭包
闭包1、闭包是什么?2、闭包可以解决什么问题?3、闭包的缺点1、闭包是什么? 闭包是一个函数加上到创建这个函数的作用域的链接,就是一个作用域可以访问到另一个作用域的变量,闭包‘关闭’了函数的自由变量 function f…...

如何自定义您的网站实时聊天图标
实时聊天图标是您网站上的一个按钮,可在访问者单击时打开实时聊天。它代表了您的企业与客户沟通的门户。这是您的网站访问者与您联系、提出问题和接收个性化推荐的一种方式,聊天图标的设计最好是简单且引人入胜,个性化的图标往往更能提现企业…...
Vue侦听器Watch
31. Vue侦听器Watch 1. 定义 Watch是Vue.js提供的一个观察者模式,用于监听数据的变化并执行相应的回调函数。虽然计算属性Computed在大多数情况下更合适,但有时也需要一个自定义的侦听器Watch。因为在有些情况下,我们需要在状态变化时执行一…...

云快充研发中心平台架构师谈云原生稳定性建设之路
作者:吕周洋 大家好,我是来自云快充研发中心的平台架构师吕周洋,今天我给大家分享云快充云原生稳定性之路。 点击查看:云快充研发中心平台架构师 吕周洋:云快充云原生稳定性治理之路 云快充成立于2016年,…...

ENVI IDL学习笔记之基本操作
前言ENVI IDL(交互式数据语言)是一个通用的科学计算包,它提供了一套数学函数、数据分析工具,以及一些科学可视化和动画工具。IDL 是 ENVI 图像处理和分析软件的基础,可用于编写脚本并自动执行许多使用 ENVI 图形用户界…...

多线程面试题
1. Sychronized的锁升级过程是怎样的? 2. Tomcat 中为什么要使用自定义类加载器? 3. 说说对线程安全的理解 4. 对守护线程的理解 5. 并发、并行、串行之间的区别 6. Java死锁如何避免? 7. 谈谈你对AQS的理解,AQS如何实现可重入锁&…...
YARN运行流程
YARN是Hadoop资源管理器,他是一个通用资源管理平台和调度平台,可为上层应用提供统一的资源管理和调度,MapReduce等运算程序则相当于运行于操作系统上的应用程序,YARN为这些程序提供运算所需的资源内存、cpu。 YARN并不清楚用户提…...

java八股系列——SpringMVC从接受请求到完成响应的过程
Spring的MVC框架是围绕一个DispatcherServlet来设计的,这个Servlet会把请求分发给各个处理器,并支持可配置的处理器映射、视图渲染、本地化、时区与主题渲染等,甚至还能支持文件上传。 流程大致如下: 用户发起请求:用…...
Elasticsearch索引全生命周期
索引(Index)是Elasticsearch中最重要的概念之一,也是整个Elasticsearch操作的基础,它是相互关联的文档的一个集合。在Elasticsearch种,数据存储为 JSON 文档,每个文档将一组键(字段或属性的名称)与其对应的…...

汇编指令学习(LOOP)
一、xor异或操作,相同为0,不同为1xor eax,eaxeax异或eax,相同为0,并把结果存放到eax,简单说该语句就是想eax寄存器清零。二、ECX,计数器mov ecx,0x3将ecx寄存器设置为3三、DEC减一操作dec ecxecx寄存器的值…...

Linux 配置本地yum源
挂载光盘 进入包 配置路径,查看在线yum源 移动在线yum源到/home/目录下 进入vi,任意取名以.repo结尾即可 按住i进行编辑,输入以下内容 注意gpgcheck1是检验,配置本地yum源不需要检验 写入上图内容按住:输入wq,点击回车…...

【PyTorch】教程:torch.nn.LeakyReLU
torch.nn.LeakyReLU 原型 CLASS torch.nn.LeakyReLU(negative_slope0.01, inplaceFalse) 参数 negative_slope (float) – 控制负值斜率,默认为 1e-2inplace (bool) – in-place 操作,默认为 False 定义 LeakyReLU(x)max(0,x)negative_slope∗min…...
【刷题】-- 基础 -- 二分查找
精于结构、敏于心智、熟于代码 方式:对于会的代码:学会以最快的速度构建,并以最快的速度书写;对于不会的代码:学会(以最短的路径下)看懂别人的代码。学会使用参考文档、熟悉每一个容器。 刷题位…...

Spark MLlib 特征工程
Spark MLlib 特征工程预处理特征选择归一化离散化Embedding向量计算特征工程制约了模型效果 : 决定了模型效果的上限 , 而模型调优只是在不停地逼近上限好的特征工程才能有好的模型 特征处理函数分类 : 预处理 : 将模型无法直接消费的数据,转为可消费的数据形式特…...
CentOS7 完全卸载 php
在 CentOS 7 使用 yum install 简单安装 php 后,发现 php 版本 5.4 ,太低了! 然后,使用 yum remove 简单卸载后,发现 php 还在,不干净! 只好 rpm 慢慢卸载 rpm -qa |grep php php-gd-5.4.16-4…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...