进程的创建、终止

目录

  • 前言
  • 1. 进程创建
  • 2. 进程终止
  • 3. exit && _exit 的异同
    • 3.1 相同点
    • 3.2 不同点

前言

紧接着进程地址空间之后,我们这篇文章开始谈论进程控制相关的内容,其中包括进程是如何创建的,进程终止的几种情况,以及进程异常终止的本质,还有 C 语言库中的 strerror 以及 errno 全局变量的相关内容,最后对比系统调用 _exit 与 C 库的 exit 的异同点。


1. 进程创建

在之前文章 进程概念(三)----- fork 初识,我们初始了 fork(),大致了解了一个进程是怎么被创建出来的,不管是我们的程序运行起来后,操作系统为我们自动创建的,还是代码层面我们手动创建的进程,都是通过 fork() 实现的,也回答了与 fork 相关的几个内容,为什么要有两个返回值(为了让父子进程分流工作);如何做到返回两次的(在return之前,子进程就已经被创建完成,因此父子进程都执行了一遍return语句)以及 一个变量如何做到两个不同的内容的(写诗拷贝实现的)。

有了之前的 fork 初始,以及进程地址空间,页表等铺垫,我们现在就足以理解一个进程是如何被创建的了。

创建进程,最终都离不开 fork() 函数,当一个进程调用 fork 之后,操作系统就会做以下几件事:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程(包括进程地址空间,页表都会拷贝一份给子进程)
  • 添加子进程到系统进程列表当中(本质就是讲子进程的 PCB 链入到 cpu 的运行队列中)
  • fork返回,开始调度器调度

所以 fork 之后,父子进程就开始共享代码,但数据却不一定是共享的,也就是可能发生写诗拷贝。
在这里插入图片描述
关于写诗拷贝,再谈一个细节。在创建子进程时,当子进程开始要拷贝父进程的进程地址空间,页表的时候,父进程会把页表中的所有读写权限的字段暂时设置为只读(原本只读的字段保持不变),然后再让子进程开始继承其进程地址空间和页表。而后续当其中一个进程开始对这些只读字段的地址进行写入时(操作系统能够识别到原本属于读写权限的),操作系统不做异常处理,它会重新开辟一块内存空间,将要写入的数据拷贝一份到新地址中,然后修改这个进程的页表中相应字段的物理地址,再把这个字段恢复为读写权限,这就完成了所谓的 写时拷贝

而既然有写时拷贝,那就注定在技术层面上可以不要写时拷贝,在创建子进程的时候,直接一次性的将父进程的全部数据拷贝一份给子进程不就完了吗,还省事!

其实这样做反而 “不省事” ! ------>

  • 可能导致大量重复的数据在内存中,造成资源浪费
  • 拷贝量可能很大,导致整机性能下降(虽然之后写时拷贝的时候也要拷贝,但拷贝量注定不会很大,效率是不会降低的)

而在而来 fork 可能会出现调用失败的原因:1. 系统中有太多的进程; 2. 实际用户的进程数超过了限制

代码层面上想要创建多个子进程,可以借助循环创建。demo用例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>#define N 5void runChild()
{int cnt = 10;while(cnt){printf("I am child: %d, ppid:%d\n", getpid(), getppid());sleep(1);cnt--;}
}int main()
{int i = 0;for(; i < N; i++){pid_t id = fork();if(id == 0){runChild();exit(0);} }sleep(100);return 0;
}

关于父子进程到底谁先运行这件事,是具体某一款操作系统的调度器怎么设计决定的,并没有唯一的标准,而一定要说哪个进程先运行,因为创建出来的子进程的优先级默认都是一样的,只能是谁先被调度器调度,放在 cpu 的运行队列中,谁就先被运行。


2. 进程终止

一个进程的终止无非就是以下这三种情况:

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

而我们应该关注的是后面两种情况,代码运行完毕且结果正确我们一般不关心(就好比考试完毕且成绩达到预期,我们也不会去关心),但是第二种情况,代码运行完毕且结果不正确,我们肯定要关心吧,总得知道为什么不正确吧,包括代码异常终止。

int main()
{printf("this is a test!\n");return 0; 	// return 0 的 0 就是进程的退出码,表征进程的运行结果是否正确. 0->success
}

在这里插入图片描述

echo $? 其中的 $? 代表保存最近一次进程退出时的退出码,而上面我们执行了 test 这个可执行程序,该进程执行完毕后返回了一个 0,代表这这个进程执行完毕,并且结果正确。而如果我们代码中不是 retrun 0,而是 return 1/2/3,那么echo $? 查出来的结果也不一样的。换言之,可以用 return 不同的返回值数字,表征不同的出错原因,这些数字就称为退出码。 而 main 函数返回值的本质就是:进程运行完成时是否是正确的结果,如果不是,可以用不同的退出码表示的出错原因。

那进程中,谁会关心我这个进程的运行结果呢?? ----- 该进程的父进程

在这里插入图片描述

当我们 main 函数中 return 2 ,那我们 echo $? 查出来的退出码就是 2,但是之后我继续执行 echo $? 就不再是 2 了,而是 0,这是因为第一条 echo 命令执行完是正确的,没有发生错误,所以接下来的 echo $? 打印的就是最近一次进程退出时的退出码(echo执行起来时,本身就是一个进程)。

但是,退出码都是些 0 1 2 3 4 的纯数字,理解起来也太抽象了吧,所以退出码只是给计算机自己看的,在用户层,就需要将这些特定含义的数字转换成对应错误原因描述的字符串信息,方便我们观看使用。

而在 C 预言中,就有一个查看错误码的库函数 strerror,并且我们可以把这个函数的信息打印出来看一下。

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

当我们 ls 查看目录中不存在的文件名,它的错误信息 与 上面我们打印出来的 strerror 函数中的内容是匹配的上的!No such file or directory 这条错误信息的退出码正好是 2 。换言之,系统提供的错误码和错误码描述是有对应关系的。

但为什么父进程会关心子进程的执行结果呢??其实父进程它只是一个跑腿的,可以理解为是因为它有义务要收集子进程的执行结果,并且向上反馈,而不是它真的想关心子进程的执行结果。换言之,真正关心一个进程的执行结果的人一定是用户。就好比我们上面 ls 查看当前目录不存在的文件,执行失败了,用户才会根据执行失败的错误信息,调整自己执行程序的方式!

那为什么要创建子进程?因为有需求,不就是因为用户要创建子进程出来干活吗。所以将来这个子进程执行某个任务的结果,我作为用户,我如何得知!? ------ 通过父进程将进程的退出信息转交给用户,让用户根据错误信息做下一阶段的执行决策

接下来,我们可以来看看退出码的应用场景。在 C 库中提供了 errno 全局变量,在 C 语言中,我们可能会调用一些库函数,比如 malloc,fopen,这些库函数在调用时,内部都是有可能会执行错误的,而这个 errno 就是记录最后一次执行的错误码。要记录错误码的就是因为只要执行错误,那用户就有需求要了解错误信息是什么。

int main()
{int ret = 0;char* p = (char*)malloc(1000*1000*1000*4);if(p == NULL){printf("malloc error, %d: %s\n", errno, strerror(errno));ret = errno;}else{printf("malloc success, %d: %s\n", errno, strerror(errno));}return ret;
}

在这里插入图片描述

当我们 malloc 申请很大的内存时,极大困难就是 12 的错误码:Cannot allocate memory。而这个错误码是 C 语言提供的 errno 这样的全局变量记录下来的,结合 strerror 根据错误码查询具体错误信息得来的!

截止现在,我们还没有讲进程终止的第三种情况:代码异常中止, 现在的问题是,代码异常中止了,main 函数的退出码还有意义吗??

前面两种进程终止的情况都好说,因为起码代码执行完了,我们可以通过退出码来判定是否执行正确。

  • 而代码异常,是不是可以理解为代码没跑完,进程就已经退出了,根本就没有执行 main 函数的 return 语句。
  • 但是会不会也有可能,执行完 main 函数的 return 语句之后,才发生的异常中止的呢?好像也有可能?毕竟 main 函数也是函数,它也会被调用。return 之后,在语言层面上认为这个程序结束了,但在系统层面上,这个进程不一定退出了!

所以现在的关键是,作为用户的我,我该怎么知道,这个程序到底有没有执行最后的 return 语句呢!?就是因为我们对代码异常在哪个位置的不确定性,假设今天真的是 return 之后才异常的,退出码给返回到了父进程,进而转交给用户,作为用户的你,你真的敢用吗?换言之,你敢信吗??凭什么你就敢确定这个程序真的执行了 return 语句呢?

作为用户的我们,不能百分百确定确定在哪一行代码发生的异常,所以即便真的有退出码,这个退出码照样没有意义!(因为我们不敢相信。就好比考试的时候,你作弊了,但是你跟老师解释说是考试即将结束作弊的,你只抄了最后一道题,老师会信吗??换言之,只要你作弊了,你何时作弊,对于老师来说已经不重要了,没有意义了!你的话也不再可信,他只知道你作弊了!再者,难道你只抄了最后一题,最后100分的试卷考了95分,去掉最后的5分,你也还有90分,是年级百强选手,难道学校管理层还要把你加入百优表彰宣传单里面吗?然后同时再贴出一张违纪通知书:上面写着你的名字??你觉得这件事合理吗??换言之,成绩 与 作弊 这两件事,只能有一件事存在!同时存在就毫无意义!!同理,代码异常中止了也如此,只要中止了,那么退出码将毫无意义!!)


所以当进程异常终止退出时,我们也就不关心退出码了,但是我们应该要关心进程为什么异常了,以及进程发生了什么异常。

当我们对空指针进行解引用访问时,会出现 Segmentation fault (core dumped) 这样的错误信息;当我们进行除 0 运算时,会有 Floating point exception (core dumped) 。而类似这样的代码异常中止的情况,本质就是进程收到了对应的信号!

[outlier@localhost process3]$ kill -l1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	kill -l 可以查看给进程发送的信号,其中的 8 号信号,SIGFPE == Floating point exception
11 号信号就是我们常见的段错误 SIGSEGV == Segmentation fault

那么如何验证所谓的进程异常中止就是收到了某种信号呢??

当进程跑起来之后,我们尝试对进程发送 8 号信号 和 11 号信息,并观察现象

在这里插入图片描述


3. exit && _exit 的异同

  • exit 是 C 语言的库函数
  • _exit 是 linux 系统的系统调用

3.1 相同点

在不涉及缓冲区刷新的问题,exit 和 _exit 的作用都是一样的,都是用于进程退出的函数,与 return 不同的是,return 更多的代表函数的返回,当 main 函数调用一个函数,使用 return 之后,程序会回到调用处继续执行后续代码,而 exit / _eixt 则是直接进程退出了,不会再执行 exit / _eixt 之后的代码。

3.2 不同点

int main()
{printf("hello, linux!");sleep(1);exit(12);
}

在这里插入图片描述

int main()
{printf("hello, linux!");sleep(1);exit(12);
}

在这里插入图片描述

当调用的是 C 库的 exit,那么在退出进程时,会刷新缓冲区,关闭文件流等各种流,而在掉系统调用 _exit 则不会刷新缓冲区,这也是为什么在 _exit 之后,本该打印输出的信息并没有打印在屏幕上,因为 printf 一定是先把数据写入缓冲区中,达到某种界限时,在进行刷新!

在这里插入图片描述

程序中调用 _exit,直接在进程层面上终止进程; C 库当中的 exit 会先把应用层中打开的各种流关闭 以及 刷新缓冲区等操作,再调 _exit 终止进程。 可以理解为 exit 就是对 _exit 作了一定的封装!

那么我们需要知道,这个缓冲区一定不在哪里?? ---- 一定不在内核中!

为什么这么说呢,因为如果这个缓冲区在内核中,那么内核就一定需要维护这个缓冲区,只要是维护,就会有刷新,就不存在上面的现象了。

缓冲区在哪的问题,在后续关于 IO 方面的文章会介绍。


关于进程创建、进程终止等话题暂且谈论至此,后续还会讲进程控制中的进程等待。

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

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

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

相关文章

数学建模学习(115):主成分分析(PCA)与Python实践

文章目录 一.主成分分析简介1.1 数学背景与维度诅咒1.2 PCA的定义与应用二.协方差矩阵——特征值和特征向量三.如何为数据集选择主成分数量四.特征提取方法五.LDA——与PCA的区别六.PCA的应用七.PCA在异常检测中的应用八.总结一.主成分分析简介 1.1 数学背景与维度诅咒 主成成…

TOP10漏洞原理

## 本人为学习网安不久的新人&#xff0c;记一次学习笔记&#xff0c;有缺陷或者表述不对的地方欢迎大家指出&#xff0c;感谢&#xff01; ## 1、sql注入&#xff1a;web应用程序对用户输入的数据没有进行过滤&#xff0c;或者过滤不严&#xff0c;就把sql语句拼接进数据库…

Mac电脑遇到DNS解析失败,ip可以访问,域名无法访问

当Mac电脑遇到DNS解析失败的问题时&#xff0c;可以尝试以下几个解决方法‌&#xff1a; 1.检查网络连接‌&#xff1a;确保Mac已连接到可用的网络&#xff0c;并且网络连接正常。可以尝试重新连接Wi-Fi或使用有线连接来排除网络问题。 2.清除DNS缓存‌&#xff1a;打开终端应…

docker容器基本命令、docker进入容器的指令、容器的备份、镜像底层原理、使用commit命令制造镜像、将镜像推送到阿里云镜像仓库与私服仓库

除了exit 还有 ctrlpq exit退出停止 ctrlpq 退出不停止 将本地镜像推到阿里云 登入阿里云 容器镜像服务 实力列表 镜像仓库 创建镜像仓库 安装里面步骤来 这里192.168.10.145这部分用自己ifconfig地址

【Android 远程数据库操作】

按正常情况下&#xff0c;前端不应该直接进行远程数据库操作&#xff0c;这不是一个明智的方式&#xff0c;应该是后端提供对应接口来处理&#xff0c;奈何公司各方面原因需要前端这样做。 对此&#xff0c;我对远程数据库操作做了总结&#xff0c;便于自己复盘&#xff0c;同…

python绘制爱心代码

效果展示 完整代码 Python中绘制爱心的代码可以通过多种方式实现&#xff0c;高级的爱心代码通常指的是使用较复杂的算法或者图形库来生成更加精致的爱心图形。下面是一个使用Python的Turtle模块来绘制爱心的示例代码&#xff1a; import turtledef draw_love():turtle.speed…

[Other]-安装ruby、ascli、ascp

最近新接到这样一个需求&#xff0c;将生物原始数据上传到某中心&#xff0c;其中用到ascp命令&#xff0c;阴差阳错的装了ruby、ascli&#xff0c;这里就都一并介绍下安装方式&#xff0c;由于服务器老旧默认安装时ruby2.0&#xff0c;又 升级到2.7等引发的一系列问题&#xf…

XSS-DOM

文章目录 源码SVG标签Dom-Clobbringtostring 源码 <script>const data decodeURIComponent(location.hash.substr(1));;const root document.createElement(div);root.innerHTML data;// 这里模拟了XSS过滤的过程&#xff0c;方法是移除所有属性&#xff0c;sanitize…

AI工具革新:国内外设计艺术的融合

在人工智能的浪潮中&#xff0c;全球的创新者和开发者们推出了一系列令人惊叹的工具&#xff0c;它们正以前所未有的速度改变着我们的工作、学习和生活方式。从图像生成到语言处理&#xff0c;从数据分析到自动化设计&#xff0c;AI 作图工具展示了其强大的能力&#xff0c;帮助…

DRF——Filter条件搜索模块

文章目录 条件搜索自定义Filter第三方Filter内置Filter 条件搜索 如果某个API需要传递一些条件进行搜索&#xff0c;其实就在是URL后面通过GET传参即可&#xff0c;例如&#xff1a; /api/users?age19&category12在drf中也有相应组件可以支持条件搜索。 自定义Filter …

面试题详解

前言&#xff1a;这一期我们专门来巩固所学知识&#xff0c;同时见识一些面试题。对知识做出一个总结。 1 不创建临时变量交换两个整数 . 第一种方法 #include<stdio.h> int main() {int a 0;int b 0;scanf("%d %d", &a, &b);printf("交换前…

神经网络算法 - 一文搞懂BERT(基于Transformer的双向编码器)

本文将从BERT的本质、BERT的原理、BERT的应用三个方面&#xff0c;带您一文搞懂Bidirectional Encoder Representations from Transformers | BERT。 Google BERT BERT架构&#xff1a; 一种基于多层Transformer编码器的预训练语言模型&#xff0c;通过结合Tokenization、多种E…

Java基于数据库、乐观锁、悲观锁、Redis、Zookeeper分布式锁的简单案例实现(保姆级教程)

1. 分布式锁的定义 分布式锁是一种在分布式系统中用来协调多个进程或线程对共享资源进行访问的机制。它确保在分布式环境下&#xff0c;多个节点&#xff08;如不同的服务器或进程&#xff09;不会同时访问同一个共享资源&#xff0c;从而避免数据不一致、资源竞争等问题。 2…

等保测评服务的业务连续性规划:确保信息安全服务的韧性

在当前的数字化转型浪潮中&#xff0c;信息安全已成为企业运营的关键一环。等保测评服务作为信息安全合规的重要组成部分&#xff0c;其业务连续性规划对于保障服务的稳定性和客户信息资产的安全至关重要。本文将探讨等保测评服务的业务连续性规划策略&#xff0c;旨在构建一个…

树状数组算法

文章目录 树状数组是什么树状数组与线段树的区别与联系树状数组讲解点修&#xff0c;区查&#xff0c;讲解及模板点查&#xff0c;区修讲解及模板 树状数组是什么 树状数组是一种数据结构&#xff0c;提供O(logn)时间内的单点修改和区间求和操作&#xff0c;比线段树有更优的常…

MD编辑器学习笔记

MD编辑器学习笔记 目录标题文本样式列表图片链接代码片数学公式表格部分总结 目录 目录是使用“[TOC](目录&#xff09;”&#xff0c;记住别忘了加上&#xff08;&#xff09;标题 使用#来确定标题&#xff0c;几个#就是几级标题。记住#后面要加上空格文本样式 tips: 在写正…

物流抓取机器人整体设计方案

一、功能简介 1、运行环境&#xff1a;巡线行驶&#xff08;7路数字循迹&#xff0c;麦克纳姆轮车底盘&#xff09; 2、目标识别&#xff1a;颜色识别&#xff08;Maix-II Dock 视觉模块&#xff09; 3、目标定位&#xff1a;视觉测距&#xff08;Maix-II Dock 视觉模块&#x…

VS实⽤调试技巧(附调试例题)

&#x1f381;&#x1f381;创作不易&#xff0c;关注作者不迷路&#x1f380;&#x1f380; VS实⽤调试技巧&#xff08;附调试例题&#xff09; 前言一、什么是bug&#xff1f;二、什么是调试&#xff08;debug&#xff09;&#xff1f;三、Debug和Release四、VS调试快捷键五…

上书房信息咨询:商业项目调研方法有哪些

商业项目调研是为了了解市场需求、竞争情况和目标受众&#xff0c;从而制定合适的商业策略和项目规划。下面是一些常用的商业项目调研方法&#xff1a; 1、市场调查和问卷调查&#xff1a;通过设计和分发问卷&#xff0c;收集潜在顾客和目标市场的意见、偏好和需求。这可以帮助…

linux dig域名DNS 查询与iptables域名ip访问流量限制;PTR 反向解析从 IP 地址到域名的映射

一、域名 dns查询 在 Linux 系统中&#xff0c;你可以使用多种工具和技术来进行 DNS 查询和 IP 限制。以下是一些常用的方法和工具&#xff1a; DNS 查询 dig 命令&#xff1a; dig 是一个强大的命令行工具&#xff0c;用于查询 DNS 信息。 dig example.com你可以指定查询类型…