Linux 进程控制(进程创建,进程等待)

目录

进程创建

fork函数初识

fork函数返回值

写时拷贝

fork常规用法

fork调用失败的原因

进程终止

进程退出场景

进程退出码

进程常见退出方法

exit函数

_exit函数

return退出

return、exit和_exit之间的区别与联系

进程异常退出

进程等待

进程等待的必要性

获取子进程status

进程等待的方法

wait方法

waitpid方法

多进程创建以及等待的代码模型

非阻塞轮询


进程创建

fork函数初识

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

返回值:

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

进程调用fork,当控制转移到内核中的fork代码后,内核做: 分配新的内存块和内核数据结构给子进程 将父进程部分数据结构内容拷贝至子进程 添加子进程到系统进程列表当中 fork返回,开始调度器调度

fork之后,父子进程代码共享

运行之后:

我们可以观察到fork之前的代码执行了一次,而fork之后的代码执行了两次,其中Before是由父进程打印的,而调用fork函数之后打印的两个After,则分别由父进程和子进程两个进程执行。也就是说,fork之前父进程独立执行,而fork之后父子进程两个执行流分别执行

注意:fork之后,父进程和子进程谁先执行完全是由调度器决定

fork函数返回值

fork函数为什么要给子进程返回0,给父进程返回子进程的pid?

一个父进程可以创建很多个子进程,而一个子进程只能有一个父进程。因此对于子进程来说,父进程是不需要被标识的;而对于父进程来说,子进程是需要被标识的,因为父进程创建子进程的目的是让其执行任务的,父进程只有知道了子进程的pid才能很好的对子进程指派任务

为什么fork有两个返回值?

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

也就是说,在fork函数内部执行return语句之前,子进程就已经创建完毕了,那么之后的return语句不仅父进程需要执行,子进程也同样需要执行,这就是fork有两个返回值的原因

写时拷贝

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

1.为什么数据要进行写时拷贝?

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

2.为什么不创建子进程的时候就进行数据的拷贝?

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,我们应该按需分配,在需要修改数据的时候再分配(延迟分配),这样可以高效的使用内存空间

3.代码会不会进行写时拷贝?

90%的情况下不会的,但着并不代表代码不能进行写时拷贝,例如再进行进程替换的时候,则需要进行代码的写时拷贝

fork常规用法

1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子 进程来处理请求。

2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因

fork函数创建子进程也可能会失败,有以下两种情况:

1.系统中有太多的进程

2.实际用户的进程数超过了限制

进程终止

进程退出场景

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止

进程退出码

进程退出码
我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2022当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的。

既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,我们一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因。

当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。
例如,对于下面这个简单的代码:


该进程结束之后我们可以查看该进程的退出码:

使用echo $?指令

这时,我们就可以确定以上代码顺序执行

为什么以0表示代码执行成功,以非0表示代码执行错误?

因为成功只有一种情况,成功了就算成功了,而失败有很多种情况,例如:野指针问题,除0错误,栈溢出,越界访问,内存空间不足等原因

c语言中的strerror函数可以通过错误码来获取该错误码对应的错误信息:

运行之后,我们就可以得到错误码所对应的错误信息

实际上再Linux中的 pwd ,ls指令都是可执行程序,在其执行完毕之后也会有退出码

顺序运行之后退出码为0

但是,如果我们使用的是错误的指令,它会返回非0的错误码

进程常见退出方法

exit函数

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

2. 关闭所有打开的流,所有的缓存数据均被写入(刷新缓冲区)

3. 调用_exit终止进程

exit在退出进程时,会先将缓冲区的数据输出,在终止进程

运行之后,我们可以看到,缓冲区的数据输入到了缓冲区上

_exit函数

尽量不要去使用这个接口,它可以在程序的任何地方使用,使用时会直接终止掉程序,并不会再终止程序前做任何收尾工作

以下代码在使用_exit终止程序,缓冲区的数据不会被刷新出来

代码运行之后:

通过观察可以发现缓冲区的数据并没有刷新出来

return退出

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

return、exit和_exit之间的区别与联系

return、exit和_exit之间的区别

只有在main函数中return 才能起到退出进程的作用,在其它子函数中只会退出该函数并不会退出进程,exit和_exit可以在代码中任何地方使用,都有退出进程的作用

使用exit函数退出进程前,exit函数会执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作。

return、exit和_exit之间的联系

执行return n等同于执行exit(n),因为在main函数运行结束之后,return的返回值会当作exit函数的参数,来调用exit函数

使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

进程异常退出

情况一:向进程发出信号导致进程异常退出

例如:对一个进程使用 kill -9 pid 或者 使用ctrl+C使进程异常退出 

情况二:代码错误导致进程运行时异常退出

例如:代码执行时遇到野指针或者遇到除0错误时,进程异常退出

进程等待

进程等待的必要性

1.子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

2.进程一旦变成僵尸状态,连kill -9 也无法杀掉该进程,因为谁也没有办法杀死一个已经死去的进程。

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

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

获取子进程status

下面进程等待所使用的两个函数wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统进行填充

如果对status这个参数传入NULL,则表示不关心子进程的退出状态信息,否则操作系统会通过该参数将子进程的退出信息反馈给父进程

status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):

在status低十六位中,高八位表示进程的退出状态(退出码),

进程若是被信号所杀,则低7位表示终止信号,而第八位比特位是core dump 标记

我们可以通过一系列位操作,就可以根据status得到进程的退出码和退出信息

exitCode = (status >> 8) & 0xff; //退出码
exitSignal = status & 0x7f; //退出信号

对于次系统还提供了两个宏来获取退出码和退出信号

WIFEXITED(status):用于查看进程是否正常退出,本质是检查是否收到信号

WEXITSTATUS(status):用于获取进程的退出码

exitNormal = WIFEXITED(status); //是否正常退出
exitCode = WEXITSTATUS(status); //获取退出码

注意:当一个进程非正常退出时,说明该进程是被信号所杀死,那么该进程的退出码也就没意义了

进程等待的方法

wait方法

函数原型: pid_t wait(int* status);

作用:等待任意子进程

返回值:等待成功返回被等待进程的pid,等待失败返回-1

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

例如:创建子进程之后,父进程可使用wait函数一直等待子进程,直到子进程退出后读取子进程的退出信息

  1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/types.h>5 #include <sys/wait.h>6 7 int main()8 {9         pid_t id = fork();10         if(id == 0) //child11         {12                 int cnt = 10;13                 while(cnt)14                 {15                         printf("I am child!. pid: %d, ppid: %d\n", getpid(), getppid())    ;16                         sleep(1);17                         cnt--;18                 }19                 exit(0);20         }21 22         //parent23         int status = 0;24         pid_t ret = wait(&status);25 26         if(ret > 0)27         {28                 printf("wait child success...\n");29                 if(WIFEXITED(status)) //exit normal30                 {       31                         printf("exit code: %d", WEXITSTATUS(status));32                 }33         }34         35         sleep(3);36         return 0;37 }

我们可以使用监控脚本来对本进程进行实时监控:

while :; do ps ajx | head -1 && ps ajx | grep myproc | grep -v grep; echo "###############"; sleep 1; done

这时我们可以看到,当子进程退出之后,父进程读取了子进程的退出信息,子进程也就不会变成僵尸进程了

waitpid方法

函数原型: pid_ t waitpid(pid_t pid, int *status, int options);

作用:等待指定子进程或任意子进程

返回值:

1.等待成功返回被等待进程的pid

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

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

参数:

1.pid:待等待子进程的pid,若设置为-1,则等待任意子进程

2.status:输出型参数,获取子进程的退出状态,不关心可设置为NULL

3.option:当设置WNOHANG|时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待,若正常结束,则返回该子进程的pid

例如:创建子进程之后,父进程可以使用waitpid函数一直等待子进程(此时将waitpid的第三个参数设置为0),直到子进程退出后读取子进程的退出信息

1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/types.h>5 #include <sys/wait.h>6 7 int main()8 {9         pid_t id = fork();10         if(id == 0) //child11         {12                 int count = 10;13                 while(count)14                 {15                         printf("I am child..., pid: %d, ppid: %d\n", getpid(), getppid());16                         sleep(1);17                         count--;18                 }19                 exit(0);20         }21 22         //parent23         int status = 0;24         pid_t ret = waitpid(id, &status, 0);25         if(ret > 0)26         {27                 printf("Wait success....\n");28                 //wait success29                 if(WIFEXITED(status))30                 {31                        //exit normal32                         printf("exit code: %d\n", WEXITSTATUS(status));33                 }34                 else35                 {36                         //signal kill37                         printf("signal kill: %d\n", status&0x7F);38                 }39         }40 41         sleep(3);42 43         return 0;44 }

在父进程运行的过程中,我们可以尝试使用kill -9命令将子进程杀死,这时父进程也能等待子进程成功

注意:被信号杀死而退出的进程,其退出码将没有意义

多进程创建以及等待的代码模型

实际上我们还可以同时创建多个子进程,然后让父进程依次等待子进程退出,这叫做多进程创建以及等待的代码模型

例如以下代码中同时创建了10个子进程,同时将子进程这些子进程的pid放入到数组中

#include <stdio.h>
#include <stdlib.h>  
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{ int ids[10] = {0};for(int i = 0; i < 10; ++i){pid_t id  = fork();if(id == 0) //child{printf("child create success...:%d pid: %d, ppid: %d\n", i, getpid(), getppid());//sleep(1);exit(i);}//parentsleep(1);ids[i] = id;}int status = 0;for(int i = 0; i < 10; ++i){pid_t ret = waitpid(ids[i], &status, 0);if(ret >= 0){printf("wait success...\n");if(WIFEXITED(status)){//exit normalprintf("exit code: %d\n", WEXITSTATUS(status));sleep(1);}else{//signal killprintf("exit signal: %d\n", status&0x7F);sleep(1);}}}return 0;
}

运行如下:

非阻塞轮询

在上述的例子中,当子进程未退出时,父进程都在一直等待子进程的退出,在等待期间,在等待期间父进程没有做任何事情,这种父进程处于阻塞状态下的等待叫做阻塞等待

实际上我们可以让父进程不要一直等待子进程退出,而是当子进程未推出时父进程苦于做一些自己的事情,当子进程未退出时父进程可以做一些自己的事情,当子进程退出时再读取子进程的退出信息,即:非阻塞等待

向waitpid函数的第三个参数potions传入WNOHANG,这样一来,等待的子进程若是没结束,那么waitpid函数将直接返回0,不予以等待。而等待的子进程若是正常结束,则返回子进程的pid

例如,父进程可以隔一段时间调用一次waitpid函数,若是等待的子进程尚未退出,则父进程可以去做一些其他事,过一段时间在调用waitpid函数读取子进程的退出信息

int main()
{pid_t id = fork();//childif(id == 0){int cnt = 3;while(cnt--){printf("child do something...PID:%d, PPID:%d\n", getpid(), getppid());sleep(3);}exit(0);}//parentwhile(1){int status = 0;pid_t ret = waitpid(id, &status, WNOHANG);if(ret > 0){printf("wait child success...\n");printf("exit code:%d\n", WEXITSTATUS(status));break;}else if(ret == 0){printf("parent do other things...\n");sleep(1);}else{printf("waitpid error...\n");break;}}return 0;
}

运行结果:父进程每隔一段时间进去查看子进程是否退出,若为退出则父进程先去忙自己的事情,过一段时间再来查看,直到子进程退出之后读取子进程的退出信息

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ROS2下Rviz显示orbbec相机depth深度图

ROS2下Rviz显示orbbec相机depth深度图 视频讲解 ROS2下Rviz显示orbbec相机depth深度图 在《ROS2下编写orbbec相机C package并Rviz显示》的基础上&#xff0c;继续添加depth图像的获取及显示 rgb_publisher_ this->create_publisher<sensor_msgs::msg::Image>("…

算法——结合实例了解Minimax算法(极小化极大算法)

计算机科学中最有趣的事情之一就是编写一个人机博弈的程序。有大量的例子&#xff0c;最出名的是编写一个国际象棋的博弈机器。但不管是什么游戏&#xff0c;程序趋向于遵循一个被称为Minimax算法&#xff0c;伴随着各种各样的子算法在一块。本篇将简要介绍 minimax 算法&#…

场外个股期权下单后多久成交?场外个股期权对投资组合的影响

对普通老板们而言&#xff0c;它如同精密手术刀——用得好可精准优化投资组合&#xff0c;用不好则可能伤及本金。记住两个关键&#xff1a;一是永远用"亏得起的钱"参与&#xff0c;二是把合约条款当"药品说明书"逐字研读。 场外个股期权下单后多久成交&am…

SolidWorks C# How

目录 1.如何创建C#插件程序? 2.插件程序需要继承的类是什么? 3.如何创建C#.net WPF程序? 4.WPF界面程序参考 5.如何获取类的框图 6.如何安装XCAD.net的 nuget包 7.如何扩展命令到菜单栏和工具栏 8.如何添加自定义面板 9.如何对文档管理进行编程 10.XCAD 开发solid…

【Go并发编程】Goroutine 调度器揭秘:从 GMP 模型到 Work Stealing 算法

每天一篇Go语言干货&#xff0c;从核心到百万并发实战&#xff0c;快来关注魔法小匠&#xff0c;一起探索Go语言的无限可能&#xff01; 在 Go 语言中&#xff0c;Goroutine 是一种轻量级的并发执行单元&#xff0c;它使得并发编程变得简单高效。而 Goroutine 的高效调度机制是…

华为昇腾 910B 部署 DeepSeek-R1 蒸馏系列模型详细指南

本文记录 在 华为昇腾 910B(65GB) * 8 上 部署 DeepSeekR1 蒸馏系列模型&#xff08;14B、32B&#xff09;全过程与测试结果。 NPU&#xff1a;910B3 (65GB) * 8 &#xff08;910B 有三个版本 910B1、2、3&#xff09; 模型&#xff1a;DeepSeek-R1-Distill-Qwen-14B、DeepSeek…

桥接模式 Bridge Pattern

桥接模式Abstraction 和 Implementor 的理解 在图书馆看到一本 通过电商项目真正实战《贯穿设计模式》。拿起来翻到了 桥接模式&#xff0c;感觉味道不对&#xff0c;和我印象中不一样。 感谢这位同学提供的源码 贯穿设计模式-适配器模式桥接模式_-CSDN博客GitHub - WeiXiao…

gitee SSH 公钥设置教程

Gitee 提供了基于 SSH 协议的 Git 服务,在使用 SSH 协议访问仓库仓库之前,需要先配置好账户 SSH 公钥。 1、生成秘钥 Windows 用户建议使用 Windows PowerShell 或者 Git Bash,在 命令提示符 下无 cat 和 ls 命令。 ssh-keygen -t ed25519 -C "Gitee SSH Key"中间…

jenkins war Windows安装

Windows安装Jenkins 需求1.下载jenkins.war2.编写快速运行脚本3.启动Jenkins4.Jenkins使用 需求 1.支持在Windows下便捷运行Jenkins&#xff1b; 2.支持自定义启动参数&#xff1b; 3.有快速运行的脚步样板。 1.下载jenkins.war Jenkins下载地址&#xff1a;https://get.j…

string类详解(上)

文章目录 目录1. STL简介1.1 什么是STL1.2 STL的版本1.3 STL的六大组件 2. 为什么学习string类3. 标准库中的string类3.1 string类3.2 string类的常用接口说明 目录 STL简介为什么学习string类标准库中的string类string类的模拟实现现代版写法的String类写时拷贝 1. STL简介 …

[数据结构]红黑树,详细图解插入

目录 一、红黑树的概念 二、红黑树的性质 三、红黑树节点的定义 四、红黑树的插入&#xff08;步骤&#xff09; 1.为什么新插入的节点必须给红色&#xff1f; 2、插入红色节点后&#xff0c;判定红黑树性质是否被破坏 五、插入出现连续红节点情况分析图解&#xff08;看…

java练习(28)

ps&#xff1a;练习来自力扣 给定一个二叉树&#xff0c;判断它是否是平衡二叉树 // 定义二叉树节点类 class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}TreeNode(int val) { this.val val; }TreeNode(int val, TreeNode left, TreeNode right) {this.va…

Java并发编程6--重排序

重排序是指 编译 器和 处 理器 为 了 优 化程序性能而 对 指令序列 进 行重新排序的一种手段。 1.数据依赖性 如果两个操作 访问 同一个 变 量&#xff0c;且 这 两个操作中有一个 为 写操作&#xff0c;此 时这 两个操作之 间就存在数据 依赖性。 数据依赖的类型 上面 3 种情…

ElasticSearch映射分词

目录 弃用Type why 映射 查询 mapping of index 创建 index with mapping 添加 field with mapping 数据迁移 1.新建 一个 index with correct mapping 2.数据迁移 reindex data into that index 分词 POST _analyze 自定义词库 ik分词器 circuit_breaking_excep…

Python 面向对象的三大特征

前言&#xff1a;本篇讲解面向对象的三大特征&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff0c;还有比较细致的&#xff08;类属性类方法&#xff0c;静态方法&#xff09;&#xff0c;分步骤讲解&#xff0c;比较适合理清楚三大特征的思路 面向对象的…

deepseek多列数据对比,联想到excel的高级筛选功能

目录 1 业务背景 ​2 deepseek提示词输入 ​3 联想分析 4 EXCEL高级搜索 1 业务背景 系统上线的时候经常会遇到一个问题&#xff0c;系统导入的数据和线下的EXCEL数据是否一致&#xff0c;如果不一致&#xff0c;如何快速找到差异值&#xff0c;原来脑海第一反应就是使用公…

俄罗斯方块游戏完整代码示例

以下是一个基于Cocos Creator引擎开发的俄罗斯方块游戏的完整代码示例。该游戏实现了俄罗斯方块的基本功能&#xff0c;并且代码整合在单个文件中&#xff0c;无需任何外部依赖&#xff0c;可以直接在浏览器中运行。 1. 创建Cocos Creator项目 首先&#xff0c;确保你已经安装了…

java后端开发day16--字符串(二)

&#xff08;以下内容全部来自上述课程&#xff09; 1.StringBuilder 因为StringBuilder是Java已经写好的类。 java在底层对他进行了一些特殊处理。 打印对象不是地址值而是属性值。 1.概述 StringBuilder可以看成是一个容器&#xff0c;创建之后里面的内容是可变的。 作用…

【AI实践】deepseek支持升级git

当前Windows 11 WSL的git是2.17&#xff0c;Android Studio提示需要升级到2.19版本 网上找到指导文章 安装git 2.19.2 cd /usr/src wget https://www.kernel.org/pub/software/scm/git/git-2.19.2.tar.gz tar xzf git-2.19.2.tar.gz cd git-2.19.2 make prefix/usr/l…

从零复现R1之路[3/3]:一文速览Open R1——对DeepSeek R1训练流程前两个阶段的复现(SFT和GRPO训练)

前言 根据R1的GitHub可知 类别开源内容未开源内容模型权重R1、R1-Zero 及蒸馏模型权重&#xff08;MIT 协议&#xff09;原始训练数据 未公开冷启动数据、RL 训练数据集或合成数据的具体内容&#xff0c;仅提供依赖的公开数据集名称&#xff08;如 AI-MO、NuminaMath-TIR&…