[Linux 进程控制(一)] 进程等待

news2025/2/26 8:05:40

在这里插入图片描述

文章目录

  • 1、进程等待
    • 1.1 为什么要进程等待
    • 1.2 进程等待必要性
    • 1.3 进程等待的方法
      • 1.3.1 wait方法
      • 1.3.2 waitpid方法
    • 1.4 获取子进程的status
    • 1.5 waitpid的第三个参数options

1、进程等待

1.1 为什么要进程等待

  • 解决子进程僵尸问题带来的内存泄漏问题。
  • 子进程将父进程交给的任务完成的如何,父进程需要通过进程等待的方式,获取子进程退出的信息。本质是获取 退出码和信号编号。

1.2 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏.
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

1.3 进程等待的方法

1.3.1 wait方法

我们首先查看一个wait接口,并简单介绍一下:
在这里插入图片描述
wait这里先不说参数,下面waitpid会谈,可以传NULL。
在这里插入图片描述
我们了解了用法,接下来就开始写代码试试:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        sleep(6); // 不会让父进程立即回收子进程
        // father
        pid_t rid = wait(NULL);
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述

以上的测试验证了,进程等待是可以回收僵尸进程的!

我们再来看,如果我们不让父进程回收前休眠,直接就是回收子进程会怎么样,测试一下:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        //sleep(6); // 不会让父进程立即回收子进程
        // father
        // wait before和wait after连着打出来那么就说明直接没有在等待时卡住
        printf("wait before\n");
        pid_t rid = wait(NULL);
        printf("wait after\n"); 
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
我们发现,子进程先跑完才打印的wait after,并且这次子进程没有显示出僵尸状态(变成僵尸立即被回收),这说明:如果子进程没有退出,父进程必须在wait上进行阻塞等待,直到子进程僵尸,wait自动回收,返回!
一般而言,谁先运行不知道,但是父进程一般都是最后退出(wait阻塞等待)!

1.3.2 waitpid方法

还是先了解一下接口再说:
在这里插入图片描述
我们继续写代码试试:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(0);
    }
    else 
    {
        sleep(6); // 不会让父进程立即回收子进程
        // father
        printf("wait before\n");
        pid_t rid = waitpid(id, NULL, 0); // 等待子进程
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d\n", getpid());
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
这里可以看到子进程僵尸状态一下就别回收了。
我们这里讲一下waitpid的第二个参数:它是一个整型指针变量,做输出型参数,os将数值赋值给status。它是将子进程的退出码写入然后带回来。
我们继续写代码测试:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, status: %d\n", getpid(), status);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
我们子进程退出码为10,但是父进程接收后打印出来不是10,而是2560!怎么回事?我们看下一个小节,里面细讲!!!

1.4 获取子进程的status

status是一个int的整数,32位,但是status只取低16位。
在这里插入图片描述

今天先不谈core dump标志。我们刚给子进程的退出码设置的是10,由上面的图可以看出,status的低16位的次低8位代表了退出状态,因此10的二进制表示在次低8位,10二进制序列为1010,所以16位的表示为0000 1010 0000 0000。
在这里插入图片描述
因此,我们知道了,status不能直接使用,得我们对二进制序列经过位运算后才能得出正确的结果。 下面我们就从三个测试入手:代码跑完结果对,代码跑完结果不对,异常。
代码跑完结果对:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, rid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status&0x7F, (status>>8)&0xFF);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
这里我们退出码设置的10,所以是我们的预料之内。
所以,信号为0,退出码为0,代表代码跑完结果正常。
异常:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }

    int a = 10;
    a /= 0;
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(10);
    }
    else 
    {
        // father
        printf("wait before\n");
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            printf("wait success, pid: %d, rid: %d, exit sig: %d, exit code: %d\n", getpid(), rid, status&0x7F, (status>>8)&0xFF);
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
我们故意写了一个除零错误,信号就是8,信号8为SIGFPE。
在这里插入图片描述
但是退出状态为0,这里 信号为非0,退出码为0,代表异常!
所以, 代码跑完结果不对的表现就是,信号为0,退出码为非0!
父进程如何得知子进程的退出信息呢?原理是什么?
我们来看下面的图:
在这里插入图片描述
还有一个 问题:父进程在等待子进程的时候是阻塞状态,那么它是在哪里阻塞呢?
PCB中也维护了等待队列,当父进程等待子进程的时候,父进程的PCB会被链入到子进程的等待队列中,等子进程结束了,父进程的PCB就被链入到运行队列中,等待调度,被调度后立马回收子进程的PCB。这就叫做 软件条件。
大家记住,进程只要是阻塞了,就会被链入到等待队列中。
上面我们拿到退出码和信号是我们自己手动使用位操作实现的,那么下面我们来介绍一下Linux下封装好的系统调用,以后我们直接调用即可。
在这里插入图片描述
我们继续写代码测试一下:

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

void Worker()
{
    int cnt = 5;
    while(cnt)
    {
        printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);
        sleep(1);
    }
}

int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        Worker();
        exit(1);
    }
    else 
    {
        //sleep(6); // 不会让父进程立即回收子进程
        // father
        printf("wait before\n");
        //pid_t rid = wait(NULL);
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        printf("wait after\n");
        if(rid == id)
        {
            if(WIFEXITED(status))
            {
                printf("child process normal quit, exit code: %d\n", WEXITSTATUS(status));
            }
            else 
            {
                printf("child process quit except!\n");
            }
        }
        sleep(2); // 最后只剩父进程跑
    }
    return 0;
}

在这里插入图片描述
这里应该有人会疑惑,获取子进程的status这么麻烦,为什么不定义个全局变量直接拿呢?
如果定义一个全局变量,子进程势必需要对变量在自己的进程中写入,但是父子进程是两个进程,进程具有独立性,子进程对数据的更改,父进程是看不到的!!!

1.5 waitpid的第三个参数options

在这里插入图片描述
我们先来说waitpid的返回值,返回值分三类:

  • 0<:等待成功;
  • == 0:等待是成功的,但是进程还没有退出;
  • <0:等待失败。
    我们之前说options直接给0就可以,这里说一下,**0代表的是阻塞等待(子进程不退出,wait不返回)!**下面再说其他的:
    WNOHANG:等待的时候,以非阻塞(等待条件不满足,不阻塞,直接返回)的方式等待!非阻塞等待,往往要进行重复调用(轮询)。可以顺便做一些占据时间并不多的事情!非阻塞等待可以与返回值并用,实现轮询。
    这里我们来试一下非阻塞等待,并使用轮询的方式等待:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void Worker(int cnt)
{
    printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
    sleep(1);
}
// waitpid第三个参数测试的单个子进程
int main()
{
    pid_t id = fork();
    if(0 == id)
    {
        // child
        int cnt = 3;
        while(cnt)
        {
            Worker(cnt);
            cnt--;
        }
        exit(0);
    }

    // father
    while(1)
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, WNOHANG);
        if(rid > 0) // waitid返回值大于0,等待成功
        {
            printf("child quit success, exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);
            break;
        }
        else if(rid == 0) // 返回值等于0,等待成功,但是子进程没有退出,可以做占用时间少的事
        {
            printf("father do other thing...\n");
            sleep(1);
        }
        else // 等待失败 
        {
            printf("wait failed!\n");
            break;
        }
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述
我们能看到父进程最后退出。这就是非阻塞轮询等待。这样的方式等待,父进程就可以去做一些自己的事情,这样就可以更好的将时间利用起来!
我们来写一个这样的场景:
这里我们定义一个函数指针,里面存放一些函数指针,使用回调方式将代码封装一下:

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

#define TASK_NUM 5

typedef void (*task_t)();

/ 
void download()
{
    printf("this is a download task is runing!\n");
}
void printLog()
{
    printf("this is a write log task is runing!\n");
}
void show()
{
    printf("this is a show info task is runing!\n");
}
/ 


void initTasks(task_t tasks[], int num)
{
    for(int i = 0; i < num; i++) tasks[i]= NULL;
}

int addTask(task_t tasks[], task_t t)
{
    int i = 0;
    for(; i < TASK_NUM; i++) // 循环+判断将函数指针依次放入函数指针数组中
    {
        if(tasks[i] == NULL)
        {
            tasks[i] = t;
            return 1;
        }
    }
    return 0;
}

void executeTask(task_t tasks[], int num)
{
    for(int i = 0; i < num; i++)
    {
        if(tasks[i]) tasks[i]();
    }   
}

void worker(int cnt)
{
    printf("I am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
}
// waitpid第三个参数测试的单个子进程
int main()
{
    task_t tasks[TASK_NUM]; // 函数指针数组
    initTasks(tasks, TASK_NUM);
    addTask(tasks, download);
    addTask(tasks, printLog);
    addTask(tasks, show);

    pid_t id = fork();
    if(0 == id)
    {
        // child
        int cnt = 3;
        while(cnt)
        {
            worker(cnt);
            sleep(1);
            cnt--;
        }
        exit(0);
    }

    // father
    while(1)
    {
        int status = 0;
        // 非阻塞等待,可以让等待方在等待的时候,顺便做做自己的事情
        pid_t rid = waitpid(id, &status, WNOHANG);
        if(rid > 0)
        {// waitid返回值大于0,等待成功
            printf("child quit success, exit code: %d, exit signal: %d\n", (status>>8)&0xFF, status&0x7F);
            break;
        }
        else if(rid == 0)
        {// 返回值等于0,等待成功,但是子进程没有退出,可以做自己的事
            printf("####################################\n"); // 分隔符
            printf("father do other thing...\n");
            // 该函数内部,其实是函数回调方式
            executeTask(tasks, TASK_NUM);
            printf("####################################\n");
            sleep(1);
        }
        else 
        {// 等待失败
            printf("wait failed!\n");
            break;
        }
    }
    return 0;
}

在这里插入图片描述
虽然是打印的方式说明在执行其他任务,但是这个原理用这简单的方式讲通了。在子进程没有退出时,父进程可以去做一些自己的任务!

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

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

相关文章

【上海大学计算机组成原理实验报告】三、微指令系统实验

一、实验目的 了解译码器、微指令结构的基本工作原理。学习设计微指令的方法。 二、实验原理 根据实验指导书的相关内容&#xff0c;本实验所用的实验箱的微指令系统控制总线宽度为24位&#xff0c;每个地址单元宽度也为24位&#xff0c;其中微指令存储器由3片8位存储器按照…

如何在SFTP工具中使用固定公网地址远程访问内网Termux系统

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP&#xff08;SSH File Transfer Protocol&#xff09;是一种基于SSH&#xff08;Secure Shell&#xff09;安全协议的文件传输协议。与FTP协议相比&#xff0c;SFTP使用了…

期货开户市场是没硝烟的战场

另外&#xff0c;期货市场是没有硝烟的战场&#xff0c;《孙子兵法》、《吴子兵法》、《战略论》等兵法你最好也找来读一读。《金刚经》、《坛经》、《心经》、《道德经》等你最好也认真地学一学。我认为&#xff0c;从来没有哪个领域像金融交易领域这样充斥这么多的荒谬理论&a…

Python的基础知识学习路线2—运算符与变量类型(使用jupyter notebook进行操作:最全路线,每部分附有代码操作结果)

一、更改jupyter notebook 打开文件的位置 1、打开Anaconda Prompt终端&#xff0c;输入以下命令&#xff0c;创建配置文件&#xff1a;jupyter_notebook_config.py jupyter notebook --generate-config2、打开生成的配置文件 3、编辑配置文件jupyter_notebook_config.py&…

共享IP和独享IP如何选择,两者有何区别?

有跨境用户在选择共享IP和独享IP时会有疑问&#xff0c;不知道该如何进行选择&#xff0c;共享IP和独享IP各有其特点和应用场景&#xff0c;选择哪种方式主要取决于具体需求和预算。以下是对两者的详细比较&#xff1a; 首先两者的主要区别在于使用方式和安全性&#xff1a;共…

蓝桥杯2023年第十四届省赛真题-冶炼金属

题目描述 小蓝有一个神奇的炉子用于将普通金属 O 冶炼成为一种特殊金属 X。这个炉子有一个称作转换率的属性 V&#xff0c;V 是一个正整数&#xff0c;这意味着消耗 V 个普通金 属 O 恰好可以冶炼出一个特殊金属 X&#xff0c;当普通金属 O 的数目不足 V 时&#xff0c;无法继…

【C语言】C语言题库【附源码+持续更新】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 目录 1、练习2-1 Programming in C is fun! 2、练习2-3 输出倒三角图案 3、练习2-4 温度转换 4、练习2-6 计算物体自由下落的距离 5、练习2-8 计算摄氏温度 6、练习2-9 整数四则运算 7、练习2-10 计算分段函数[1…

深度学习500问——Chapter07:生成对抗网络(GAN)(1)

文章目录 7.1 GAN基本概念 7.1.1 如何通俗理解GAN 7.1.2 GAN的形式化表示 7.1.3 GAN的目标函数是什么 7.1.4 GAN的目标函数和交叉熵有什么区别 7.1.5 GAN的Loss为什么降不下去 7.1.6 生成式模型、判别式模型的区别 7.1.7 什么是mode collapsing 7.1.8 如何解决mode collapsing …

记一次小郭被挖矿后的应急响应

谨以此篇纪念我第n1次被挖矿经历。 时间&#xff1a;2024年3月18日&#xff08;星期一&#xff09; 地点&#xff1a;阿里云服务器 响应&#xff1a;确认–>抑制–>消除–>恢复–>总结 确认阶段&#xff1a; 2024年3月18日星期一早上收到了阿里云的短信和邮件…

城市道路井盖破损丢失目标检测数据集VOC-1377张

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;1377 标注数量(xml文件个数)&#xff1a;1377 标注类别数&#xff1a;4 标注类别名称:["jg","jg…

2006-2021年各省能源消费总量数据(无缺失)

2006-2021年各省能源消费总量数据&#xff08;无缺失&#xff09; 1、时间&#xff1a;2006-2021年 2、来源&#xff1a;能源年鉴、各省年鉴 3、范围&#xff1a;30个省 4、指标&#xff1a;能源消费总量&#xff08;万吨标煤&#xff09; 5、缺失情况&#xff1a;无缺失 …

安卓Termux安装openssh结合内网穿透实现公网使用SFTP传输文件

文章目录 1. 安装openSSH2. 安装cpolar3. 远程SFTP连接配置4. 远程SFTP访问4. 配置固定远程连接地址 SFTP&#xff08;SSH File Transfer Protocol&#xff09;是一种基于SSH&#xff08;Secure Shell&#xff09;安全协议的文件传输协议。与FTP协议相比&#xff0c;SFTP使用了…

将网址生成快捷方式的两种方式

在日常开发中&#xff0c;总会出现一些很奇怪的需求 1、生成桌面端快捷方式 1、点击右键&#xff0c;生成左面快捷方式。 2、可以将网址填入对象中。如&#xff1a;www.baidu.com。 3、修改快捷文件的图标。注意必须是原生的.icon文件。 4、注意事项。将小图标只能在本地可见&…

全球数字贸易产业联盟分享18个抓单秘诀让你业绩暴涨 | 箱讯科技

1、你就是企业 即使你所在的公司有庞杂的分支机构和几千名职工&#xff0c;但对于顾客来讲&#xff0c;公司就是你&#xff0c;同他直接接触的是你。顾客把你的公司看作一个仅为满足他要求的整体。结论一&#xff1a;不可以把问题推给另一部门&#xff1b;结论二&#xff1a;若…

git安装配置教程(小白保姆教程2024最新版)

目录 一、Git是什么?二、安装Git1.下载git2.安装git3.检测git 三、配置Git1.配置本地信息2.配置SSH1&#xff09;SSH与SSH Key是什么&#xff1f;2&#xff09;生成SSH Key3&#xff09;获取ssh key公钥内容&#xff08;id_rsa.pub&#xff09;4&#xff09;Github账号上添加公…

【鼠标悬浮,元素宽度增加,文字一个一个显示出来】

1、实现效果 2、实现思路 宽度变化&#xff1a; 给容器设置:hover时的宽度&#xff0c;通过transition: width 1s ease实现宽度的过渡效果文字一个一个出现&#xff1a;添加一个span标签&#xff0c;并设置文字不能换行&#xff0c;溢出隐藏。通过动画实现span宽度的增加 3、…

ARCM300智慧用电监控装置/剩余电流监测/温度监测/4G上传云平台/断电报警上传/电气火灾预警报警

ARCM300智慧用电在线监控装置是针对 0.4kV 以下的 TT、TN 系统设计的智能电力装置&#xff0c;具有单、三相交流电测量、四象限电能计量、谐波分析、开关量输入、继电器输出功能&#xff0c;以及 RS485 通讯或 GPRS 无线通讯功能&#xff0c;通过对配电回路的剩余电流、导线温度…

HANA计算视图的一切

我已经把BW4HANA的一切 一共几十篇文章都更新完了。当然还有很多想补充的&#xff0c;还没找到时间。 然后我发现&#xff0c;这个HANA 计算视图还是要好好写一写。 还有一些CDS View的创建&#xff0c;以前我写的&#xff0c;由于是上班抽空写的&#xff0c;总想着一篇写全&…

echarts tooltip提示框显示不全

一、背景&#xff1a; 写在前面&#xff1a; 自行封装。一个可由多个柱形图叠加而成的图表&#xff0c;命名为someHoverLine(可自定义)。 下面罗列了移动端和web端的封装组件代码&#xff1b; 展示了vue2、uniapp、vue3的不同封装和使用案列。 二、问题描述&#xff1a; 三、解…

策略模式类图与代码

某大型购物中心欲开发一套收银软件&#xff0c;要求其能够支持购物中心在不同时期推出的各种促销活动&#xff0c;如打折、返利(例如&#xff0c;满300返100),等等。现采用策略(Strategy)模式实现该要求&#xff0c;得到如图7.13 所示的类图。 【Java 代码】 import java.util…