Linux : 进程等待以及进程终止

news2025/4/19 7:06:01

进程控制之进程等待

  • (一)fork函数
    • 1*fork函数返回值
    • 2.父子进程的写时拷贝
  • (二)进程终止
    • 1.进程退出码
    • 2.进程常见退出方法
      • (1)_exit
      • (2)exit
      • (3)return
    • 3.进程的异常退出
  • (三)进程等待
    • 1.获得子进程status
    • 2.进程等待的方法
      • (1)wait方法
      • (2)waitpid方法
    • 3.多进程创建和等待
    • 4.非阻塞接口的轮询检测

(一)fork函数

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度
    在这里插入图片描述fork之后,父子进程谁先执行完全由调度器决定。

1*fork函数返回值

  • 子进程返回0。
  • 父进程返回的是子进程的pid。

子进程只有一个父进程的,而父进程可以有很多个子进程。对于子进程来讲,它的父进程是根本不需要被标识,因为天然就知道它的父进程;而对于父进程来讲,它的子进程是需要被标识的,因为父进程的子进程是可以有很多的,所以就需要进行标识哪一个子进程。父进程只有知道子进程的标识符的时候,才能更好的给子进程分配任务。

  • 调用fork函数后,操作系统都做了什么??
    父进程调用fork函数后,为了创建子进程,fork函数内部将会进行一系列操作,包括创建子进程的进程控制块(PCB)、创建子进程的进程地址空间(mm_struct)、创建子进程对应的页表等等。子进程创建完毕后,操作系统还需要将子进程的进程控制块添加到系统进程列表当中,此时子进程便创建完毕了。

因此在fork函数返回前,子进程已经创建完毕,所以父子进程都执行了return语句,这也是为什么fork函数有两个返回值的原因。

2.父子进程的写时拷贝

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
可以这么理解:
子进程把父进程的进程地址空间和页表以及代码数据都拷贝到了自己身上。当父子进程某一方对代码数据进行写入进行写时拷贝

  • 操作系统如何实现写时拷贝操作?
    下面是一个父进程的模拟图。
    在这里插入图片描述

看到上面这个图,原本父进程的某个代码数据是可读写状态的,当创造子进程的时候,这个状态就会变成只读状态,
如下图:
在这里插入图片描述

那么原本可读写的数据变成了可读,如何进行对该数据写入呢?
当父子进程的某一方要对原本可读写的数据进行写入时,操作系统就会识别出原本该数据位置时可以写入的,操作系统并不会做出异常处理,而是把数据拷贝一份下来,并在物理内存中开辟一个空间,将修改后的数据写进去。然后对写入数据的进程的对应页表处进行重新映射。

比如子进程修改数据时:
进行写时拷贝之后,操作系统会对原本可读写的状态进行恢复,如下图
在这里插入图片描述

  • 为什么不在创建子进程的时候就进行数据的拷贝,而是通过页表指向同一块物理地址呢?
    子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,操作系统会按需分配,在需要修改数据的时候再分配,这样可以高效的使用内存空间。

  • 写时拷贝的意义
    进程具有独立性。多进程运行,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的修改影响到父进程。

  • 代码会不会进行写时拷贝?
    90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝。

fork的常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  • 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制

(二)进程终止

进程退出场景有三种:

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

1.进程退出码

main函数结尾,我们最后都会写上 return 0;这个0便是退出码。那么我们为什么要写退出码呢??其实main函数也是被调度的函数,操作系统也需要知道main函数是否正确执行完毕,而规定0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,所以我们结尾都写 return 0;

查看最近一次进程的退出码信息:

echo $? //查看最近一次进程的退出码信息

在这里插入图片描述

查看进程的退出码对应描述:
使用strerror命令。
通过如下代码我们可以查看对应退出码信息

#include<stdio.h>                                                                                                                                                                             
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main()
{
    for (int i = 0; i < 200; i++)
    {
        printf("退出码%d: %s\n", i, strerror(i));
    }
    return 0;
}

在这里插入图片描述

退出码都有对应的字符串含义,帮助用户确认执行失败的原因,而这些退出码具体代表什么含义是人为规定的,不同环境下相同的退出码的字符串含义可能不同。

2.进程常见退出方法

  • 从main返回
  • 调用exit
  • _exit

异常退出:

  • ctrl + c,信号终止

(1)_exit

_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前做任何收尾工作。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>

int main()
{
    printf("Hello World"); //注意这里没加\n
    sleep(1);
    _exit(1);
}

运行结果如下:
在这里插入图片描述
可以看到这里并没有执行打印,因为_exit函数并不会刷新缓冲区,只是一味的退出进程。

(2)exit

exit最后也会调用_exit, 但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit
    在这里插入图片描述

(3)return

return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。

3.进程的异常退出

进程发生异常时,进程会直接终止。进程出现异常,本质是我们的进程收到了对应的信号。
通常代码错误导致进程运行时异常退出。

任何进程最终的执行情况有两个数字表示:

  • 退出信号
  • 进程退出码

而进程信号出现异常,进程会直接退出并且弹出对应的错误信息,例如下面这样
在这里插入图片描述

我们可以通过如下命令查看对应的进程信号
在这里插入图片描述
这里没有0,因为0是正常的,没有异常。

(三)进程等待

进程等待必要性

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

1.获得子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待

具体细节如下图(只研究status低16比特位):
在这里插入图片描述
进程正常退出的时候,高8位表示进程的退出状态,即退出码。
进程若是被信号所杀,则低7位表示终止信号,而第8位比特位是core dump标志。

如何从status得到进程的退出码和退出信号
exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F; //退出信号

系统当中提供了两个宏来获取退出码和退出信号。
WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
WEXITSTATUS(status):用于获取进程的退出码。

2.进程等待的方法

(1)wait方法

等待任意子进程,默认是阻塞等待

  • 原型:pid_t wait(int* status);
  • 等待成功返回被等待进程的pid,等待失败返回-1。
  • 参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL。

我们通过如下代码进行测试:

int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        int cnt = 3;
        while(cnt--)
        {
            printf("我是子进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
        exit(0);
    }
    //父进程
    int status = 0;
    pid_t ret = wait(&status);
    if(ret == id)
    {
        printf("wait success\n");

        printf("exit code : exit signal: %d , exit ret: %d\n",WIFEXITED(status),WEXITSTATUS(status));
    }
    
    sleep(3);
    return 0;
}

运行结果如下:
在这里插入图片描述

(2)waitpid方法

原型pid_ t waitpid(pid_t pid, int *status, int options);
返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:
    Pid=-1,等待任一个子进程。与wait等效。
    Pid>0.等待其进程ID与pid相等的子进程。
  • status: (与wait函数的用法相同)
    WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:
    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
    等于0:父进程会一直等待子进程。

我们通过下面代码认识一下waitpid:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
    pid_t id = fork();

    if(id == 0)
    {
        int cnt = 3;
        while(cnt--)
        {
            printf("我是子进程,pid: %d, ppid: %d\n",getpid(),getppid());
            sleep(1);
        }
        exit(1);
    }
    //父进程
    int status = 0;
    pid_t ret = waitpid(id,&status,0);
    if(ret == id)
    {
        printf("wait success\n");

        printf("exit code : exit signal: %d , exit ret: %d\n",WIFEXITED(status),WEXITSTATUS(status));
    }
    
    sleep(3);
    return 0;
}

运行结果:
在这里插入图片描述

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

3.多进程创建和等待

通过循环创建多个进程,但是要注意子进程的相关代码完成记得退出,否则子进程会执行原本父进程该执行的语句,造成混乱

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{

    for (int i = 0; i < 10; i++)
    {
        pid_t id = fork();
        if (id == 0)
        {
            // child
            printf("我是子进程:%d\n", i);
            sleep(1);
            exit(i);
        }
    }
    for (int i = 0; i < 10; i++)
    {
        int status = 0;
        pid_t ret = waitpid(-1, &status, 0);
        if (ret >= 0)
        {
            // wait 
            printf("等待成功\n");
            if (WIFEXITED(status))
            {

                printf("exit code:%d\n", WEXITSTATUS(status));
            }
            else
            {
                // 信号出错
                printf("信号出错,信号为:%d\n", status & 0x7F);
            }
        }
        else
        {
            printf("wait fail\n");
        }
    }
    return 0;
}

运行结果如下:
在这里插入图片描述

4.非阻塞接口的轮询检测

waitpid的第三个option参数为0时,是阻塞等待,即父进程会一直等待子进程的推出信息才会运行后面的代码。这种情况下,如果子进程的运行时间还很久,而父进程却一直等待,会造成内存空间的浪费,即内存泄漏。

当option = WNOHANG时,父进程在等待子进程退出的时候会继续运行代码,也就是子进程未退出的时候父进程去干别的事情,子进程退出的时候,父进程在通过wait或者是waitpid去接收再双双退出。

我们可以通过循环调用的waitpid的方式实现非阻塞接口的轮询,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // child
        int count = 3;
        while (count--)
        {
            printf("子进程..., getppid():%d\n", count, getpid(), getppid());
            sleep(3);
        }
        exit(0);
    }
    // father
    while (1)
    {
        int status = 0;
        pid_t ret = waitpid(id, &status, WNOHANG);
        if (ret > 0)
        {
            // wait sucess
            printf("等待成功\n");
            printf("exit code:%d\n", WEXITSTATUS(status));
            break;
        }
        else if (ret == 0) // 等于0时
        {
            // 子进程没结束
            printf("父进程可以做某些工作\n");
            sleep(1);
        }
        else
        {
            // wait fail
            printf("wait fail\n");
            break;
        }
    }
    return 0;
}

当子进程没结束,而父进程还在等待时,waitpid 返回 0,父进程可以在这小段时间内执行一些比较轻量的代码。

运行结果如下:
在这里插入图片描述

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

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

相关文章

LSTM结合LightGBM高纬时序预测

1. LSTM 时间序列预测 LSTM 是 RNN&#xff08;Recurrent Neural Network&#xff09;的一种变体&#xff0c;它解决了普通 RNN 训练时的梯度消失和梯度爆炸问题&#xff0c;适用于长期依赖的时间序列建模。 LSTM 结构 LSTM 由 输入门&#xff08;Input Gate&#xff09;、遗…

【统信UOS操作系统】python3.11安装numpy库及导入问题解决

一、安装Python3.11.4 首先来安装Python3.11.4。所用操作系统&#xff1a;统信UOS 前提是准备好Python3.11.4的安装包&#xff08;可从官网下载&#xff08;链接&#xff09;&#xff09;&#xff0c;并解压到本地&#xff1a; 右键&#xff0c;选择“在终端中打开”&#xff…

【中间件】nginx反向代理实操

一、说明 nginx用于做反向代理&#xff0c;其目标是将浏览器中的请求进行转发&#xff0c;应用场景如下&#xff1a; 说明&#xff1a; 1、用户在浏览器中发送请求 2、nginx监听到浏览器中的请求时&#xff0c;将该请求转发到网关 3、网关再将请求转发至对应服务 二、具体操作…

鸿蒙应用(医院诊疗系统)开发篇2·Axios网络请求封装全流程解析

一、项目初始化与环境准备 1. 创建鸿蒙工程 src/main/ets/ ├── api/ │ ├── api.ets # 接口聚合入口 │ ├── login.ets # 登录模块接口 │ └── request.ets # 网络请求核心封装 └── pages/ └── login.ets # 登录页面逻辑…

突发重磅消息!!!CVE项目将被取消?

突发重磅消息&#xff01;&#xff01;&#xff01;CVE项目将被取消&#xff1f;突发&#xff01;来自可靠消息来源。MITRE 对 CVE 项目的支持将于明天到期。附件信件已发送给 CVE 董事会成员。https://mp.weixin.qq.com/s/N3qkiHaDfzDuBMK3JbBCjw

详解与FTP服务器相关操作

目录 什么是FTP服务器 搭建FTP服务器相关 ​编辑 Unity中与FTP相关的类 上传文件到FTP服务器 使用FTP服务器上传文件的关键点 开始上传 从FTP服务器下载文件到客户端 使用FTP下载文件的关键点 开始下载 关于FTP服务器的其他操作 将文件的上传&#xff0c;下载&…

解决 .Net 6.0 项目发布到IIS报错:HTTP Error 500.30

今天在将自己开发许久的项目上线的时候&#xff0c;发现 IIS 发布后请求后端老是报一个 HTTP Error 500.30 的异常&#xff0c;如下图所示。   后来仔细调查了一下发现是自己的程序中写了 UseStaticFiles 的依赖注入&#xff0c;这个的主要作用就是发布后端后&#xff0c;想…

STM32F103_HAL库+寄存器学习笔记16 - 监控CAN发送失败(轮询方式)

导言 《STM32F103_HAL库寄存器学习笔记15 - 梳理CAN发送失败时&#xff0c;涉及哪些寄存器》从上一章节看到&#xff0c;当CAN消息发送失败时&#xff0c;CAN错误状态寄存器ESR的TEC会持续累加&#xff0c;LEC等于0x03&#xff08;ACK错误&#xff09;。本次实验的目的是编写一…

实现定长的内存池

池化技术 所谓的池化技术&#xff0c;就是程序预先向系统申请过量的资源&#xff0c;然后自己管理起来&#xff0c;以备不时之需。这个操作的价值就是&#xff0c;如果申请与释放资源的开销较大&#xff0c;提前申请资源并在使用后并不释放而是重复利用&#xff0c;能够提高程序…

vs2022使用git方法

1、创建git 2、在cmd下执行 git push -f origin master &#xff0c;会把本地代码全部推送到远程&#xff0c;同时会覆盖远程代码。 3、需要设置【Git全局设置】&#xff0c;修改的代码才会显示可以提交&#xff0c;否则是灰色的不能提交。 4、创建的分支&#xff0c;只要点击…

Mysql中表的使用(3)

目录 1.updata的使用 2.delete(删除表中数据)drop&#xff08;删除表&#xff09; 数据库的约束 1.NOT NULL 指定列不能为空 2.UNIQUE指定列唯一 3.DEFAULT(默认值) 4.PRIMARY KEY 5.自增主键 1.updata的使用 1.0update 表名 set 列名x where 列名y; 2.0update 表名 s…

BUUCTF-Web(1-20)

目录 一.SQL注入 (1)[极客大挑战 2019]EasySQL 万能密码 (7)[SUCTF 2019]EasySQL 堆叠注入 解一&#xff1a; 解二&#xff1a; (10)[强网杯 2019]随便注 堆叠注入 解一&#xff1a; 解二&#xff1a; 解三&#xff1a; (8)[极客大挑战 2019]LoveSQL 联…

Uniapp:确认框

目录 一、 出现场景二、 效果展示三、具体使用 一、 出现场景 在项目的开发中&#xff0c;会经常出现删除数据的情况&#xff0c;如果直接删除的话&#xff0c;可能会存在误删&#xff0c;用户体验不好&#xff0c;所以需要增加一个消息提示&#xff0c;提醒用户是否删除。 二…

实验四 中断实验

一、实验目的 掌握中断服务程序的编写。 二、实验电路 三、实验内容 1&#xff0e;实验用PC机内部的中断控制器8259A&#xff0c;中断源用TPC-ZK实验箱上的单脉冲电路&#xff0c;将单脉冲电路的输出接中断请求信号IRQ&#xff0c;每按一次单脉冲按键产生一次…

腾势品牌欧洲市场冲锋,科技豪华席卷米兰

在时尚与艺术的交汇点&#xff0c;米兰设计周的舞台上&#xff0c;一场汽车界的超级风暴正在酝酿&#xff0c;腾势品牌如一头勇猛无畏的雄狮&#xff0c;以雷霆万钧之势正式向欧洲市场发起了冲锋。其最新力作——腾势Z9GT的登场&#xff0c;仿佛是一道闪电划破夜空&#xff0c;…

Java 中的各种锁详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/literature?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;…

【2025年泰迪杯数据挖掘挑战赛】A题 数据分析+问题建模与求解+Python代码直接分享

目录 2025年泰迪杯数据挖掘挑战赛A题完整论文&#xff1a;建模与求解Python代码1问题一的思路与求解1.1 问题一的思路1.1.1对统计数据进行必要说明&#xff1a;1.1.2统计流程&#xff1a;1.1.3特殊情况的考虑&#xff1a; 1.2 问题一的求解1.2.1代码实现1.2.2 问题一结果代码分…

NO.95十六届蓝桥杯备战|图论基础-单源最短路|负环|BF判断负环|SPFA判断负环|邮递员送信|采购特价产品|拉近距离|最短路计数(C++)

P3385 【模板】负环 - 洛谷 如果图中存在负环&#xff0c;那么有可能不存在最短路。 BF算法判断负环 执⾏n轮松弛操作&#xff0c;如果第n轮还存在松弛操作&#xff0c;那么就有负环。 #include <bits/stdc.h> using namespace std;const int N 2e3 10, M 3e3 1…

在机器视觉检测中为何选择线阵工业相机?

线阵工业相机&#xff0c;顾名思义是成像传感器呈“线”状的。虽然也是二维图像&#xff0c;但极宽&#xff0c;几千个像素的宽度&#xff0c;而高度却只有几个像素的而已。一般在两种情况下使用这种相机&#xff1a; 1. 被测视野为细长的带状&#xff0c;多用于滚筒上检测的问…

Windows 下 MongoDB ZIP 版本安装指南

在开发和生产环境中&#xff0c;MongoDB 是一种非常流行的 NoSQL 数据库&#xff0c;以其灵活性和高性能而受到开发者的青睐。对于 Windows 用户来说&#xff0c;MongoDB 提供了多种安装方式&#xff0c;其中 ZIP 版本因其灵活性和轻量级的特点&#xff0c;成为很多开发者的首选…