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

news/2024/5/18 15:59:36 标签: 服务器, tcp/ip, 网络, 网络协议, udp

目录

僵尸(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.niftyadmin.cn/n/438964.html

相关文章

乐鑫 Thread 边界路由器方案通过 Thread 1.3 认证,开发板正式上架!

乐鑫科技 Thread 边界路由器 (Thread Border Router) 解决方案获得由 Thread Group 颁发的 Thread Certified Component 证书&#xff0c;符合最新的 Thread 1.3 标准&#xff0c;并支持 Matter 应用场景。 该方案由乐鑫的 Wi-Fi SoC (ESP32, ESP32-C, ESP32-S) 和 IEEE 802.1…

Vivado远程开发探索

平时主要用轻薄本办公&#xff0c;但是有时候又需要用Vivado做一些开发的工作&#xff0c;就感觉生产力不够。如果能在远程的高性能服务器上跑Vivado综合实现就好了。前段时间用ubuntu下安装的Vivado发现有一个Remote Host的设置。所以就准备折腾一下这个。 WSL WSL的安装看官…

【STM32F1】以太网通信之UDP/TCP实验

在本实验中&#xff0c;开发板主控芯片通过 SPI 接口与 CH395Q 以太网芯片进行通讯&#xff0c;从而完成对 CH395Q 以太网芯片的功能配置、数据接收等功能&#xff0c;同时将 CH395Q 以太网芯片的 Socket0 配 置为 UDP 模式&#xff0c;并可通过按键发送 UDP 广播数据至其他的 …

VUE项目无法启动NODE版本与NODE-SASS、SASS-LOADER版本不兼容

系列文章目录 文章目录 系列文章目录错误分析一、版本比对二、解决方案总结 错误分析 在VUE项目开发中&#xff0c;我们经常会遇到报错&#xff1a; Node Sass version 7.0.1 is incompatible with ^4.0.0。 网上解决方案也千奇百怪&#xff0c;最终操作下来&#xff0c;也是…

【已解决】“X-Content-Type-Options”头缺失或不安全

Appscan是一款安全漏洞扫描软件&#xff0c;由IBM公司研发&#xff0c;后又被卖给了印度公司HCL。 在web安全测试中&#xff0c;今天我们说下扫描结果中包含X-Content-Type-Options请求头header的缺失或不安全的时候&#xff0c;我们该如何应对。 风险&#xff1a;可能会收集…

Java实现TestNg+ExtentReport实现接口测试,并生成测试报告

一 在pom.xml文件中引入TestNg以及ExtentReport包 <dependencies> <!--testNg引入--> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.9.10</version> </de…

Python学习49:词频统计

类型&#xff1a;文件‪‬‪‬‪‬‪‬‪‬‮‬‪‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‪‬‭‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‮‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬‪‬‪‬‪‬‪‬‪‬‮‬‭‬‫‬‪‬‪‬‪‬‪‬‪‬‮‬‫‬‪‬ 描述‪‬‪‬…

dockerfile和dockercompose都有expose,听谁的?

Dockerfile 的 EXPOSE 指令和 docker-compose 的 expose 配置选项实际上具有不同的含义和目的。 在 Dockerfile 中&#xff0c;EXPOSE 指令是为了声明在容器运行时会使用的网络端口。这对于在运行或使用容器时提供了关于其网络配置的信息。然而&#xff0c;它并不会实际打开这…