Linux操作系统——进程控制(一) 进程创建和进程终止

进程创建

fork函数

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

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

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

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

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。

int main( void )
{
    pid_t pid;
    printf("Before: pid is %d\n", getpid());
    if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
    printf("After:pid is %d, fork return %d\n", getpid(), pid);
    sleep(1);
    return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示:

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。

fork函数返回值

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

写时拷贝

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

当父进程创建子进程之后,子进程进行写入,会发生写时拷贝?重新申请空间,进行拷贝,修改页表,这部分工作都是由操作系统来做的。那么我想请问一下,你说发生写时拷贝就发生写时拷贝啊?你当前可是在发生写入啊,那么操作系统是怎么把握这个时机来完成写时拷贝的这个工作呢?

比如说你在家的时候,你说你饿了,你爸还没下班,你就准备动筷子吃饭了,你妈这个时候刚好叫住你说:"等一下,我先给你把打包一份你在吃",打包之后就说:"好了,现在你可以吃了。"就好比这样,但是呢,操作系统到底如何能做到什么时机应该完成写时拷贝呢?

其实,父进程创建子进程的时候首先把自己的读写权限改成只读,然后再创建子进程。这个工作我们作为用户是不知道的,那么用户就可能对某一批数据进行写入!而写入的时候由于权限是只读的,那么在我们的页表转换会因为权限问题而出错,一旦出错了,操作系统就可以介入了。出错的原因可能是真的出错了,也有可能不是出错,而是触发我们进行重新申请内存拷贝内容的策略机制。(写时拷贝)

fork常规用法

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

fork调用失败的原因

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

下面我们通过写一份多进程的代码:

运行结果:

进程终止

我们平常写的C语言代码都有一个main函数,而这个main函数有一个返回值,我们通常返回的是0,那么我们main函数也是函数,所以它也会被调用,那么main函数是被谁调用呢?这个0又是给谁进行返回呢?

那我们下面来谈一谈main函数的 返回值。

其实我们的main函数一般是被一个__StartCRT的函数给调用了,既然被调用了那么这个main函数返回值应该就是交给调用他的函数,其实调用他的函数也要将返回值继续向上进行传递。为什么呢?

下面我们先把main函数退出码设为10.

我们运行起来

当前代码什么工作都没做,但是这个可执行程序一运行起来变成一个进程,变成进程之后他的父进程就是bash,而这个进程的main函数的返回值最终会交给父进程。

下面我们通过该指令获取一些父进程收到的退出结果。

我们就得到了一个退出结果是10,这个退出结果就是刚刚main函数的返回结果。

这是我们发现的一个现象。

其实在我们的生活中平常做的事情的情况来分,你做任何一件事情情况无外乎就这三种:

  • 事情做完了,做的做的结果非常好。
  • 事情做完了,做的结果很不好。
  • 事情没做完,中间出问题出岔子。

比如说我们平常考试,第一,你把试考完了,但是考出来的结果不太好,第二种,你把试考完了,考出来的结果非常好,第三种,你考试没考完,中间出岔子了,比如说作弊被抓了,没考完。

同样在我们的程序当中也是从这个三种情况来看的:

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

举个简单例子,我们在跑一个冒泡排序的代码的时候,要么就是代码跑完了,最后排出来的结果是正确的,要么呢就是代码跑完了跑出来的结果不正确,还有一种呢就是跑到一般代码出异常了。

我们上面所谈的main函数的返回值目前只考虑前两种情况,也就是把代码异常终止的情况排除在外。我们代码运行完毕结果不正确还是结果正确,我们得知道这个结果。那么这里说的我们指的是谁呢?如果我们写了一个单进程的代码,比如说链表逆置的算法,或者一个什么算法最终的结果是需要我们自己去看的,如果我们在多进程环境中,我们创建子进程的最终目的是什么?最终目的是帮我办事。子进程把事情办得怎么样呢?这里的我们一般都是指的父进程。而我们的子进程把事情办得怎么样了是通过main函数的返回值来进行识别的,比如说返回值是0那就说明办成功了,而非0代表出错了,但是出错的原因有很多啊,具体是由于什么原因出错的,非0的数字有很多,0只有一个,这个时候就是通过不同的数字来代表不同的出错的原因的。但是呢一般用这些纯数字来表示出错原因对于人来讲不便于人阅读,所以就有了将exit code-> exit code string! 也就是将对应的退出码转换成人便于阅读的字符串,其实c语言内置的一个函数接口:

其实我们一开始学习的时候也不知道怎么来描述的,所以我们通过一段代码来进行测试:

运行结果:

上述的错误码就是我们可能出现的错误信息与错误码之间的对应关系。

我们发现到了134之后就是都不认识了。

根据不同的结果现象,返回不同的退出码,什么现象?

C语言有一个全局变量叫errno是C语言的错误码。我们来看一下errno的说明:

退出码vs错误码

错误码通常是用来衡量一个库函数或者是一个系统调用一个函数的调用情况

退出码通常是一个进程退出的时候,他的退出结果。

虽然这两个是不同的概念,但是他们都有共同的表征,那就是当失败的时候,用来衡量函数,进程出错时的详细出错原因。

下面我们编写如下代码将错误信息打印出来:

运行结果:

下面呢我们再从代码异常终止的情况来了解异常问题:

 下面我们用除0错误和野指针问题来进行说明:

运行结果:

我们可以看到出现了除0错误。

下面再用一个野指针问题

运行结果:

野指针问题,在Linux中叫做段错误。

如果我们的程序异常了,本质就是我们的进程异常了,进程出异常了,我们的操作系统为了不让该进程影响到别人通常会将这个异常的进程给杀掉,那么怎么杀掉呢?通过信号的方式:

我们刚刚的除0异常其实就是上图中的8号信号,而我们的段错误是上图中的11号信号。

上述我们说到的进程异常终止其实是因为进程收到了操作系统发送的信号导致的异常终止的,那么怎么证明呢?

下面我们通过运行这段代码

运行起来之后为了验证操作系统是通过发送信号让进程异常终止的同时报错异常终止的原因,那么我们是不是也可以通过发送信号让一个正常运行的进程报出相对应的错误呢?

我们再另一个窗口上发送了一个8号信号发现确实报错浮点数除零错误,发送了一个11号信号发现确实报错段错误。

所以我们就得出了一个结论:进程出异常,本质是进程收到了相对应的信号,进程终止了。

所以一个进程是否异常我们只要看有没有收到信号即可,而判断一个进程运行是否正确只需要通过进程的退出码来判断。所以父进程本质只需要关注这两个数字来判断子进程完成的任务如何。

进程常见退出方法

1.从main函数返回

首先我们可以看一下这一段代码:

我们运行一下:

发现最近一个进程的退出码就是main函数中return的值,所以这是一种进程退出的方法。

2.调用exit

我们先看看exit的手册:

exit()是c提供的一个接口,这个exit 中有一个参数,所以我们可以很容易的想到这个参数就是我们进程的退出码。

此时为了验证该函数是用来进行进程退出的,我们可以直接用以下代码进行测试:

运行结果:

我们发现进程的退出码是12,所以exit中的参数是进程的退出码,等价于main函数中的return.

下面我们把代码改成如下情况:

然后再运行之后:

第一个现象:fun函数被调用了,后续代码没有跑了。

第二个现象:当前退出码变成了21了,而不是12了。

所以我们得出了结论:任意地点调用exit表示进程退出,不进行后续执行。

为了进一步验证该结论我们可以再尝试一次代码的验证:

然后运行:

我们发现这个程序啥都没干就退出了,然后进程退出码变成了31.

所以后续我们想让一个进程直接退出的时候我们就可以直接调用exit();

3._exit

_exit的手册:

下面我们把我们上述写到的代码中的exit 换成_exit

进行运行:

发现跟exit的结果是一样的,其实在这里与exit的效果一样。

下面我们来讨论这两者有什么区别呢?

首先我们先对exit来进行操作:

然后运行:

这条打印的消息会先刷出来然后再暂停3s然后退出。

如果把\n去掉,也就是不让他刷新缓存区:

代码变成这样然后运行:

刚开始是这样

然后:

这样是在进程退出的时候是会自动刷新缓存区的。

如果我们换成_exit的话:

运行是这样:

这是通过行刷新来刷新缓冲区的,所以没有问题,但是如果我们把\n去掉之后运行:

运行之后:

发现打印消息直接不见了。

两者区别:

1.exit是库函数,_exit是系统调用。

2.exit终止进程的时候,会自动刷新缓冲区。_exit终止进程的时候不会自动刷新缓冲区。

我们目前知道的缓冲区,绝对不在操作系统内部。

不然的话双方都应该刷新。

我们把关于缓冲区这个问题留到后面。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

BetaFlight开源代码之电流校准

BetaFlight开源代码之电流校准 1. 源由2. 分析2.1 常规逻辑2.2 数据流2.3 采样电路2.3.1 采样实现2.3.2 采样原理2.3.3 Layout参考2.3.4 INA169芯片2.3.5 INA169 Near-Zero Vsense 3. 原理4. 示例4.1 实测&转换数据4.2 线性拟合-小电流4.3 线性拟合-大电流4.4 大电流/小电流…

HDMI彩条显示实验与方块移动实验

一、HDMI接口简介 一种数字音视频接口标准&#xff0c;提供高质量的数字音视频传输&#xff0c;同时支持多通道音频、高分辨率视频和其他数据传输功能。提供更高的数据传输带宽&#xff08;带宽&#xff1a;1s内传输多少比特数据&#xff09; 数字传输&#xff1a; HDMI是一种全…

【VRTK】启用多种VR设备的Passthrough功能

【背景】 透视可以让VR头盔展现AR能力,通过VRTK,可以快速实现多种设备平台可用的透视功能。包括主流的Oculus,Pico等。整个不成不需要自己写代码。 【操作】 针对WaveXR,点击场景中的CameraRigsWaveXR-》WaveRig-》Camera Offset-》Main Camera,追加一个新组件,名为Und…

QT自定义信号和槽

信号和槽 介绍实现创建文件对teacher的h和cpp文件进行处理对student的h和cpp文件进行处理对widget的h和cpp文件进行处理 介绍 Qt中的信号和槽是一种强大的机制&#xff0c;用于处理对象之间的通信。它们是Qt框架中实现事件驱动编程的核心部分。 信号&#xff08;Signal&#x…

vite4项目中,vant兼容750适配

一般非vite项目&#xff0c;使用postcss-px-to-viewport。在设计稿为750时候&#xff0c;可使用以下配置兼容vant。 在vite4项目中&#xff0c;以上配置不行。需要调整下&#xff0c;使用postcss-px-to-viewport-8-plugin&#xff0c;并修改viewportWidth&#xff0c;具体如下…

51单片机定时/计数器相关知识点

51单片机定时/计数器相关知识点 结构组成 51单片机的定时/计数器中有两个寄存器&#xff1a; T0&#xff1a;低位&#xff1a;TL0&#xff08;字节地址8AH&#xff09;高位&#xff1a;TH0&#xff08;字节地址8CH&#xff09;T1&#xff1a;低位&#xff1a;TL1&#xff08…

走向云原生 破局数字化

近年来&#xff0c;随着云计算概念和技术的普及&#xff0c;云原生一词也越来越热门&#xff0c;云原生成为云计算领域的新变量。行业内&#xff0c;华为、阿里巴巴、字节跳动等各个大厂都在“抢滩”云原生市场。行业外&#xff0c;云原生也逐渐出圈&#xff0c;出现在大众视野…

Visual Studio Code安装C#开发工具包并编写ASP.NET Core Web应用

前言 前段时间微软发布了适用于VS Code的C#开发工具包&#xff08;注意目前该包还属于预发布状态但是可以正常使用&#xff09;&#xff0c;因为之前看过网上的一些使用VS Code搭建.NET Core环境的教程看着还挺复杂的就一直没有尝试使用VS Code来编写.NET Core。不过听说C# 开发…

设计模式Java实战,彻底学会

​这是全网最强的Java设计模式实战教程。此教程用实际项目场景&#xff0c;结合SpringBoot&#xff0c;让你真正掌握设计模式。 网址是&#xff1a;Java设计模式实战专栏介绍 - 自学精灵&#xff08;也可以百度搜索“自学精灵”&#xff09;。 本设计模式专栏的威力 用Java实…

MySQL学习笔记2: MySQL的前置知识

目录 1. MySQL是什么?2. 什么是客户端&#xff0c;什么是服务器&#xff1f;3. 服务器的特点4. 安装mysql5. mysql 客户端6. mysql 服务器7. mysql的本体8. MySQL 使用什么来存储数据&#xff1f;9. 数据库的多种含义10. MySQL 存储数据的组织方式 1. MySQL是什么? MySQL 是…

算法每日一题: 被列覆盖的最多行数 | 二进制 - 状态压缩

大家好&#xff0c;我是星恒 今天的题目又是一道有关二进制的题目&#xff0c;有我们之前做的那道 参加考试的最大学生数的 感觉&#xff0c;哈哈&#xff0c;当然&#xff0c;比那道题简单多了&#xff0c;这道题感觉主要的考点就是二进制&#xff0c;大家可以好好总结一下这道…

使用 Maven 的 dependencyManagement 管理项目依赖项

使用 Maven 的 dependencyManagement 管理项目依赖项 介绍 在开发 Java 项目时&#xff0c;管理和协调依赖项的版本号是一项重要而繁琐的任务。 而 Maven 提供了 <dependencyManagement> 元素&#xff0c;用于定义项目中所有依赖项的版本。它允许您指定项目中每个依赖…

Java网络编程 、UDP、TCP、Socket通信

这个是第一篇&#xff0c;我先写udp&#xff0c; 首先我解释一下这个的特点是什么&#xff0c;他的特点主要是&#xff1a; 我发送消息之后就不管这个消息的任何情况&#xff0c;也就是&#xff0c;我只要把这个消息发送出去就不管了 这个是大白话的解释&#xff0c;具体的就…

el 消除inpu输入框内容和下拉内容

输入这个就好了,clearable @clear="getList()" 非常简单 <span class="type-box"><span class="label">订单状态</span><el-select v-model="params.orderStatus" placeholder="请选择" class=&…

openGauss学习笔记-188 openGauss 数据库运维-常见故障定位案例-core问题定位

文章目录 openGauss学习笔记-188 openGauss 数据库运维-常见故障定位案例-core问题定位188.1 磁盘满故障引起的core问题188.1.1 问题现象188.1.2 原因分析188.1.3 处理办法 188.2 GUC参数log_directory设置不正确引起的core问题188.2.1 问题现象188.2.2 原因分析188.2.3 处理办…

html js加载本地文件报错处理,跨域问题

这个问题是怎么来的&#xff1f;我写了一个本地html文件&#xff0c;里面通过three.js加载并显示一个本地三维模型&#xff0c;结果报错了。 报错如下&#xff1a; Access to XMLHttpRequest at file:///C:/model/quater.mtl from origin null has been blocked by CORS poli…

GUI设计基础

层次结构 要学GUI&#xff0c;大概先知道它的层次结构&#xff0c;如下图所示&#xff0c;我们要设计的就是下面这个几个东西。 菜单uimenu 建立一级菜单项的函数调用格式&#xff1a; hmuimenu(h_parent,PropertyNamel,valuel,propertyName2,value2&#xff0c;...); hm 是…

云原生十二问

一、什么是云原生&#xff1f; 云原生是在云计算环境中构建、部署和管理现代应用程序的软件方法。现代企业希望构建高度可扩展、灵活且具有弹性的应用程序&#xff0c;可以快速更新以满足客户需求。为此&#xff0c;他们使用现代工具和技术&#xff0c;这些工具和技术本质上支…

从零开始的OpenGL光栅化渲染器构建1

前言 参照Learnopengl&#xff0c;我开始回顾OpenGL中的内容&#xff0c;最终目标是构建一个玩具级的光栅化渲染器&#xff0c;最好还能和之前做的光线追踪渲染器相结合&#xff0c;希望能够有所收获吧~ 包管理 之前我用CMake配置过OpenGL的环境&#xff0c;这样做出来的项目…

【Java EE初阶六】多线程案例(单例模式)

1. 单例模式 单例模式是一种设计模式&#xff0c;设计模式是我们必须要掌握的一个技能&#xff1b; 1.1 关于框架和设计模式 设计模式是软性的规定&#xff0c;且框架是硬性的规定&#xff0c;这些都是技术大佬已经设计好的&#xff1b; 一般来说设计模式有很多种&#xff0c;…