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

目录

僵尸(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在没有发生阻塞的同时销毁了可能出现的僵尸进程。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

干吃咖啡豆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值