【Linux】进程创建进程终止进程等待

news2024/12/25 9:06:13

目录

    • 一、进程创建
      • 1.1 写时拷贝
      • 1.2 frok的常规用法
      • 1.3 fork调用失败的原因
    • 二、进程终止
      • 2.1 进程退出码
      • 2.2 进程退出方式
        • 2.2.1 exit函数的使用
        • 2.2.2 _exit函数的使用
        • 2.2.3 exit函数与_exit函数的区别
      • 2.3 进程信号
    • 三、进程等待
      • 3.1 进程等待的必要性
      • 3.2 进程等待的方式
        • 3.2.1 wait函数
        • 3.2.2 waitpid函数
      • 3.3 获取子进程的状态信息(status)
      • 3.4 阻塞等待
      • 3.5 非阻塞等待
      • 3.6 wait与waitpid的区别


一、进程创建

关于进程创建这个话题其实在之前我们就已经讲的差不多了,利用fork函数从已存在进程中创建一个新进程,新进程为子进程,而原进程为父进程。返回值:自进程中返回0,父进程返回子进程id,出错返回-1。

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

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中
  • fork返回,开始调度器调度

在这里插入图片描述

fork之前父进程独立执行,fork之后,父子两个执行流分别执行:

在这里插入图片描述

1.1 写时拷贝

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

在这里插入图片描述

1.2 frok的常规用法

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

1.3 fork调用失败的原因

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

关于进程的创建使用fork函数我在之前的文章中已经全部讲到,这里就不花太多时间进行讲解了。

二、进程终止

在这里插入图片描述

下面我们就来探究一下进程终止正常执行完成与进程异常的情况,我们的重点肯定是要探究结果不正确的原因(因为结果正确了就代表我们一定程度上就完成了某项任务),而结果不正确又可以通过一些方式来进行判断,所以接下来我们将来谈谈退出码。

2.1 进程退出码

进程退出码(exit code)是一个整数,用于表示一个进程在执行完成后的状态。当一个进程完成执行并退出时,它会返回一个退出码,该退出码通常是一个非零整数或者是0。一个非零的退出码通常表示进程执行过程中出现了错误或异常情况,而0则通常表示进程执行成功。进程退出码的范围通常是0到255之间的整数。

我们平时最常见的int main() {return 0;} 退出码为0,我们一般默认这个进程执行成功,而!0表示进程执行过程中出现了错误或异常情况,我们可以知道!0是有很多种情况的,具体的退出码含义因操作系统和应用程序而异。通常,每个应用程序会定义自己的退出码,并在文档中提供退出码的含义说明,例如:1表示找不到该文件 2表示进程创建失败… 每个退出码都对应着解释说明此时进程的执行结果,这也是为了方便后续进程出现某种错误时我们能够快速查找原因!!

下面我们通过一个例子来讲讲这个退出码:

echo $?  # 查看最近一次指令的退出码

在这里插入图片描述

既然我们的退出码有很多中,下面我们就来见一见这些退出码对应的文档解释,我们将使用strerror这个库函数来进行展示,它能够将退出码转化成对应的退出码信息:

在这里插入图片描述

在这里插入图片描述

我们的退出码在不同的操作系统中可能数量、解释说明不一致,大家下来可以在不同的操作系统中去查看一下退出码;另外退出码对应的信息也有可能不会严格按照上面的标准打印信息,这跟具体的指令实现有关,我们自己也可以去定义这些退出码对应的信息。

2.2 进程退出方式

进程退出的方式我们在之前已经知道main函数return就是一个典型的进程退出方式,那么我想问其他函数return呢?它是否也代表进程退出?

相信这个答案应该是非常明了的,其他函数return仅仅代表该函数返回到调用该函数处,而main函数return则是真正的进程退出,这其实也说明进程执行本质其实是main执行流执行。

下面我们另外来介绍两种进程退出方式:

  • exit函数退出,exit(int status):status就代表进程的退出码,它其实就等价于main函数return xxx,但是它可以在代码的任意位置处调用都表示进程退出。
  • _exit函数退出
2.2.1 exit函数的使用

在这里插入图片描述

在这里插入图片描述

接着我们在其他位置试试去调用exit函数看看会出现什么现象:

在这里插入图片描述

结论:exit函数在代码的任意位置被调用都表示该进程退出!!


2.2.2 _exit函数的使用

在这里插入图片描述

_exit函数跟exit函数的使用方式一样:

在这里插入图片描述

2.2.3 exit函数与_exit函数的区别

我们在上述的例子中可以看到_exit函数与exit函数的使用方式一致,这是否说明它们两者是一样的呢?

在这里插入图片描述

其实exit函数内部封装了_exit函数, 但在调用exit之前,还做了其他工作:

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

在这里插入图片描述

平时我们也建议一般用exit来终止进程。

2.3 进程信号

当进程异常时,OS会向进程发送终止信号,我们可以通过kill -l查看所有的终止信号:

在这里插入图片描述

但是我们发现其中没有0信号,当我们未接收到操作系统发出的信号时此时就为0信号,代表进程正常执行,而这些!0信号就是进程异常时操作系统发出的,这些信号都是宏定义的,我们可以在signum.h中查看:

在这里插入图片描述

关于进程信号这里我们不做过多的讲解,我们只是见见它们到了进程信号我再给大家具体讲解每种信号表示的含义。

三、进程等待

3.1 进程等待的必要性

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

Q:为什么要存在进程等待?

  • 回收子进程资源,避免内存泄露
  • 获取子进程执行结果

在这里插入图片描述

Q:什么是进程等待?

通过系统调用获取子进程退出码或者信号的方式,顺便释放内存问题。

3.2 进程等待的方式

3.2.1 wait函数

在这里插入图片描述

wait函数的参数是输出型参数,用来获取子进程退出的状态。如果不关心子进程退出的状态,我们可以设置为 NULL。wait 函数的返回值:成功返回被等待进程的 ID,失败返回 -1。

在这里插入图片描述

while :; do ps axj | head -1 && ps ajx | grep mytest | grep -v grep; sleep 1; echo "------------"; done  
# 脚本代码,每隔1s监测一下mytest进程的状态

在这里插入图片描述

通过观察上述现象,我们发现wait函数确实解决了僵尸进程占用资源的问题,这里我们抛出一个问题:父进程在等待的时候,如果子进程没退出,那么父进程在干什么?

从上述图中我们观察到父进程使用wait函数,我们的父进程确实是一直是处于阻塞状态在等待子进程结束,子进程结束之后它才会执行相应的代码。那么有人会问了上述代码中在子进程结束之后使用了sleep休眠,所以我们的父进程就一直在等待在子进程结束之后它才会结束啊,这说的确实不无道理,但是你去掉sleep之后去检测一下,还是会出现相同的情况即:父进程一直处于阻塞状态等待子进程结束!!

对于这个问题的回答其实是不准确的,因为后面我们要讲的waitpid函数其实是可以通过参数来设置父进程在等待子进程时的状态的,这一点我们在后续会讲到,但是对于父进程调用wait函数来讲,父进程一直处于阻塞状态等待子进程结束这个观点是完全没问题的!!

下面通过这个例子相信大家会对wait函数有一个更深的理解:

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

Q:父进程使用wait函数等待子进程结束,将子进程的信息返回给父进程后才能执行父进程的代码吗?

是的,父进程使用wait()函数会等待其子进程退出后才会继续执行父进程的代码。wait()函数的作用是使父进程阻塞,直到一个子进程退出为止。当有一个子进程退出时,wait()函数会返回该子进程的进程状态信息。在父进程中调用wait()函数可以避免僵尸进程的产生,同时也可以确保父进程在子进程执行完毕后再继续执行自己的代码。

另外我们还需知道在父进程中调用wait()函数时,如果有多个子进程同时退出,wait()函数会返回其中一个子进程的进程状态信息,而不保证返回哪一个子进程的进程状态信息。如果需要获取所有子进程的退出状态,可以使用waitpid()函数或者wait()函数的循环调用来实现。

关于wait函数的使用我们暂且就讲到这里,因为后续的waitpid才是我们的重点,它比起wait函数更加的灵活、功能也更加丰富!!我们最后也还会讲到它们之间的区别。

3.2.2 waitpid函数

waitpid函数相较于wait更为复杂,但是也更加灵活、功能丰富。

在这里插入图片描述

我们先来看看waitpid函数的三个参数:pid参数指定要等待的子进程ID、status参数用于存储子进程退出状态的信息、options参数用于指定等待的行为。

下面对这三个参数进行详细解析:

  • pid参数:可以指定要等待的子进程ID。
    取值有以下几种:
    pid > 0:等待进程ID为pid的子进程结束。
    pid == 0:等待与当前进程在同一进程组中的任何子进程结束。
    pid == -1:等待任何子进程结束,相当于wait()函数。
    pid < -1:等待进程组ID为-pid的任何子进程结束。
  • status参数:是一个指向整型变量的指针,用于存储子进程的退出状态信息,其中包括退出状态和终止信号等信息。如果不关心退出状态,可以将该参数设置为NULL。
  • options参数:是一个整型值,用于指定等待的行为。
    可以使用以下选项:
    WNOHANG:如果没有已经结束的子进程,则立即返回0而不是等待。
    WUNTRACED:如果子进程暂停了,也会返回状态信息。
    WCONTINUED:如果子进程被继续了,则返回状态信息。
    如果不需要使用任何选项,则可以将该参数设置为0。

waitpid()函数的返回值:

waitpid()函数的返回值为已结束的子进程ID,如果没有已结束的子进程,且没有使用WNOHANG选项,则当前进程会被挂起,直到有一个子进程结束为止。

需要注意的是,waitpid()函数可以多次调用,以等待多个子进程的结束。每次调用可以等待一个指定的子进程,或者等待任意一个子进程。

3.3 获取子进程的状态信息(status)

我们先简单的使用一下waitpid函数来获取子进程的status状态信息:

在这里插入图片描述

我们的status其实主要存储着退出状态和进程信号等信息,所以我们此时看到的结果肯定不可能只是一个退出码的结果。对于status这个整数,我们不能将它看做一个完整的整数,而应该看做位图,位图是一种用一个或多个bit位来表示某种状态的一种数据结构,换句话说其实status它其实是按照区域来划分的一个数据。

status的高16位全为0,因为我们的退出状态一般为0~255用一个字节即可表示,而进程信号只有64种使用一个字节完全就能表示了,所以我们的低16位就用来表示退出状态和进程信号,当然其实还有一个bit位是core dump标志,实际上进程信号是用低7位来进行表示的。

在这里插入图片描述

我们来看看status使用运算符提取出退出状态和进程信号的方式:

在这里插入图片描述

但实际上我们通常是使用两个宏函数来获取子进程的status状态信息:

WEXITSTATUS()宏可以从waitpid()返回的状态信息中提取子进程的退出状态
WIFEXITED()宏可以检查子进程是否正常退出。

在这里插入图片描述

在获取到子进程的退出状态和进程信号后,我们就能对子进程的执行结果进行判断了,下面是关于退出状态与进程信号之间的关系。

在这里插入图片描述

结论:本质上进程信号决定了退出状态是否有意义!!


下面我们来看看进程异常的情况:

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

在这里插入图片描述

通过上图查看我们的status就能快速判断出进程的问题所在了。

我们知道父进程可以使用waitpid等待子进程结束去获取子进程的状态信息,那么这个过程是怎么样的呢?

在这里插入图片描述

3.4 阻塞等待

我们继续来谈前面抛出的问题:父进程在等待的时候,如果子进程没退出,那么父进程在干什么?

在这里插入图片描述

阻塞等待:子进程没有退出前,父进程一直在等待子进程退出,不执行其他代码,这种等待就是阻塞等待。

3.5 非阻塞等待

非阻塞等待:就算子进程没退出,父进程也不会一直等待子进程退出,期间父进程会去执行它的代码,过一段时间再来看子进程是否退出,这种等待就是非阻塞等待。父进程每次检查子进程是否退出都是一次非阻塞等待,多次非阻塞等待就是轮询的过程。

下面我们先来通过一个小故事感性理解一下阻塞等待和非阻塞等待:

小库和阿耶莎是一对情侣,这一天他们约好了在某个地方会合下午4点去xxx饭店吃饭,小库到了集合地点之后给阿耶莎打电话问他到了没,阿耶莎说再等等她她马上就好了,于是小库没挂断就一直站在那儿静静等待着阿耶莎的到来。另外一种场景是小库到达集合地点给阿耶莎打电话问他到了没,阿耶莎说还没呢你再等等,于是小库就挂断电话,想着阿耶莎还没到我可以先给她点一杯奶茶,在她到达时看见我手中的奶茶,想必我们关系更能加深一步~在点完奶茶之后,小库再次打电话给阿耶莎问她到了没,此时阿耶莎说还没呢路上出了一点小问题你再等等吧,于是小库又挂断了电话,他想阿耶莎还没来我可以先买束花在这里等着她,到时候阿耶莎看到之后应该会非常开心吧,小库不愧是舔中之王啊~ 在买完花之后,小库再次给阿耶莎打了一个电话,阿耶莎说她到了,于是小库拿着花返回集合地点。

上述故事中小库就对应父进程,阿耶莎就对应子进程,打电话就相当于系统调用waitpid,阻塞等待就是第一种情形:小库一直没挂电话等待阿耶莎到达目的地;第二种情形:小库在等待阿耶莎期间去做其他的事情就是非阻塞等待,打电话给他然后挂断电话本质上就对阿耶莎做了一次状态检测!小库打了很多次电话询问阿耶莎就是做了很多次检测,这个过程叫做非阻塞轮询。

下面我们就来模拟一下非阻塞等待的情形:

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,父进程不予以等待,可以执行它相应的任务。若正常结束,则返回该子进程的ID。

在这里插入图片描述

在这里插入图片描述

3.6 wait与waitpid的区别

从本质上讲,wait和waitpid的作用是完全相同的都是用于等待子进程结束的系统调用,但是waitpid多了两个参数,从而使我们的编程能更加的灵活。

  • 等待的进程不同:wait() 等待任意一个子进程结束,而 waitpid() 可以指定等待某个特定的子进程结束。
  • 非阻塞等待:在 wait() 中,父进程会一直等待,直到任意一个子进程结束,如果没有子进程结束,则父进程会一直阻塞。而 waitpid() 可以通过 WNOHANG 选项实现非阻塞等待,即如果指定的子进程还没有结束,则立即返回,而不会阻塞等待。
  • 灵活性:由于 waitpid() 可以指定等待某个特定的子进程结束,并且可以进行非阻塞等待,因此它比 wait() 更灵活,更适合需要精细控制的情况。

总的来说,wait() 和 waitpid() 的主要区别在于等待的进程、参数和阻塞等待的方式。根据具体的情况选择使用哪个函数会更加合适。


本篇文章的内容就到这里了,如果有任何疑问或者错处欢迎大家评论区相互交流orz~🙈🙈

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

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

相关文章

从零开始的MicroPython(一) 软件安装及环境搭建

文章目录 MicroPython简介下载安装 ESP32(NodeMCU-32S)简介引脚注意事项 CH340下载安装 Thonny IDE下载 Python简介下载环境配置 MicroPython 简介 ​ MicroPython 是 Python 3 编程语言的精简高效的实现 其中包括 Python 标准库的一小部分&#xff0c;并且是经过优化&#x…

达梦数据库系列—40.执行计划

目录 优化器 执行计划 操作符 执行过程 优化器 查询优化器通过分析可用的执行方式和查询所涉及的对象统计信息来生成最优的执行计划。此外&#xff0c;如果存在 HINT 优化提示&#xff0c;优化器还需要考虑优化提示的因素。 查询优化器的处理过程包括&#xff1a; 1.优化…

手摸手教你撕碎西门子S7通讯协议14--开发自己的通讯库读数据

1、S7通讯回顾 - &#xff08;1&#xff09;建立TCP连接 Socket.Connect- - &#xff08;2&#xff09;发送访问请求 COTP- - &#xff08;3&#xff09;交换通信信息 Setup Communication- - &#xff08;4&#xff09;执行相关操作 读、写、PLC启停、时间…

【Android】DrawerLayout+NavigationView实现侧滑菜单页面

【Android】DrawerLayoutNavigationView实现侧滑菜单页面 在 Android 开发中&#xff0c;侧滑菜单是一个非常常见的用户界面模式&#xff0c;它能够在屏幕的一侧显示一个导航菜单&#xff0c;允许用户通过滑动手势或点击按钮来访问不同的应用功能。本文将介绍如何使用 DrawerL…

网页UI设计工具全攻略:九大精选

如果担心不知道如何进行网站 UI 设计、设计网站和编辑网页技术程序&#xff0c;很多人会选择快速方便的 Wix 建设。然而&#xff0c;如果你想建立一个最合适的网站&#xff0c;使用一个功能强大、资源丰富的网站 UI 设计工具仍然是您的最佳选择。网站设计中的 UI 设计不同于一般…

你是否知道Vue的data两种不同定义区别呢?

在做vue项目的时候&#xff0c;虽然vue3出来了一段时间了&#xff0c;vue2已经官方宣布不再维护了&#xff0c;然而我们有些旧项目原来是用的vue2的&#xff0c;那么用了那么久的vue2&#xff0c;不知道你是否有注意到&#xff0c;vue2我们往往会在根文件定义了一个对象形式的d…

类似redmine的项目管理系统有哪些?10款软件测评

国内外主流的10款类似redmine项目管理系统对比&#xff1a;PingCode、Worktile、TAPD、OpenProj、禅道&#xff08;ZenTao&#xff09;、Teambition、JIRA、Asana、Basecamp、Wrike。 在项目管理领域&#xff0c;选择一个既能满足需求又易于操作的工具是每个团队都面临的挑战。…

利用SOLIDWORKS CAD 2024新功能 提高团队工作效率

随着科技的不断发展&#xff0c;CAD&#xff08;计算机辅助设计&#xff09;软件在各行业中的应用越来越广泛&#xff0c;尤其在机械、汽车、航空航天、电子设备等领域。SOLIDWORKS作为一款功能强大的CAD软件&#xff0c;一直在不断更新和优化&#xff0c;以适应不断变化的市场…

【区块链】控制台的配置、操作及常用命令②

常用命令-账户管理 常用命令-区块信息 在控制台中编译部署智能合约 启动节点 在fisco目录下 bash nodes/127.0.0.1/start_all.sh启动控制台 cd ~/fisco/console && bash start.sh部署合约 deploy HelloWorldtransaction hash: 交易的哈希值 contract address&#x…

plugin ‘ROS2‘: loading...error CoppeliaSim和ROS2插件问题

问题 装了24年最新版本ROS2 Jazzy但是仿真软件打开出bug&#xff0c;怎么办&#xff1f; 等支持的出来&#xff0c;完全可以。但是&#xff0c;如果需要用&#xff0c;那调整一下即可。 CoppeliaSim&#xff08;V-Rep&#xff09;和ROS2的使用说明_coppeliasim编译-CSDN博客…

【网络】HTTP协议——应用层协议、URL、HTTP协议格式、HTTP的方法、HTTP的状态码、HTTP常见Header

文章目录 Linux网络1. 应用层2. HTTP协议2.1 URL2.2 urlencode和urdecode2.3 HTTP协议格式2.4 HTTP的方法2.5 HTTP的状态码2.6 HTTP常见Header Linux网络 1. 应用层 应用层是 OSI 七层模型或 TCP/IP 四层模型中的最高层&#xff0c;它直接为用户的应用程序提供服务。 应用层的…

MySQL数据库-SQL编程

一、触发器 1.触发器简介 触发器&#xff08;trigger&#xff09;是一个特殊的存储过程&#xff0c;它的执行不是由程序调用&#xff0c;也不是手工启动&#xff0c;而是由事件来触发&#xff0c;比如当对一个表进行操作&#xff08; insert&#xff0c;delete&#xff0c; u…

【C++标准库】模拟实现string类

模拟实现string类 一.命名空间与类成员变量二.构造函数1.无参&#xff08;默认&#xff09;构造2.有参构造3.兼容无参和有参构造4.拷贝构造1.传统写法2.现代写法 三.析构函数四.string类对象的容量操作1.size2.capacity3.clear4.empty5.reserve6.resize 五.string类对象的访问及…

傅里叶变换与FFT应用

一、傅里叶变换 1.1 变换 我们先给例子&#xff0c;假设在直角坐标系上有A(2,1),B(1,2);数和图之间存在的关系&#xff0c;称作变换&#xff1b;在图上我们想找对角线C&#xff0c;通过计算我们就知道C(3,3)&#xff1b;我们知道&#xff0c;在坐标系上有单位向量&#xff0c;…

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3(含分析过程)

Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3&#xff08;含分析过程&#xff09; 目录 Python 【机器学习】 进阶 之 【实战案例】房价数据中位数分析 之 [ 选择并训练模型 ] [ 模型微调 ] | 3/3&#xff08;含分析…

Vue使用flex将图片并排居中且左对齐

先看效果&#xff1a; 在看代码 <template><div class"outer"><div class"inner"><div classeach_image v-for"(item,index) in image_list" :key"index"><img class"image_class" src"./…

培训孵化公司必备ERP的系统,跟卖和铺货以及订单物流发货打包

培训孵化必备的 ERP 系统&#xff0c;贴牌定制独立部署&#xff0c;跟卖铺货&#xff0c;物流对接。 说说新手与培训孵化学员如何使用 ERP&#xff01; 1. ERP 系统对于新手来说非常简单且容易操作&#xff0c;上面的跟卖功能很全面。比如铺货方面&#xff0c;可以采集 1688、…

发现SOLIDWORKS设计活页夹

您有没有遇到过将模型文件转交给同事时丢失附件的文档信息的&#xff1f;您有没有遇到过您的业务同事使用您的模型时仍然搞不清模型和业务项目之间的关系&#xff1f; 在纸制图纸的“旧时代”中&#xff0c;会有一整套信息&#xff08;文档或者表格&#xff09;与模型和图纸一…

电路原理--基础电路工具

1.正弦信号激励下的动态电路分析法-----频域相量法 课本第六章269页。 2.阻抗 3.滤波器简单理解 先介绍下滤波&#xff0c;芯片和元器件在相互工作的时候&#xff0c;会相互影响&#xff0c;在线路上产生寄生电阻或者寄生电容&#xff0c;这种现象叫耦合&#xff0c;耦合会带…