【Linux】第十七站:进程创建与进程终止

文章目录

  • 一、进程创建
    • 1.fork函数
    • 2.写时拷贝
    • 3.批量化创建多个进程
  • 二、进程终止
    • 1.进程退出场景
    • 2.进程退出的方法
      • (1)exit和return
      • (2)_exit和exit

一、进程创建

1.fork函数

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

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

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

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

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

image-20231114141629329

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

如下所示,可以简单的验证一下结果

#include<stdio.h>    
#include<unistd.h>    
    
int main()    
{    
    printf("pid: %d,before\n",getpid());    
    fork();    
    printf("pid: %d, after\n",getpid());    
    return 0;
}    

运行结果为:

image-20231114155156442

这其实就是因为如下图所示,父进程创建了子进程以后,他们的虚拟地址经过页表映射后,指向同样的代码和数据。

image-20231114145342047

2.写时拷贝

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

image-20231114145556194

它会将代码和数据设置为只读的,当页表进行写的时候,直接进行写时拷贝即可

3.批量化创建多个进程

我们使用如下,代码

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

在这段代码中,我们可以瞬间批量化创建五个进程。当子进程运行完它需要做的部分的时候,直接退出进程。

接下来我们可以对这些进程进行监控

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

如下所示

image-20231114161653634

这是一开的状况,后面所有的子进程都会进入僵尸状态

image-20231114161737134

我们也同时可以注意到

如果有多个进程,那么哪一个进程先执行完全是由调度器所决定的

image-20231114161902857

所以我们的父子进程被创建出来以后,谁先运行我们完全不可知

二、进程终止

我们知道,在我们以前写代码的时候,最后总是会return 0,那么为什么这个main函数总是会返回0,如果返回1呢,2呢?其他值呢?可以吗?这个东西是给谁了?为什么要返回这个值?

1.进程退出场景

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

对于一个代码运行完毕,结果正确,我们不会关心它是为什么跑正确的

但是当一个代码运行完毕,结果不正确,我们就会关心为什么不正确?

#include<stdio.h>  
int main()      
{    
    printf("模拟一个逻辑的实现\n");    
	return 0;                                          
}        

在这个代码中,这个0代表的就是进程的退出码,表征进程的运行结果是否正确,如果是0代表的就是正确

image-20231114164230459

当我们运行完这个进程以后,我们可以用下面的指令来查看是否正确

echo $?

那么在进程中,谁会关心我运行的情况呢?

一般而言是我们的父进程会关心的。所以我们上面的bash就是我们程序的父进程,是可以拿到对应的退出码的

而我们的关心主要关心的就是不正确的情况,因为成功只有一种情况,而失败可以有无数种情况

所以可以用return的不同的返回值数字,表征不同的出错原因—退出码

所以我们的main函数的返回值可以是其他的任意值

image-20231114164804837

image-20231114164817141

所以main函数的返回值,本质表示:进程运行完成时是否是正确的结果,如果不是,可以用不同的数字,表示不同的出错原因!

不过对于上面这个打印退出码的这个指令,我们需要注意

image-20231114165153343

我们只有第一次打印是我们前面的退出码,当我们多打印几次就都变成了0

所以说,$?保存的是最近一次进程退出的时候的退出码

当我们后面的几次,其实打印的是echo这个指令的退出码了。

像我们之前的那些程序,其实不怎么关注退出码,因为并没有涉及到多进程等等,但是未来我们是看不到这些错误信息。所以我们就需要用退出码来标识。

不过对于退出码,它现在的这些只是单纯的数字,我们人并无法直接的知道它的意思,所以我们就有一个函数吗,可以将退出码转化为字符串

image-20231114171618851

我们可以试着用如下代码,打印出所有的错误码

#include<stdio.h>    
#include<unistd.h>    
#include<stdlib.h>    
#include<string.h>    
int main()                               
{                                                                 int i = 0;                           
    for(; i < 200; i++)                  
    {                                    
        printf("%d:%s\n",i,strerror(i));    
    }                                    
    return 0;                            
}           

运行结果如下所示

image-20231114172028134

所以就好比下面的例子,因为找不到这个文件,所以它会直接将错误码返回并解析

image-20231114172250939

当指令正确运行的时候,毫无疑问,退出码就是0

image-20231114172418181

所以系统提供的错误码和错误码描述是具有对应的关系的。

我们可不可自己设计一套错误码体系呢?

当然是可以的,也是非常的简单的,直接定义这样一个指针数组就可以了

image-20231114172831958

不过父进程为什么要关心这个退出码呢?

其实父进程也不是很关心的,它也相当于一个跑腿的。

真正关心的应该是用户,用户根据错误的描述,才能决定下一步执行什么操作

所以最终无论结果正确与否,统一会采用退出码来进行判定!!!

我们继续用下面的代码来验证

  #include<stdio.h>    
  #include<unistd.h>    
  #include<stdlib.h>    
  #include<string.h>    
      
  int main()    
  {    
      int ret = 0;    
      char* p = (char*)malloc(4*1000*1000*1000);    
      if(p == NULL)    
      {    
          ret = 1;    
          printf("malloc fail\n");    
      }    
      else    
      {    
          printf("malloc success\n");    
      }    
      return ret;    
  }    

image-20231114173817105

像我们在之前C语言中也有一个很类似的全局变量,errno

image-20231114174441228

它保存的是最近一次执行的错误码,比如当我们调用库函数的时候,就会发生失败,就会设置这个错误码。

它的用法如下

  #include<stdio.h>    
  #include<unistd.h>    
  #include<stdlib.h>    
  #include<string.h>    
  #include<errno.h>    
  int main()    
  {    
      int ret = 0;    
      char* p = (char*)malloc(4*1000*1000*1000);    
      if(p == NULL)    
      {    
          ret = errno;    
          printf("malloc fail,%d:%s\n",errno,strerror(errno));    
      }    
      else    
      {    
          printf("malloc success\n");    
      }    
      return ret;                                                                                                                                                                              
  }                                                                                                         

运行结果为

image-20231114180657499


以上都是对前两种场景的分析

但是还有第三种情况,代码异常了。那么此时退出码还有意义吗?

代码如果异常了,那么代码很可能就没有跑完

此时进程的退出码无意义,因为我们已经不关心退出码了

那么要不要关心为什么异常了呢?以及发生了什么异常

其实异常就如同下面的样子

image-20231114182225493

image-20231114182255621

这里就是发生了野指针错误

又或者,写出了这样的代码

image-20231114182344603

此时的就会提示浮点数异常

image-20231114182426296

一般而言,进程出现了异常,本质是我们的进程收到了对应的信号!

image-20231114182901185

如上所示,8号信号其实就是我们刚刚发生的浮点数异常,11号信号就是段错误,也就是野指针错误

我们可以用下面的代码来验证一下

image-20231114183416164

当我们使用8号和11号信号的时候,结果如下

image-20231114183530288

这就证明了异常的本质是我们的进程收到了对应的信号!

2.进程退出的方法

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

  1. 从main返回
  2. 调用exit
  3. _exit

异常退出

ctrl + c,信号终止

(1)exit和return

exit的介绍如下

我们可以简单的使用一下

image-20231114184357016

运行结果为

或者我们可以直接使用return来返回,他们两个在main函数种是完全等价的

那么exit和return有什么区别呢?

我们观察如下代码

image-20231114184733919

image-20231114184805520

所以现在,我们就知道了

exit在任意地方调用,都表示调用的进程直接退出,而return只表示当前函数返回,还会继续向后运行

(2)_exit和exit

_exit是一个系统调用

image-20231114185153247

它也可以直接退出当前进程

image-20231114185259918

image-20231114185321884

那么这两个有什么区别呢?

我们先看这段代码

image-20231114185624155

运行结果为

image-20231114185644763

我们在看看这段代码

image-20231114185707002

image-20231114185737367

我们发现第二次是没有打印出来的,而第一次是打印出来的

所以说

_exit是纯正的系统调用,它会直接终止进程,对缓冲区的数据不做刷新

exit它在终止之前会下做一些清理函数,冲刷缓冲区,是一个库函数

image-20231114190041140

我们可以这样理解,exit 和 _exit是一个调用和被调用的关系

像printf这个函数也是类似的

这个函数一定是先把数据写入缓冲区,合适的时候,在进行刷新。

那么这个缓冲区绝对不在哪里?

根据我们上面的现象,这个缓冲区一定不在内核中

因为如果在操作系统内部,那么执行_exit这个系统调用的时候,它一定会刷新缓冲区。因为操作系统不做任何浪费时间和空间的事情。

image-20231114190558739

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

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

相关文章

Java 中的全部锁

目录 一. 前言 二. 乐观锁 VS 悲观锁 三. 自旋锁 VS 适应性自旋锁 四. 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁 五. 公平锁 VS 非公平锁 六. 可重入锁 VS 非可重入锁 七. 独享锁&#xff08;排他锁&#xff09; VS 共享锁 八. 总结 一. 前言 Java提供了种类丰富的锁&a…

中小型企业内网搭建

某中小型公司客户提出网络比较单一整体都在一个大的广播域中&#xff0c;AP无线的SSID有很多个&#xff0c;包括一些小的无线路由器散发出来的信号&#xff0c;用起来网络不太稳定&#xff0c;并且AP的SSID要分开&#xff0c;办公室只有单个SSID&#xff0c;不允许出现其他的&a…

vue3+vite+ts 发布自定义组件到npm

vue3vite 发布自定义组件到npm 初始化项目编写组件配置打包组件上传到npm测试组件库 初始化项目 // 创建项目 pnpm create vite vue-test-app --template vue-ts// 运行项目 cd vite vue-test-app pnpm install pnpm run dev编写组件 1、根目录下创建packages目录作为组件的开…

leetcode刷题日志-151反转字符串中的单词

给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意&#xff1a;输入字符串 s中可能会存在前导空格、尾随…

面向对象成员之属性

属性:通过方法改造出来 # 1.编写时 # 方法上方写property # 方法参数:只有一个self # 2.使用时:无需加括号 对象.方法 # 3.应用场景:对于简单的方法,当无需传参且有返回值时,可以使用 property class Foo(object):def _init_(self):...propertydef start(self):return 1pr…

【vitis】 AIE basic

AIE vs AIE-ML versal 期间分类 文件 操作 vitis -new -w . 安装

易点易动固定资产管理系统场景应用一:集成ERP/财务系统

在企业的日常运营中&#xff0c;固定资产管理是一个重要而繁琐的任务。传统的手工管理方式往往效率低下且容易出错&#xff0c;给企业带来不必要的成本和风险。为了解决这一问题&#xff0c;易点易动固定资产管理系统应运而生。本文将重点介绍易点易动固定资产管理系统在集成ER…

【C语法学习】26 - strcat()函数

文章目录 1 函数原型2 参数3 返回值4 使用说明5 示例5.1 示例1 1 函数原型 strcat()&#xff1a;将src指向的字符串拼接在dest指向的字符串末尾&#xff0c;函数原型如下&#xff1a; char *strcat(char *dest, const char *src);2 参数 strcat()函数有两个参数src和dest&am…

SAP BAPI BAPI_CLASS_EXISTENCECHECK

物料分类是否存在的BAPI

Molecular Plant | ChIP-seq+RNA-seq解析E2F转录因子在植物复制胁迫响应中的独特和互补作用

生物体的生存完全依赖于它们对基因组完整性的维持&#xff0c;而基因组完整性受到增殖细胞复制胁迫的永久威胁。尽管植物DNA损伤反应&#xff08;DDR&#xff09;调节因子SOG1已被证明能够应对复制缺陷&#xff0c;但越来越多的证据表明&#xff0c;还有其他途径独立于SOG1发挥…

【IDEA 使用easyAPI、easyYapi、Apifox helper等插件时,导出接口文档缺少代码字段注释的相关内容、校验规则的解决方法】

问题 IDEA 使用easyAPI、easyYapi、Apifox helper等插件时&#xff0c;导出的接口文档上面&#xff0c;缺少我们代码里的注解字段&#xff0c;如我们规定了NOTNULL、字段描述等。 问题链接&#xff0c;几个月之前碰到过&#xff0c;并提问了&#xff0c;到现在解决&#xff0c…

k8s ingress高级用法一

前面的文章中&#xff0c;我们讲述了ingress的基础应用&#xff0c;接下来继续讲解ingress的一些高级用法 一、ingress限流 在实际的生产环境中&#xff0c;有时间我们需要对服务进行限流&#xff0c;避免单位时间内访问次数过多&#xff0c;常用的一些限流的参数如下&#x…

【Android】如何使用模拟器调试安卓项目

1、电脑安装逍遥模拟器&#xff0c;用来跑安卓项目。安装好模拟器之后&#xff0c;直接起安卓项目&#xff0c;自动会在选择设备处显示 2、如果前端是安卓后端是其他语言的话&#xff0c;这种前后端分离的模式&#xff0c;需要监听端口&#xff0c;原因是运行安卓和后端编译器都…

设计模式-11-模板模式

经典的设计模式有23种&#xff0c;但是常用的设计模式一般情况下不会到一半&#xff0c;我们就针对一些常用的设计模式进行一些详细的讲解和分析&#xff0c;方便大家更加容易理解和使用设计模式。 1-什么是模板模式 模板模式&#xff0c;全称是模板方法设计模式&#xff0c;英…

spark的资源调度与任务调度

blockManager 资源调度与任务调度

LangChain:打造自己的LLM应用

文章来源&#xff1a;https://www.cnblogs.com/jingdongkeji/p/17599838.html 1、LangChain是什么 LangChain是一个框架&#xff0c;用于开发由LLM驱动的应用程序。可以简单认为是LLM领域的Spring&#xff0c;以及开源版的ChatGPT插件系统。核心的2个功能为&#xff1a; 1&am…

国鑫受邀出席2023英特尔中国区数据中心渠道客户金秋会

10月18日&#xff0c;2023英特尔中国区数据中心渠道客户金秋会在重庆隆重举行&#xff0c;本次会议共邀请了全国英特尔数据中心渠道生态伙伴、本地服务器系统设计及制造厂商参与&#xff0c;Gooxi作为英特尔在中国区重要合作伙伴受邀参加。 会上&#xff0c;英特尔向生态合作伙…

玩具、儿童用品、儿童服装上亚马逊TEMU平台CPC认证办理

CPC认证是Childrens Product Certificate的简称&#xff0c;即儿童产品证书。它是美国强制性法规CPSIA要求的一部分&#xff0c;该法规主要针对12岁及以下儿童使用的产品&#xff0c;如玩具、儿童用品、儿童服装等。 一、儿童小汽车CPC测试项目可能会因产品标准和法规的不同而…

HIKVISION iSecure Center RCE 海康威视综合安防管理平台任意文件上传 POCEXP

参考:GitHub - Sweelg/HIKVISION_iSecure_Center-RCE: HIKVISION iSecure Center RCE 海康威视综合安防管理平台任意文件上传 POC&EXP&#xff08;一键getshell&#xff09; 速修&#xff01;海康威视综合安防RCE已被用于勒索 近日&#xff0c;勒索团伙利用海康威视综合安防…

navigator.geolocation.getCurrentPosition在谷歌浏览器不执行的问题

/*** 获取我的位置*/getNavigatorLocation: function () {navigator.geolocation.getCurrentPosition(function (success) {console.log(inner>>>, success);if (success && success.coords) {var data success.coords;var point "POINT(" data.…