进程控制之进程等待

本篇目标:

学习进程等待并了解几个概念与函数

一.进程等待

1.进程等待必要性

之前讲过,子进程退出,⽗进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。

• 另外,进程⼀旦变成僵尸状态,那就刀枪不入,杀⼈不眨眼的kill-9也无能为力,因为谁也没有办法杀死一个已经死去的进程。

• 最后,父进程派给子进程的任务完成的如何,我们需要知道。如果⼦进程运⾏完成,结果对还是 不对,或者是否正常退出。

• 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

例如:

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
     pid_t pid;
    if ((pid = fork()) == 0)                                         
    {                                                                
         // 子进程执行逻辑                                           
         printf("子进程,PID: %d,PPID:%d, 开始运行\n", getpid(),getppid    ());                                                                
         sleep(2);                                                   
         printf("子进程,PID: %d,PPID:%d, 运行结束\n", getpid(),getppid    ());                                                                 
         //子进程退出                                       
         exit(0);                                     
    }                                                                                                                                
    printf("父进程,PID: %d 开始等待子进程\n", getpid());                                                     
    return 0;                                        
 }

此时子进程的代码与数据均已经销毁了,但是子进程的退出信息父进程却没有去拿,就导致子进程一直处于僵尸进程

2.进程等待的方法

2.1.wait方法

如图:

我们先不看waitpid,先看wait,status其实是个输出型参数,但是下面讲waitpid时,在讲他,等下我们设置为NULL,而返回值则是成功返回被等待进程 pid ,失败返回-1 ,可以等待任意个退出的子进程。

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(0);
    }
    pid_t rid=wait(NULL);
    if(rid>0)
    {
        printf("我是一个父进程,pid:%d\n",getpid());
        printf("等待成功,rid:%d\n",rid);
    }
    return 0;
}

输出结果:

可以看出返回值确实是子进程的pid,现在有一个问题:如果子进程没有退出,父进程却wait呢?

其实结果与这个差不多

cpp 复制代码
// 子进程代码
while (1) 
{
    printf("子进程还在死循环中...\n");
    sleep(1);
}
// 永远跑不到这里的 exit(0);

此时父进程如果调用 wait(NULL),会发生什么?

父进程执行到 wait(NULL),发现子进程还在跑 while(1),根本没退出。

父进程直接暂停执行(阻塞),停在 wait(NULL) 这一行,不再往下走。

子进程依然在欢快地跑死循环,打印日志。

父进程就这么一直卡着,永远等不到头,除非你手动杀掉子进程(比如用 kill 命令)。

2.2.waitpid方法

如图所示:

<1>.先关注它的第一个参数,如图所示:

重点关注-1与>0,其实-1表示等待任意的子进程,而>0则表示等待id的那个子进程,下面演示一下:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(0);
    }
    pid_t rid=waitpid(id,NULL,0);
    if(rid>0)
    {
        printf("我是一个父进程,pid:%d\n",getpid());
        printf("等待成功,rid:%d\n",rid);
    }
    return 0;
}

输出结果:

目前与wait的差不多

<2>.其次是第二个参数,,该参数是⼀个输出型参数,负责由操作系统填充,而父进程通过status拿到子进程退出时的详细信息,也包括退出码,演示一下:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(1);
    }
    int status=0;
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        printf("我是一个父进程,pid:%d,status:%d\n",getpid(),status);
        printf("等待成功,rid:%d\n",rid);
    }
    return 0;
}

输出结果:

此时可能就会有人疑惑:为什么status不是退出码1呢?这256是如何来的呢?

其实status的真正的二进制应该是这样的:

前16位不用管,全为0,后16位中的前8位才是退出码,后8位,如果退出正常的话则为0,所以我们的退出码的二进制在后16位中的前8位为00000001,而后面又全为0,就导致结果为256,想要获取退出码也很简单,如图所示即可:

cpp 复制代码
printf("我是一个父进程,pid:%d,status:%d\n",getpid(),(status>>8)&0xFF);

输出结果:

<3>.第三个参数options:默认为 0 ,表示阻塞等待,这里用个例子来加深我们对阻塞等待的理解:

期末小王找小李补高数,拨通电话后,小李说自己正在给别人补英语,要半小时才能收尾。

小王选择不挂电话,也不做看书、刷手机这些事,就举着电话一动不动干等------ 这就是阻塞等待的核心。

这半小时里,小李正常补他的课(子进程正常运行,没到退出节点),小王就一直卡在等待状态,啥后续的事都做不了。

直到小李补完课、彻底腾出身(子进程执行exit/return 0退出),小王才被唤醒,开始请教问题。

如果小李永远忙不完(子进程死循环、永不退出),小王就会永远卡在这干等,彻底卡死。

其实option除了0,还有其他的,今天讲一个有关waitpid的,如图所示:

WNOHANG其实就是非阻塞等待,依旧举一个例子:

期末快到了,小王数学很差,想找学霸小李帮忙补习功课,他知道小李现在正在给别的同学讲题,还没结束(子进程没 exit /return)

小王这次不想一直拿着电话死等,他选择非阻塞等待 ,也就是带上**WNOHANG**。

  1. 小王先打了一个电话给小李,开口就问:"你讲完了吗?我想找你补习。"小李说:"还没呢,我还在给别人讲,至少还要半小时。"小王听完,没有握着电话一直等 ,直接说:"那你先忙,我等会儿再找你。" 然后就挂了电话。这就是 WNOHANG 的行为:问一次,没好就立刻走,不阻塞、不卡住

  2. 挂掉电话后,小王没有闲着,他回到座位上自己翻课本、看错题、做练习题,一边忙自己的事,一边等小李有空。父进程没有被卡住,而是继续执行自己的代码。

  3. 过了十几分钟,小王停下笔,再打一次电话问小李:"现在讲完了吗?"小李还是说:"还没,快了,再等会儿。"小王依旧不等待,直接挂电话,继续低头做自己的题目,完全不耽误时间。

  4. 又过了一会儿,小王第三次打电话过去,这次小李说:"终于讲完了,现在可以帮你补习了。"这时候小王才开始和小李聊补习的内容,相当于父进程检测到子进程退出,完成回收。

整个过程里,小王从来没有一直拿着电话死等 ,每次只问一下,没好就立刻去干自己的事,隔一段时间再来问一次。这就是 waitpid(..., WNOHANG) 的非阻塞等待:子进程没退出 → 立刻返回,父进程继续运行;子进程退出了 → 再处理回收。

有了例子后,再谈谈返回值,此时的pid_t id=waitpid(id,&status,WNOHAGN);如果id>0则等待结束;id==0,调用结束,但是子进程没有结束;id<0,则失效了,下面以代码演示:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(1);
    }
    while(1)
    {
        int status=0;
        pid_t rid=waitpid(id,&status,WNOHANG);
        if(rid>0)
        {
            printf("我是一个父进程,pid:%d,status:%d\n",getpid(),(status>>8)&0xFF);
            printf("等待成功,rid:%d\n",rid);
            break;
        }
        else if(rid==0)
        {
            printf("本轮调用结束,但是子进程没有退出\n");
            sleep(1);
        }
        else
        {
            printf("等待失效\n");
            break;
        }
    }
    return 0;
}

输出结果:

可以看出父进程一直在询问子进程是否结束,直到子进程exit

其实非阻塞等待的一个重要作用就是让等待方可以做自己的事情,如代码所示:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

// 1. 数学计算函数:计算1~n的累加和
int calculate_sum(int n) {
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += i;
    }
    return sum;
}

// 2. 轮询计数函数:统计父进程检查子进程的次数(静态变量持久化)
void check_counter() {
    static int count = 0;
    count++;
}

// 3. 父进程核心工作函数:整合所有任务
void do_parent_business() {
    printf("结果为:%d\n",calculate_sum(100));   
    check_counter();       
}

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(1);
    }

    // 父进程:非阻塞等待 + 执行自定义函数
    while(1)
    {
        int status=0;
        pid_t rid=waitpid(id,&status,WNOHANG);

        if(rid>0)
        {
            printf("父进程[%d]: 回收子进程成功,退出码:%d\n",getpid(),(status>>8)&0xFF);
            break;
        }
        else if(rid==0)
        {
            do_parent_business();
            sleep(1);
        }
        else
        {
            printf("等待失效\n");
            break;
        }
    }
    return 0;
}

输出结果:

3.补充内容

<1>.WEXITSTATUS(status): 若 WIFEXITED 非零,提取子进程退出码。(查看进程的退出码)

如代码所示:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(1);
    }
    int status=0;
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        printf("我是一个父进程,pid:%d,status:%d\n",getpid(),WEXITSTATUS(status));
        printf("等待成功,rid:%d\n",rid);
    }
    return 0;
}

<2>.WIFEXITED(status): 若为正常终止,子进程返回的状态,则为真(查看进程 是否是正常退出)

如代码所示:

cpp 复制代码
#include<stdio.h>
#include<errno.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("我是一个子进程,pid:%d,ppid:%d\n",getpid(),getppid());
            sleep(1);
            cnt--;
        }
        //子进程退出
        exit(1);
    }
    int status=0;
    pid_t rid=waitpid(id,&status,0);
    if(rid>0)
    {
        if(WIFEXITED(status))
        {
            printf("我是一个父进程,pid:%d,status:%d\n",getpid(),WEXITSTATUS(status));
            printf("等待成功,rid:%d\n",rid);
        }
        else
            printf("退出异常\n");
    }
    return 0;
}

下一篇:进程控制之进程替换

相关推荐
宋浮檀s4 分钟前
应急响应——Web漏洞:命令执行+SSRF+弱口令
运维·数据库·sql·网络安全·oracle·应急响应
日取其半万世不竭12 分钟前
iftop、nethogs 和 nload:Linux 服务器网络流量实时监控工具介绍
linux·运维·服务器
mounter62524 分钟前
Linux 内核资源管理:控制组(cgroup)的演进与“策略组”新提案
linux·运维·服务器·cgroup·kernel
bksczm26 分钟前
文件在磁盘中的存储方式
linux·运维·服务器
L16247627 分钟前
OpenSSH 半自动升级方案(独立编译 + 手动迁移 + 重建 systemd 服务)
linux·服务器·ssh
半旧夜夏32 分钟前
【保姆级】微服务组件环境搭建(Docker Compose版)
java·linux·spring cloud·微服务·云原生·容器
Wpa.wk44 分钟前
win环境本地文件上传远程服务器(scp/远程连接工具)
运维·服务器
Soari1 小时前
SSH 主机密钥冲突
运维·网络·ssh
爱莉希雅&&&2 小时前
zabbix快速搭建和使用
android·linux·数据库·zabbix·监控
z200509302 小时前
【linux学习】深入理解linux文件I/O,从C标准库到内核态
linux·学习·操作系统