【TCP/IP】多进程服务器的实现(进阶) - 僵尸进程及wait、waitpid函数

news2024/11/29 22:34:00

目录

僵尸(Zombie)进程

僵尸进程的产生机制

僵尸进程的危害

僵尸进程的销毁

wait函数

waitpid函数


        进程管理在网络编程中十分重要,如果未处理好,将会导致出现“僵尸进程”,进而影响服务器端对进程的管控。

僵尸(Zombie)进程

        第一次听到这个名词大家可能会有些陌生,为什么叫“僵尸”进程?事实上,这个词用的很形象也很恰当:当一个进程使用fork创建子进程后,若出现子进程先于父进程结束,并且此时父进程没有对子进程的资源进行释放回收,那么这个子进程将成为一个“僵尸进程”,并始终占据着一个进程号。

僵尸进程的产生机制

        通过使用fork函数可以制造一些“僵尸进程”。根据之前的定义,让我们用代码来引出僵尸进程。

zombie.cpp

#include <stdio.h>
#include <unistd.h>

#define CIRCLE 40

int main(int argc, char *argv[])
{
    pid_t pid = fork();

    if (pid == 0) // 子进程
    {
        printf("I'm child process\n");
    }
    else
    {
        printf("Child Process ID: %d \n", pid);
        sleep(CIRCLE); // 休眠20s
    }

    pid == 0 ? printf("Child process end")
             : printf("Parent process end");

    return 0;
}

 运行结果(注意要编译出来运行哦~一些IDE会默认对僵尸进程进行处理):

9f63376908a944438ec0353bf9088f7d.png

5562a1cf92e4418da88f9621e94ad54a.png

        通过 ps au 指令,我们可以查看所有当前进程的具体信息。不难发现,ID为10476的进程被标记为僵尸进程(Z+),而经过40秒的等待后,子进程和父进程同时被销毁,僵尸进程消失。

补充:

用终端打开程序时,我们可以用 & 符号将窗口中输入的指令放到后台去运行。

比如我们编译好的zombie程序,输入 ./zombie & 后程序开始执行,继续输入 ps au,可以在同一个终端下查看进程的变化。

僵尸进程的危害

        出现少量的僵尸进程并不会对操作系统造成太大影响,但如果数量多了,那么操作系统将可能因为没有可用的进程号(僵尸进程也占用进程号)而导致系统无法创建新的进程。

僵尸进程的销毁

        在这之前,我们应晓得如何使程序得到结束,具体方式有以下几种:

  • 传递参数并调用exit函数
  • main函数中执行return语句并返回值

        为了正确销毁子进程,父进程应主动请求获取子进程的返回值。在<sys/wait.h>库中,有2个方法可以提供给我们帮助。

wait函数

#include <sys/wait.h>

pid_t wait(int * statloc);

//成功时退回终止的子进程 ID, 失败时返回-1。

        调用此函数时如果已有子进程终止 ,那么子进程终止时传递的返回值(exit函数的参数值、main函数的return返回值)将保存到该函数的参数所指内存空间内。但函数参数指向的单元中还包含其他信息,因此需要通过以下宏进行分离:

  • WIFEXITED(整数型变量):子进程正常终止时,返回true
  • WEXITSTATUS(整数型变量):获取子进程的的返回值

如何使用呢?请看以下例子:

wait.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define CIRCLE 40

int main(int argc, char *argv[])
{
    int status;

    pid_t pid = fork(); //这里创建的子进程将在通过return语句终止

    if (pid == 0) // 子进程进入
    {
        return 111;
    }
    else
    {
        printf("Child process ID: %d \n", pid);
        pid = fork(); //这里创建的进程将通过下面的exit()函数终止
        if (pid == 0)
        {
            exit(222); //exit中所填常量即返回值
        }
        else
        {
            printf("Child process ID: %d \n", pid);
            //之前终止的子进程信息将保存在status变量中,同时相关子进程被销毁
            wait(&status); 
            
            //通过WIFEXITED验证子进程是否正常终止。
            //如果正常退出,则调用WEXITSTATUS输出子进程的返回值。
            if (WIFEXITED(status)) 
            {
                printf("Child 1 return: %d \n", WEXITSTATUS(status));
            }
            //再次调用wait函数和宏,以处理另一个子进程
            wait(&status);
            if (WIFEXITED(status))
            {
                printf("Child 2 return: %d \n", WEXITSTATUS(status));
            }

            sleep(CIRCLE); //让父进程休眠,以便验证查看
        }
    }
    return 0;
}

运行结果: 

f418a871f16d43c3aead86f2f45778f1.png

        但是请大家注意,利用wait来销毁僵尸进程时,如果没有己终止的子进程,那么程序将会一直阻塞(Blocking),直到有子进程终止才能继续下一步操作。

waitpid函数

        wait函数会引起程序阻塞,那么有没有其他方法能够解决这个问题呢?当然有,那就是wait函数的改进版——waitpid。这个方法能够避免阻塞的同时控制僵尸进程。

#include <sys/wait.h>

pid_t waitpid(pid_t pid , int * statloc , int options);

//成功时返回终止的子进程ID,失败时返回-1

/*参数说明*/

// pid: 等待终止的目标子进程的ID,若传递-1,则与wait函数相同,等待任意子进程终止
// statloc: 反应状态的变量 
// options: 传递头文件sys/wait.h中声明的常量WNOHANG(值为1),即使没有终止的子进程也不会进入阻塞状态,而是返回O并退出函数

测试用例:

waitpid.cpp

#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(10); //延迟子进程10s
        return 222;
    }
    else
    {
        while (!waitpid(-1, &status, WNOHANG)) //若之前没有终止的子进程将返回0结束循环
        {
            sleep(2);
            printf("sleep 2s");
        }

        if (WIFEXITED(status))
        {
            printf("Child return %d \n", WEXITSTATUS(status));
        }
    }
    return 0;
}

        运行结果: 

292f1d007a304c35b47c3d2a3875566b.png

        sleep(2)函数被调用了5次,共计延迟 2*5 =10 s,验证了waitpid在没有发生阻塞的同时销毁了可能出现的僵尸进程。

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/626113.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

继万物分割SAM之后,万物识别模型RAM来了!

Recognize Anything: A Strong Image Tagging Model 提出“识别万物模型”&#xff08;Recognize Anything Model&#xff0c;RAM&#xff09;&#xff0c;用于图像打标签。RAM 可以高精度地识别任何常见类别。RAM 引入了图像打标签的一个新范式&#xff0c;利用大规模的图像-文…

如何在RPC和RESTful之间做选择?

不同软件系统之间的通信可以通过RPC&#xff08;远程过程调用&#xff09;或RESTful&#xff08;表现层状态转移&#xff09;协议来建立&#xff0c;这些协议允许多个系统在分布式计算中协同工作。 这两种协议在设计哲学上有所区别。RPC使得可以像调用本地过程一样调用服务器上…

AI时代已经到来,不想被抛弃,特别是传统产业的你,怎么办?

由于ChatGTP的惊人表现&#xff0c;本来已经趋于平淡的AI&#xff0c;又火爆起来。毫无疑问&#xff0c;人类已经进入了AI时代&#xff0c;AI将渗入到各行各业&#xff0c;渗入到生活与工作的每个方面。这是一场新的工业革命&#xff0c;很多工作都将消失&#xff0c;但也会产生…

【Python】Python进阶系列教程-- Python3 MySQL - mysql-connector 驱动(三)

文章目录 前言创建数据库连接创建数据库创建数据表主键设置 插入数据批量插入查询数据where 条件语句排序Limit删除记录更新表数据删除表 前言 往期回顾&#xff1a; Python进阶系列教程-- Python3 正则表达式&#xff08;一&#xff09;Python进阶系列教程-- Python3 CGI编程…

记录--7 个沙雕又带有陷阱的 JS 面试题

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 为了保证的可读性&#xff0c;本文采用意译而非直译。 在 JS 面试中&#xff0c;经常会看到一些简单而又沙雕的题目&#xff0c;这些题目包含一些陷阱&#xff0c;但这些在我们规范的编码下或者业务中…

基于BP神经网络的轨迹跟踪研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

NLP(1):Introduction

文章目录 Why process textChallengesText processingword, sentence, document, corpus, tokenProcessing stepsSentence segmentationWord TokenizationMaxMatch AlgorithmSubword Tokenization &#xff08;BPE&#xff09;BPE 算法 Word NormalizationStop Words Remove Wh…

数据分析第17课seaborn绘图

关系型绘图 seaborn.relplot() 这个函数功能非常强大,可以用来表示多个变量之间的关联关系。默认情况下是绘制散点图(散点图是看到变量与变量之间相关性最优的一个图形),也可以绘制线性图,具体绘制什么图形是通过kind参数来决定的。实际上以下两个函数就是relplot的特例…

Vue2 事件的默认和传播行为、事件修饰符

前言 在学习vue2时&#xff0c;学到了 事件修饰符&#xff0c;但是对事件的默认行为和传播行为不太理解&#xff0c;所以也就是不知道为啥要使用事件修饰符&#xff0c;所以找了一些资料&#xff0c;在此记录一下。 Vue2官方文档 事件处理 — Vue.js (vuejs.org)https://v2.…

【 Python 全栈开发 - WEB开发篇 - 29 】MySQL初步

文章目录 一、MySQL介绍二、SQL语言三、MySQL安装与配置第一步&#xff1a;下载压缩文件第二步&#xff1a;解压第三步&#xff1a;配置第四步&#xff1a;登录 一、MySQL介绍 MySQL 是一个开源的关系型数据库管理系统&#xff0c;它使用 Structured Query Language&#xff0…

JDBC的增删改查

文章目录 前言创建数据库基础版JDBC实例添加JDBC实例删除JDBC实例修改JDBC实例查询 高级版JDBC实例添加JDBC实例删除JDBC实例修改JDBC实例查询 前言 JDBC编程步骤&#xff1a; 加载数据库驱动程序创建数据库连接对象创建Statement语句对象&#xff08;createStatement、prepa…

谈谈聚簇索引与非聚簇索引

技术主题 聚簇索引是一种数据的存储方式,它的数据行只存放在索引(B+树)的叶子上,内部节点不存放数据。 聚簇索引 聚簇索引默认是主键,如果没有定义主键,innodb会选择一个唯一的非空索引代替。如果没有这种索引,innodb会隐式定义一个主键作为聚簇索引。 非聚簇索引 非…

百度出品,Nature重磅 -- 优化的mRNA设计算法可改善mRNA的稳定性和免疫原性

摘要 尽管mRNA疫苗已用于COVID-19的预防&#xff0c;但仍然面临不稳定和易降解的风险&#xff0c;这是mRNA疫苗存储、配送、效价等面临的重要障碍。先前的研究已表明&#xff0c;增加二级结构可延长mRNA的半衰期&#xff0c;再加上选择优化的密码子&#xff0c;可改善蛋白表达。…

Django实现接口自动化平台(五)httprunner(2.x)基本使用【持续更新中】

上一章&#xff1a; Django实现接口自动化平台&#xff08;四&#xff09;解决跨域问题【持续更新中】_做测试的喵酱的博客-CSDN博客 下一章&#xff1a; 一、参考地址&#xff1a; 使用说明_httprunner2.0 概述及使用说明 二、介绍 HttpRunner是一款面向 HTTP(S) 协议的通…

一文带你了解MySQL之锁

目录 一、解决并发事务带来问题的两种基本方式1.1 一致性读&#xff08;Consistent Reads&#xff09;1.2 锁定读&#xff08;Locking Reads&#xff09;1.2.1 共享锁和独占锁1.2.2 锁定读的语句 1.3 写操作 二、多粒度锁三、MySQL中的行锁和表锁3.1 其他存储引擎中的锁3.2 Inn…

高通KMD框架详解

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、概览二、核心模块解析三、模块初始化四、处理UMD CSL请求 一、概览 利用了V4L2可扩展这一特性&#xff0c;高通在相机驱动部分实现了自有的一套KMD…

未来3年,请善待你的工作

作者| Mr.K 编辑| Emma 来源| 技术领导力(ID&#xff1a;jishulingdaoli) “如果不是现在环境差&#xff0c;下家不好找&#xff0c;我早TM跟那个傻X老板翻桌子走人了&#xff0c;这破公司我真是一天都不想待…”这是一位粉丝朋友给K哥私信中的一段话。类似同款的话&#xff…

百度推出基于大模型的代码编写助手“Comate”真的好用吗?

‍ 点击蓝字 关注我们 关注并星标 从此不迷路 计算机视觉研究院 公众号ID&#xff5c;计算机视觉研究院 学习群&#xff5c;扫码在主页获取加入方式 计算机视觉研究院专栏 Column of Computer Vision Institute 6月6日&#xff0c;在文心大模型技术交流会&#xff08;成都&…

从业务出发,K8S环境自建和非自建整体架构设计比较

新钛云服已累计为您分享751篇技术干货 随着数字化转型的大潮到来&#xff0c;越来越多的企业开始上云&#xff0c;同时也纷纷加入到微服务和K8S队伍中。但在K8S整体环境究竟应该用自建的还是非自建&#xff1f;以及他们需要用到的服务&#xff0c;究竟应该自建还是直接用PAAS服…

02.加载GDT表,进入保护模式

加载GDT表&#xff0c;进入保护模式 加载GDT表&#xff0c;实现操作系统从实模式进入保护模式 参考 操作系统学习 — 启动操作系统&#xff1a;进入保护模式 保护模式与实模式 GDT、GDTR、LDT、LDTR 调用门与特权级 趣谈 Linux 操作系统 在01.硬盘启动盘&#xff0c;加载操作系…