Linux socket编程(4):服务端fork之僵尸进程的处理

在上一节利用fork实现服务端与多个客户端建立连接中,我们使用fork函数来实现服务端既可以accept新的客户端连接请求,又可以接收已连接上的客户端发来的消息。但在Linux中,在子进程终止后,父进程需要处理该子进程的终止状,否则子进程将成为僵尸进程,本节就来探讨一下僵尸进程的处理。

文章目录

  • 1 什么是僵尸进程
  • 2 回收僵尸进程
    • 2.1 SIG_IGN忽略
    • 2.2 wait和waitpid
      • 2.2.1 wait
      • 2.2.1 waitpid

1 什么是僵尸进程

僵尸进程(Zombie Process)是操作系统中的一种特殊进程状态,它通常出现在一个子进程终止,但其父进程尚未能够处理该子进程的终止状态。

  1. 特点

    僵尸进程不执行任何代码,它们仅仅是一个进程描述符和一些状态信息,如退出状态码,占用少量系统资源。如果大量的僵尸进程积累,可能会导致系统资源耗尽。

  2. 解决方法

    • 当子进程终止时,父进程可以使用wait()waitpid()等系统调用来等待子进程的退出状态信息,从而释放子进程的资源,同时告知操作系统可以回收子进程的进程表项
    • 另一种方法是使用信号处理程序,在父进程中注册SIGCHLD信号处理程序来处理子进程的退出状态

在上一篇文章的例子中,如果在客户端的进程终止后,服务端没有回收子进程的话,将产生一个僵尸进程。

在这里插入图片描述

我们可以使用top指令来看系统中现在有多少个僵尸进程:

在这里插入图片描述

我们还可以使用ps -aux |grep Z来查看具体的僵尸进程的信息:

在这里插入图片描述

图中STAT(状态)为Z+(Zombie)的即为僵尸进程。

2 回收僵尸进程

2.1 SIG_IGN忽略

最简单的,我们可以使用SIG_IGN来忽略SIGCHLD信号,这样内核会在子进程终止时立即将其资源释放,而不需要父进程调用waitwaitpid来获取子进程的终止状态以释放资源。

在这里插入图片描述

可以看到此时是没有产生僵尸进程的:

在这里插入图片描述

2.2 wait和waitpid

使用signal(SIGCHLD, SIG_IGN);的方式处理僵尸进程有一些局限性和潜在的问题:

  1. 父进程无法得知子进程是正常退出还是异常终止,以及子进程的退出状态是什么。
  2. 父进程无法正确处理每个子进程的终止状态。

如果需要掌握子进程退出的情况,建议注册信号回调函数,然后使用waitwaitpid来处理僵尸进程。

2.2.1 wait

如下图所示,可以使用wait函数来回收子进程的资源。

在这里插入图片描述

运行程序,创建一个客户端然后关闭,再创建一个客户端然后再关闭,结果如下:

在这里插入图片描述

可以看到服务端正常地回收了资源,此时使用top查看也是没有僵尸进程的。

但是在多个客户端同时关闭的情况下,wait会产生问题

我们现在对客户端的代码做出如下修改:

在这里插入图片描述

现在来看一下这10个套接字同时退出后会发生什么:

在这里插入图片描述

可以看到我们注册的SIGCHLD回调函数只被触发了4次,也就是说只有4个子进程的资源被回收了。此时用top查看僵尸进程的数量,果然还有6个:

在这里插入图片描述

实际上也好理解,这些套接字在非常短的时间间隔内同时关闭,对于Linux的内核来看,应该是有一个进程专门用来处理这些信号,在上一个信号还在处理的同时又来了多个信号,那么下次OS只会响应一个信号,而不会调用多次回调函数,然后调用一次wait就回收一个子进程。

所以如果我们多次测试,可以发现每次被回收的进程的数量都是不同的,这和OS内部的任务调度有关,但基本上不可能10个全部回收。

那我们是否可以在sigchld_handler中调用while循环无限地wait来解决这个问题呢?

答案是否定的。因为 wait 是一个阻塞调用,会导致信号处理函数阻塞,而信号处理函数的处理应该尽量迅速。

2.2.1 waitpid

这时我们就可以使用waitpid函数:

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

其中第三个参数options的常用值如下(可以使用按位或运算符|组合多个选项):

  • WNOHANG:在没有终止的子进程时立即返回,不阻塞。如果指定了这个选项,waitpid 将立即返回,不会等待子进程终止。
  • WUNTRACED:也等待已停止的子进程的状态。
  • WCONTINUED:也等待被停止的子进程被继续的状态。

所以我们只要在信号处理回调函数中使用waitpid(-1, &status, WNOHANG)即可避免前面回收资源不完全的情况。

void sigchld_handler(int signo) {pid_t pid;int status;// 在信号处理函数中循环调用waitpid以获取所有子进程的终止状态,其中-1表示等待任意子进程while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {printf("Child process %d exited with status %d\n", pid, WEXITSTATUS(status));}
}

结果如下:

在这里插入图片描述
这样就回收了所有的僵尸进程的资源。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

程序员告诉你:人工智能是什么?

随着科技的快速发展,人工智能这个词汇已经逐渐融入了我们的日常生活。然而,对于大多数人来说,人工智能仍然是一个相对模糊的概念。 首先,让我们从人工智能的定义开始。人工智能是一种模拟人类智能的技术,它涵盖了多个领…

【Linux】冯诺依曼体系结构、操作系统、进程概念、进程状态、环境变量、进程地址空间

目录 一、冯诺依曼体系结构二、操作系统(OS)1. 操作系统是什么2. 操作系统如何做管理3. 系统调用和库函数概念 三、进程1. 进程是什么?2. 描述进程-PCB3. 查看进程的方法 四、进程状态1 运行、阻塞和挂起状态2 Linux中的进程状态 五、进程优先级1. 什么是优先级2.查…

庖丁解牛:NIO核心概念与机制详解 04 _ 分散和聚集

文章目录 Pre概述分散/聚集 I/O分散/聚集的应用聚集写入Code Pre 庖丁解牛:NIO核心概念与机制详解 01 庖丁解牛:NIO核心概念与机制详解 02 _ 缓冲区的细节实现 庖丁解牛:NIO核心概念与机制详解 03 _ 缓冲区分配、包装和分片 概述 分散/聚…

STM32CubeMX学习笔记-CAN接口使用

STM32CubeMX学习笔记-CAN接口使用 CAN总线传输协议1.CAN 总线传输特点2.位时序和波特率3.帧的种类4.标准格式数据帧和遥控帧从STM32F407参考手册中可以看出主要特性如下CAN模块基本控制函数CAN模块消息发送CAN模块消息接收标识符筛选发送中断的事件源和回调函数 CubeMX项目设置…

读像火箭科学家一样思考笔记03_第一性原理(上)

1. 思维的两种障碍 1.1. 为什么知识会成为一种缺陷而非一种美德 1.1.1. 知识是一种美德 1.1.2. 知识同样的特质也会把它变成一种缺点 1.1.3. 知识确实是个好东西,但知识的作用应该是给人们提供信息,而不是起约束作用 1.1.4. 知识应该启发智慧&#…

程序员带你入门人工智能

随着人工智能技术的飞速发展,越来越多的程序员开始关注并学习人工智能。作为程序员,我们可能会对如何开始了解人工智能感到困惑。今天,我将向大家介绍一些如何通过自学了解人工智能的经验和方法,帮助大家更好地入门这个充满挑战和…

学霸教你自学人工智能

在这个信息爆炸的时代,人工智能已经渗透到我们生活的方方面面。无论是语音助手、自动驾驶汽车,还是医疗诊断,人工智能都在发挥着越来越重要的作用。如果你对人工智能充满热情,希望在这个领域有所建树,那么,…

如何看待人工智能行业发展

随着人工智能技术的飞速发展,这个领域的就业前景也日益广阔。人工智能在各行各业都有广泛的应用,包括医疗、金融、制造业、教育等。因此,对于想要追求高薪、高技能职业的人来说,学习人工智能是一个非常有前景的选择。 首先&#x…

C++二分查找算法:有序矩阵中的第 k 个最小数组和

本文涉及的基础知识点 二分查找算法合集 本题的简化 C二分查找算法:查找和最小的 K 对数字 十分接近m恒等于2 题目 给你一个 m * n 的矩阵 mat,以及一个整数 k ,矩阵中的每一行都以非递减的顺序排列。 你可以从每一行中选出 1 个元素形成…

多线程概述

文章目录 线程是什么线程有什么作用线程和进程的区别多线程相较于进程优势 在Java这个圈子中,多进程用的并不多,因为进程是一个重量级操作,进程是资源分配的基本单位,申请资源是一个比较消耗时间的操作. 线程是什么 线程是一个独立的执行流,可以被独立调度到CPU上执行 线程是…

Bean实例化的基本流程

Spring容器在进行初始化时&#xff0c;会将xml配置的<bean>的信息封装成一个BeanDefintion对象&#xff0c;所有的BeanDefintion存储到BeanDefintionMap的Map集合中去&#xff0c;Spring框架对该Map进行遍历&#xff0c;使用反射创建Bean实例对象&#xff0c;创建好的Bea…

Dockerfile自定义镜像以及案例分析

文章目录 一、Dockerfile自定义镜像1.1 镜像结构1.2 Dockerfile语法 二、构建Java项目三、基于java8构建java四、小结 一、Dockerfile自定义镜像 常见的镜像在DockerHub就能找到&#xff0c;但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像&#xff0c;就必须先了…