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

news2024/12/25 8:06:44

文章目录

  • 一、进程创建
    • 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

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

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

相关文章

【总结】坐标变换和过渡矩阵(易忘记)

xCy&#xff0c;此为x到y的坐标变换。 [β1,β2,…,βn] [α1,α2,…αn]C&#xff0c;此为基α到基β的过渡矩阵。 这个概念经常忘记。。。alpha到beta看来就是alpha后面加一个过渡矩阵了&#xff0c;很直观。坐标变换就是根据过渡矩阵和基本形式推一推得到吧&#xff0c;记…

若依前后端分离版,快速上手

哈喽~大家好&#xff0c;这篇来看看若依前后端分离版&#xff0c;快速上手&#xff08;肝了挺久的&#xff09;。 &#x1f947;个人主页&#xff1a;个人主页​​​​​ &#x1f948; 系列专栏&#xff1a;【Springboot和Vue全栈开发】…

LeetCode算法题解(动态规划)|LeetCoed62. 不同路径、LeetCode63. 不同路径 II

一、LeetCoed62. 不同路径 题目链接&#xff1a;62. 不同路径 题目描述&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下…

harmonyOS鸿蒙开发工具下载安装以及使用流程

注册账号 进入鸿蒙官方网站&#xff1a;https://www.harmonyos.com/ 推荐使用手机号注册 进行实名认证 下载开发环境 华为集成开发环境IDE DevEco Device Tool下载 | HarmonyOS设备开发 下载开发工具 HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 安装 无脑下一…

java源码-工程讲解

1、 工程目录 源码工程目录讲解部分&#xff0c;讲解过程会让大家对后端源码工程有一个大致的了解&#xff0c;能让大家在此改造&#xff0c;就可以衍生出一些新的功能&#xff0c;需要对java技术深入了解&#xff0c;需要看后续java技术讲解部分 整个架构是一个spring-boot…

python学习:break用法详解

嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 在执行while循环或者for循环时&#xff0c;只要循环条件满足&#xff0c;程序会一直执行循环体。 但在某些场景&#xff0c;我们希望在循环结束前就强制结束循环。 Python中有两种强制结束循环的方法&#xff1a; continue语…

fopen/fwrite/fread 对UNICODE字符写入的总结

windows对fopen函数进行了升级&#xff0c;可以支持指定文件的编码格式&#xff08;ccs参数指定&#xff09;。 例如&#xff1a; FILE *fp fopen("newfile.txt", "rt, ccsUTF-8"); 当以 ccs 模式打开文件时&#xff0c;进行读写操作的数据应为 UTF-16…

订阅号和服务号有什么区别

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;我们都知道&#xff0c;服务号一个月只能发4次文章&#xff0c;但是订阅号每天都能发文章。不过在接收消息这一方面&#xff0c;服务号群发的消息有消息提醒&#xff0c;并显示在对话框&#xff1b…

ARDUINO UNO 12颗LED超酷流水灯效果

效果代码&#xff1a; #define t 30 #define t1 20 #define t2 100 #define t3 50 void setup() { // set up pins 2 to 13 as outputs for (int i 2; i < 13; i) { pinMode(i, OUTPUT); } } /Effect 1 void loop() { effect_1(); effect_1(); effect_…

视频剪辑技巧:轻松搞定视频随机合并,一篇文章告知所有秘诀

在视频制作的过程中&#xff0c;视频随机合并是一种创新的剪辑手法&#xff0c;它打破了传统的线性剪辑模式&#xff0c;使得视频剪辑更加灵活和有趣。通过将不同的视频片段随机组合在一起&#xff0c;我们可以创造出独特的视觉效果和情感氛围。这种剪辑方式让观众在观看视频时…

Redis 9 数据库

4 设置键的生存时间或过期时间 通过EXPIRE命令或者PEXPIRE命令&#xff0c;客户端可以以秒或者毫秒精度为数据库中的某个键设置生存时间&#xff08;TimeToLive&#xff0c;TTL&#xff09;&#xff0c;在经过指定的秒数或者毫秒数之后&#xff0c;服务器就会自动删除生存时间…

腾讯云服务器新用户优惠怎么领?附腾讯云新用户优惠领取链接

大家好&#xff0c;今天我们来聊聊腾讯云服务器的优惠活动&#xff01;如果你是腾讯云的新用户&#xff0c;那么你一定不能错过这个机会&#xff01; 首先&#xff0c;新用户可以领取双十一9999代金券&#xff0c;这可是一大笔钱啊&#xff01;而且&#xff0c;你还可以另外再…

sort()方法详解

作用 对数组进行排序&#xff0c;默认情况下&#xff0c;将元素转换为字符串&#xff0c;然后按照它们的UTF-16码值升序排序。 语法 sort() 元素是字符串时 默认排序时根据字典顺序进行排序的 元素是字母字符串时&#xff0c;按照字母进行升序&#xff0c; const stringAr…

Spring学习③__Bean管理

目录 IOC接口ApplicationContext 详解IOC操作Bean管理基于xml方式基于xml方式创建对象基于xml方式注入属性使用set方法进行注入通过有参数的构造进行注入p 名称空间注入&#xff08;了解&#xff09; 基于xml方式注入其他类型属性xml 注入数组类型属性 IOC接口 IOC思想基于IOC…

LM(大模型)应用开发利器之LangChain,带你走进AI世界

原文&#xff1a;LLM&#xff08;大模型&#xff09;应用开发利器之LangChain&#xff0c;带你走进AI世界 - 简书 LangChain组件图 LangChain 是什么 首先 LangChain 是一个框架&#xff0c;这个框架是用来让开发者进行 LLMs &#xff08;大语言模型&#xff09;应用开发的。…

【操作系统】磁盘物理地址怎么表示

常见主存物理地址是一串01串&#xff0c;那磁盘物理地址呢&#xff1f; 磁盘物理地址由以下组成&#xff1a; 柱面号磁头号扇区号 重点知识点辨析&#xff1a; 磁盘物理地址的翻译是由磁盘驱动程序进行的&#xff0c;目的是将逻辑上的蔟号转化为上述的物理地址 408真题溯源…

现在的各类解释非常混乱,到底什么是智慧城市?

智慧城市&#xff0c;简单来说&#xff0c;就是运用先进的信息和通信技术&#xff0c;让城市管理更加智能、高效&#xff0c;让市民的生活更加便捷、舒适。 在我们日常生活中&#xff0c;智慧城市带来的改变无处不在。 想象一下&#xff0c;当你早上醒来&#xff0c;你的手机已…

我叫:冒泡排序【JAVA】

1.什么是冒泡排序&#xff1f; 冒泡排序(Bubble Sorting)的基本思想是:通过对待排序序列从前向后&#xff08;从下标较小的元素开始)&#xff0c;依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就象水底下的气泡一样逐渐向上冒。 2.来个实战应用 我们…

Microsoft SQL Server Management Studio(2022版本)启动无法连接到服务器

Microsoft SQL Server Management Studio&#xff08;2022版本&#xff09;启动无法连接到服务器 解决方法&#xff1a; 打开SQL Server 2022 配置管理器。 启动即可。

原来机械硬盘比内存慢10万倍

我们都知道机械硬盘的速度很慢&#xff0c;内存的速度很快&#xff0c;那么不同存储器之间的差距到底有多大呢&#xff1f; 我们先来看一幅图&#xff1a; CPU访问寄存器的时间是0.3纳秒&#xff0c;访问L1高速缓存的时间是1纳秒&#xff0c;访问L2高速缓存的时间是4纳秒… 秒…