Linux操作系统~进程fork到wait到底怎么用?

news2024/12/24 20:11:14

目录

1.fork()

(1).概念

(2).fork的写时拷贝

(3).fork常规用法

2.进程终止

(1).进程退出场景/退出码

(2).进程常见退出方法

1).exit函数

2)._exit和exit的区别

3).进程退出后,OS层面做了什么呢?

3.进程等待

(1).概念

(2).为什么要让父进程等待

(3).wait

(4).waitpid

(5).获取子进程status

1.子进程的退出有两种情况

2.位运算获取子进程的退出状态(退出码)和终止信号

3.宏函数获取子进程的退出状态(退出码)和判断是否收到终止信号

Q:为什么我们echo可以查到各个进程的退出码?

理解waitpid调用的流程和status

4.第三个参数options-设置等待方式

        1.阻塞等待和非阻塞等待

        2.如何设计一个非阻塞等待的轮询机制


1.fork()

(1).概念

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

#include <unistd.h>
pid_t fork(void);

返回值:自进程中返回0,父进程返回子进程id,出错返回-1

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

  1. 分配新的内存块和内核数据结构(pcb+地址空间mm_struct+页表)给子进程
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度


(2).fork的写时拷贝

        通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:

        父进程和子进程的虚拟地址空间以及页表都是各自有一份的,但是对应数据虚拟地址映射到的物理地址是同一个。

        默认所有的数据都是只读的,如果有子进程想要写某个数据(页表项100),会发生写时拷贝,就会发生缺页中断,先拷贝一份数据到新的物理地址空间,然后修改子进程页表的映射关系,让子进程对应数据的虚拟地址映射到新的物理地址,然后中断结束,让子进程来修改数据。

Q:如何保证进程独立性?(如何保证父子进程间的独立性)——写时拷贝


(3).fork常规用法

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

2.进程终止

(1).进程退出场景/退出码

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果不正确
  3. 代码异常终止(此时程序崩溃了,退出码也就变得没有意义了!我们echo出来也没有意义)

我们的main函数返回0就是表示代码运行完毕,结果正确。(这个返回值就是进程的退出码

我们可以通过echo $?查看最近一次进程退出时的退出码

[zebra@VM-8-12-centos test6]$ echo $?
0

        第二次第三次echo的时候输出的是前一次echo这个进程的退出码。实际上每个命令执行都是一个进程,都会有一个退出码,这个退出码就是main函数的返回值,每个进程都有一个main函数

不同的错误码对应不同的错误信息,当然我们也可以自己设计一种错误码和错误信息的映射。 

(2).进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):

1. 从main返回(代表进程退出,从函数返回也一样,叫做函数返回)

2. 调用exit

3. _exit

异常退出:

ctrl + c,信号终止

1).exit函数

#include <unistd.h>
void _exit(int status);

参数:status 定义了进程的终止状态,父进程通过wait来获取该值(status有很多宏值,比如EXIT_SUCCESS)

  • 说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255。

e.g.:我们就算不加上\n,最后main return或者程序exit的时候也会把缓存区中的内容刷新到显示器上面。

    printf("hello,zebra");
    sleep(5);
    exit(EXIT_SUCCESS);

    return 0;

2)._exit和exit的区别

_exit则是强制终止进程,不会进行进程后续的收尾工作,比如刷新缓冲区。

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

1. 执行用户通过 atexit或on_exit定义的清理函数。

2. 关闭所有打开的流,所有的缓存数据均被写入

3. 调用_exit

在这里也变相说明了我们所谓的缓冲区是用户层面的缓冲区,不再操作系统层面


3).进程退出后,OS层面做了什么呢?

        系统层面,少了一个进程: free PCB,free,mm_struct,free页表和各种映射关系,代码+数据申请的空间也要释放掉掉


3.进程等待

(1).概念

        父进程执行fork之后,会创建子进程,子进程一般都是要帮助父进程完成某种任务,此时父进程需要通过wait/waitpid等待子进程退出。

(2).为什么要让父进程等待

1.通过获取子进程退出的信息,可以得到子进程的执行结果

2.既然父进程要拿到子进程的执行结果,也就需要保证父进程在子进程之后退出,使用wait可以保证时序问题,即让子进程先退出,父进程后退出。

3.子进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放子进程占用的资源。

(3).wait

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

返回值:

成功返回被等待进程pid,失败返回-1。

参数:

输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

(4).waitpid

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

当正常返回的时候waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:

Pid=-1,等待任意一个子进程。与wait等效。(如果这里给的pid和子进程对不上,会等待失败

Pid>0.等待其进程ID与pid相等的子进程。

  • status:(输出型参数,父进程拿到什么status结果,一定和子进程如何退出强相关

为了确定是三种退出情况中的哪一种

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

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

  • options:

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

说明:

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

(5).获取子进程status

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

1.子进程的退出有两种情况

  • 一种是正常终止(结果正确或者不正确)
  • 一种是异常终止(进程因为异常问题,收到了某种信号)

        所以我们可以通过判断是否收到终止信号(0-6,低7位)来判断进程是否是正常终止,如果是正常终止(低7位全是0),我们再去看退出状态(8-15,8个比特位)

2.位运算获取子进程的退出状态(退出码)和终止信号

        如果我们要获取8-15这8位退出状态(退出码),可以让status右移8位,然后和000...000 1111 1111(也就是(status>>8)&0xFF)&一下(因为status是32位的,右移8位后,我们要把前面的24位置0)

        如果我们要获取0-6这7位终止信号,可以让status&0x7F(我们只需要获取低7位就行)

        如果正常退出,退出信号都是0;如果异常退出,退出信号就不是0,此时前面的退出状态也就没有意义

3.宏函数获取子进程的退出状态(退出码)和判断是否收到终止信号

  1. WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。判断进程是否收到终止信号
  2. WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

        首先我们需要判断进程是否收到退出信号,如果收到了,那就是异常退出的;如果没有收到退出信号,那就是正常退出,退出码也就有效了

Q:为什么我们echo可以查到各个进程的退出码?

        bash是命令行启动的所有进程的父进程。bash是通过wait方式得到子进程的退出结果,所以我们echo $?能够查到子进程的退出码

理解waitpid调用的流程和status

        waitpid是一个系统调用接口,肯定是用户来调的。用户调用waitpid让父进程等待子进程退出并变成僵尸状态,然后从进程的pcb中获取exit_code和signal,组合起来放到status中,父进程中的status和用户层的status也就都发生了变化,用户就可以看到进程的退出码和终止信号了。(实际上这个status是一个指针,作为一个输出型参数,所以在操作系统内部修改status会直接影响用户层的status)

 

4.第三个参数options-设置等待方式

WNOHANG: 设置等待方式为非阻塞

若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

1.阻塞等待和非阻塞等待

首先都是等待的一种方式,都是父进程在等子进程退出(子进程退出是一种条件)

阻塞的本质:其实是进程的PCB被放入了等待队列,并将进程的状态改为S状态

返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度

  • 阻塞等待:父进程调waitpid这个接口等待子进程完成时,把父进程放在等待队列里面,等到子进程退出后,再把它从等待队列里面拿出来。
  • 非阻塞等待:父进程不会被放入等待队列,而是时不时地多次调用waitpid,直到子进程结束。

2.如何设计一个非阻塞等待的轮询机制

0:阻塞等待

WNOHANG: 设置等待方式为非阻塞

全称是which no hang,表示不会被hang住

我们进行非阻塞等待时,需要考虑三种情况,并进行基于非阻塞等待的轮询方案

每次循环调用waitpid,通过返回值判断子进程是否退出

情况一:这次查询子进程还没退出,父进程做自己的事情

情况二:这次查询子进程退出了

情况三:调用waitpid出错,返回错误信息

while(1){
    pid_t ret = waitpid(id, &status, WNOHANG);
    if(ret == 0){
        //子进程没有退出,但是waitpid等待是成功的,需要父进程重复进行等待
        printf("Do father things!\n");
    }
    else if(ret > 0){
        //子进程退出了,waitpid也成功了,获取到了对应的结果
        printf("fahter wait: %d, success, status exit code: %d, status exit signal: %d\n", ret, (status>>8)&0xFF, status&0x7F);
        break;
    }
    else{ //ret < 0
        //等待失败
        perror("waitpid");
        break;
    }
    sleep(1);  //每隔一秒进行一次轮询
}

阻塞就是调用waitpid,如果子进程没退出,就干等

非阻塞就是调用一次waitpid,然后继续执行后面的代码,需要自己设计一个基于非阻塞等待的轮询方案

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

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

相关文章

类与对象(中级)

目录 1. 包 1.1 包的概念 1.2 导入包中的类 1.3 自定义包 1.4 常见的包 2. Java三大特性 -- 封装 3. Java三大特性 -- 继承 3.1 继承的概念&#xff1a; 3.2 继承的语法 3.3 父类成员访问 3.3.1 子类中访问父类的成员变量 3.3.2 子类中访问父类的成员方法 4. supe…

数据挖掘(六) 层次聚类

数据挖掘&#xff08;六&#xff09; 层次聚类 1.层次聚类简介 层次聚类算法(Hierarchical Clustering)将数据集划分为一层一层的clusters&#xff0c;后面一层生成的clusters基于前面一层的结果。层次聚类算法一般分为两类&#xff1a; Divisive 层次聚类&#xff1a;又称自…

【nacos】5.1 spring cloud + Nacos 实现统一配置管理

1. 解决的问题&#xff1a; 配置动态更新配置集中式管理配置内容的安全性和权限不同部署环境下的配置 2. 环境&#xff1a; ideaspring cloudspring-cloud-alibaba nacosmavenmqtt &#xff08;客户端&#xff0c;服务器采用的是EMQ X 5.0 &#xff09; 3. pom依赖 3.1 父级…

皮卡丘python turtle海龟绘图(电力球版)附源代码

皮卡丘python turtle海龟绘图&#xff08;电力球版&#xff09;附源代码 本文目录&#xff1a; 一、皮卡丘python turtle海龟成品效果图 二、皮卡丘背景介绍 三、皮卡丘卡角色形象 四、愿你拥有一只皮卡丘 五、Python 海龟画图主要方法 &#xff08;1&#xff09;海龟画图…

Allegro在PCB上制作二维码和条形码操作指导

Allegro在PCB上制作二维码和条形码操作指导 当我们需要在PCB放置一个二维码或者条形码的时候,可以不需要额外去贴标签,可以直接在PCB上制作,如下图 制作出来的二维码和条形码是可以直接用扫码程序扫描的 具体操作步骤如下 首先要用was performance allegro productivity…

python Clickhouse 分布式表介绍和创建、插入和查询数据,以及解决遇到的问题

目录 一、分布式表和本地表 原理解析&#xff1a; 二、Clickhouse创建分布式表结构 三、python代码实现&#xff08;亲测有效&#xff09; 四、解决遇到的问题 解决 DB::Exception: Missing columns: wefgrgrfew while processing query: wefgrgrfew, required columns: …

【深度学习】第三章:卷积神经网络

文章目录1. 为什么要使用卷积神经网络&#xff1f;2. 卷积2.1 数学上的卷积2.2 深度学习的卷积3. 卷积的构成4. 卷积的特征5. 卷积的计算(1) 一维卷积计算(2) 二维卷积计算(黑白图片)(2) 三维卷积计算(彩色图片)6. 卷积的优势7. 卷积神经网络7.1 卷积层7.2 池化层7.3 全连接层8…

浅聊一下Nginx

目录 Nginx的下载与安装 去Nginx官网安装&#xff1a;nginx news 直接进入下载页面进行安装 直接安装&#xff1a; 在服务器上使用命令对nginx的安装过程 Nginx命令 Nginx配置文件结构 Nginx配置文件&#xff08;conf/nginx.conf&#xff09;正题分为三个部分&#xff1…

Vue框架的学习(Vue操作指令学习三 V-bind )第三课

Vue框架的学习(Vue操作指令学习三 V-bind )第三课 语法的学习关键在于实操 案例一 V-bind基本操作 通过这个案例了解基本的操作 <div id"app"><img src"./img/1-1 (1).jpg" alt""><!--! 绑定图片利用V-bind指令 --><img v-…

一本通1073;救援(c++)

#include <iostream> #include <cmath> using namespace std; int main() {// 屋顶数目、人数int n, m;// x坐标、y坐标、实际距离、所需时间double x, y, s, sum 0;cin >> n; // 输入屋顶数目for (int i 1; i < n; i){// 输入x、y坐标和人数cin >&g…

Rockland丨艾美捷Rockland大鼠γ-球蛋白说明书

艾美捷Rockland大鼠γ-球蛋白&#xff1a; 大鼠γ-球蛋白组分由含有全抗体和其他非白蛋白血浆蛋白的血清组分组成。丙种球蛋白可用于治疗&#xff0c;以暂时提高患者的免疫力&#xff08;如免疫抑制感染后&#xff09;或增加接受肾移植的可能性。γ-球蛋白级分可作为对照试剂用…

【问题记录与解决】jupyter notebook 无法重命名,无法运行测试代码 || jupyter notebook 中常用的两个快捷键。

可以回顾下之前遇到的小问题&#xff0c;因为这次的问题解决就有赖于之前记录的内容喔&#xff01; 一、问题记录与解决】启动Jupyter&#xff0c;运行代码时报错【Error】 || 通过 Jupyter 建立的Python文件在哪儿 || Jupyter 中 移动 Python 文件 到 指定文件夹 二、【记录】…

nosql期末

文章目录第一章 绪论选择判断题简答题1. NoSQL和关系型数据库在设计目标上有何主要区别&#xff1f;2. 简要总结一下NoSQL数据库的技术特点。第二章 NoSQL数据库的基本原理选择判断简答题1. 描述分布式数据管理的特点。2 什么是CAP原理&#xff1f;CAP原理是否适用于单机环境&a…

数据结构之顺序表

目录一、什么是顺序表二、顺序表的分类1、静态顺序表2、动态顺序表(重要)三、C语言实现顺序表1、顺序表的基本结构&#xff08;2&#xff09;、动态顺序表2、动态顺序表中常见的函数接口&#xff08;1&#xff09;、初始化&#xff08;2&#xff09;、销毁函数&#xff08;3&am…

Java:什么是Quarkus?

Quarkus是一个全栈Kubernetes原生Java框架&#xff0c;用于Java虚拟机(JVM)和原生编译&#xff0c;专门针对容器优化Java&#xff0c;使其成为无服务器、云和Kubernete环境的有效平台。 Quarkus旨在与流行的Java标准、框架和库(如Eclipse MicroProfile和Spring)以及Apache Kafk…

基于FOC电路低次谐波抑制Simulink仿真

Foc电路如下图 当Sa导通时当Sb导通时当Sa导通时 Dc电压全被C2吃了 Lr电流向→ 当Sb导通时 Dc电压全被C1吃了 Lr电流向← 假设C1C2C&#xff0c;开关频率接近无穷、占空比为50%时 Uc1Uc2Udc/2、Ilr0 当占空比D>50%,Uc2增大&#xff0c;Ilr→增大 当占空比D<50%,Uc1增…

【java基础系列】13- java的面向对象

面向对象 程序是为了模拟现实世界&#xff0c;解决现实问题而使用计算机语言编写的指令集合。 1、面向对象的思想&#xff08;Object Oriented Programming&#xff09; 一切客观存在的事物都是对象&#xff0c;万物皆对象。任何对象&#xff0c;一定具有自己的特征和行为 特…

【计算机网络】HTTPS协议的加密流程

文章目录HTTPS简介关于加密过程中的名词SSL中的加密操作对称加密非对称加密证书HTTPS执行流程总结HTTPS简介 HTTPS协议也是应用层的协议&#xff0c;它是在HTTP协议的基础上引入了加密层&#xff0c;称为SSL&#xff08;旧的叫法&#xff09;或TLS&#xff08;新的叫法&#x…

如何用卡片翻转动画制作一个星座运势页面

效果展示&#xff1a;前置准备&#xff1a; 图片素材 有文案素材的detail页面 配置按钮组件触发器 具体步骤&#xff1a; 添加配置按钮、图片组件 配置图片组件动画和触发器步骤分解&#xff1a; 添加配置轮播图基础组件 添加 按钮 组件 添加 图片 组件 选中 按钮 组件 在 数据…

【菜鸡读论文】Learning-based Video Motion Magnification

Learning-based Video Motion Magnification 哈喽&#xff0c;大家好呀&#xff01; 这周有点开心&#xff0c;看到了一篇很有趣的论文。最近天气好热&#xff0c;明明已经十一月了&#xff0c;最近的温度却一直在25度以上&#xff0c;甚至有种可以过夏天&#xff0c;穿裙子的…