【Linux】—— 进程的创建和退出

序言:

在上期,我们已经对 Linux的进程的相关知识进行了相关的学习。接下来,我们要学习的便是关于进程控制 的基本知识!!!

目录

(一)进程创建

1、fork函数初识

2、写时拷贝

3、fork使用场景

4、fork调用失败的原因

(二)进程终止

1、进程退出场景

2、进程常见退出方法

1️⃣echo $?

 2️⃣return退出

3️⃣exit函数

4️⃣_exit函数 

5️⃣exit 和 _exit 的区别

总结


(一)进程创建

1、fork函数初识

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

fork()函数,其原型如下:

#include <unistd.h>
pid_t fork(void);返回值:自进程中返回 0,父进程返回子进程id,出错返回 -1
fork() 函数不需要参数,返回值是一个进程标识符 PID 。返回值有以下三种情况:
  • 1) 对于父进程,fork()函数返回新创建的子进程的PID
  • 2) 对于子进程,fork()函数调用成功会返回0
  • 3) 如果创建出错,fork()函数返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:
  1. 分配新的内存块和内核数据结构给子进程
  2. 将父进程部分数据结构内容拷贝至子进程
  3. 添加子进程到系统进程列表当中
  4. fork返回,开始调度器调度

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

  #include<stdio.h>#include<unistd.h>#include<stdlib.h>int main(  ){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;}

输出结果如下:

 【说明】

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

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

2、写时拷贝

创建进程的时候,会使用写时拷贝技术来优化内存资源的使用,避免不必要的内存复制。

写时拷贝是一种延迟内存复制的策略,在创建子进程时不会立即复制整个父进程的内存空间,而是让父进程和子进程共享同一份物理内存。只有在需要修改共享内存内容的时候,才会进行实际的内存复制,以保证各个进程之间的隔离性。

  • 下面是一个简单的示例代码,展示了创建进程时的写时拷贝特性:
#include <stdio.h>
#include <unistd.h>int main() 
{int data = 123;pid_t pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子进程printf("Child: data = %d\n", data);data = 456;  // 修改共享内存中的内容printf("Child: modified data = %d\n", data);} else {// 父进程sleep(1);  // 等待子进程执行完毕printf("Parent: data = %d\n", data);}return 0;
}

运行这段代码,输出结果如下:

 【说明】

  1.  可以看到,父进程和子进程在创建时共享了同一份内存空间,即data变量;
  2. 当子进程对data进行修改后,父进程的data并没有发生变化,实现了进程之间的隔离。这就是写时拷贝技术的效果。

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


3、fork使用场景

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

4、fork调用失败的原因

fork()调用失败的原因可能有以下几种情况:

  1. 系统资源不足:当系统没有足够的资源(如可用内存、进程表项等)来创建新的进程时,fork()调用可能会失败。
  2. 达到了进程限制:系统对同时存在的进程数量设置了限制,如果已经达到了这个限制,再进行fork()调用就会失败。
  3. 进程权限不足:如果当前进程不具有创建子进程的权限,例如在特殊的用户模式下或者进程受到了某些权限限制,fork()调用也会失败。

(二)进程终止

1、进程退出场景

有多种场景会导致进程退出,以下是一些常见的进程退出场景:

  1. 正常退出:进程执行完了所有的任务,或者遇到了终止条件,主动调用了exit()函数或返回了main()函数的末尾,从而正常退出进程。

  2. 异常退出:进程在执行过程中遇到了错误或异常情况,无法继续执行下去,例如访问非法内存、除以零等。此时,操作系统会将异常信息传递给进程的错误处理机制,进程可能会执行相应的错误处理操作后终止。

  3. 进程被操作系统终止:操作系统可能会因为某些原因(如内存资源紧张、系统崩溃等)需要终止进程,从而导致进程退出。

这些仅是一些常见的进程退出场景,具体情况还可能根据操作系统和应用程序的不同而有所差异。处理进程退出的方式也会因为不同的场景而有所变化,例如通过信号处理函数、异常处理机制等来进行适当的处理。


2、进程常见退出方法

不知道大家是否知道每次写代码都会在末尾加上return 0; 这是为什么?

  1. 在C/C++程序中,最后的return 0;语句用于指示程序的正常退出,并且返回退出码为0
  2. 在UNIX/Linux系统中,退出码为0表示程序成功地执行完成并且没有发生错误

退出码是一个整数值,通常在范围0-255之间。不同的退出码可以用于指示不同的情况,例如指示程序的执行状态、错误码、操作结果等。在编写C/C++程序时,根据程序的需要,可以使用不同的退出码。

一般来说,返回0作为退出码主要用于表示程序成功执行完成,并且没有发生任何错误。这在脚本或其他程序通过检查退出码来判断程序是否成功执行时非常有用。如果程序成功执行完成,它将返回0,否则可能返回其他非零的退出码,以表示不同的错误情况。


1️⃣echo $?

echo $?是一个命令,用于在终端中显示上一个执行的命令的退出状态码。在大多数操作系统中,退出状态码为0表示上一个命令正常执行完成,非零的状态码通常表示出现了错误或异常情况。

当你在终端中执行echo $?时,它会显示上一个命令的退出状态码。例如,如果你之前执行了一个成功的命令,那么echo $?将显示0。如果上一个命令出现了错误,那么echo $?将显示非零的状态码,以反映出错的原因。

代码演示:

#include <stdio.h>int main() 
{int sum = 0;for (int i = 1; i <= 100; i++) {sum += i;}if (sum == 5050) {printf("和为5050\n");return 0;} else {printf("和不为5050\n");return 1;}
}

结果展示:

当我们把循环体的范围改为 i<=101; 在查看此时的退出码为多少:

 当我们把错误时的退出码改为 return 11;  在查看此时的退出码为多少:

 【注意】


正常终止(可以通过 echo $? 查看进程退出码)
  • 1. main返回
  • 2. 调用exit
  • 3. _exit
异常退出
  • ctrl + c,信号终止

 2️⃣return退出

return 是一种更常见的退出进程方法。执行 return n 等同于执行 exit(n), 因为调用 main 的运行时函数会将 main 的返回值当做 exit 的参数。

3️⃣exit函数

原型如下:

#include <unistd.h>
void exit(int status);

 exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

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

代码示例:

#include<stdio.h>#include<unistd.h>#include<stdlib.h>int add_to_top(int top){printf("enter add_to_top\n");int sum = 0;for(int i = 0; i <= top; i++){sum += i;}exit(123);printf("out add_to_top\n");return sum;}int main(){int result = add_to_top(100);if(result == 5050)                                                                                                                                               return 0;elsereturn 11; //计算结果不正确}

结果展示:

4️⃣_exit函数 

原型如下:

#include <unistd.h>
void _exit(int status);参数:status 定义了进程的终止状态,父进程通过wait来获取该值
【说明】
  • 虽然statusint,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值是255

结果展示:


5️⃣exit 和 _exit 的区别

exit()_exit()是两个不同的函数,用于终止程序的执行,但它们有一些区别:

exit()

  • exit()是C和C++标准库中提供的函数。
  • 调用exit()函数将导致正常的程序终止流程。它首先执行注册的终止处理函数(通过atexit()函数注册的函数),然后关闭所有标准I/O流(例如stdinstdoutstderr),最后返回到操作系统。
  • 在执行exit()之前,还会对静态全局变量、全局对象析构函数、自动变量和动态内存进行清理和释放。
  • exit()还允许程序向操作系统返回一个退出状态码,这个状态码可以在其他程序或脚本中用来判断程序的执行结果。

_exit()

  • _exit()是POSIX标准库中提供的函数。
  • 调用_exit()函数将直接终止程序的执行,不会执行任何清理操作。它会立即关闭进程并返回到操作系统,不会执行终止处理函数、关闭标准I/O流、清理静态全局变量等操作。
  • _exit()函数并不返回任何退出状态码给操作系统。

总结

以上便是进程创建和退出的全部知识了。接下来,我们简单回顾下本文内容!!!

进程的创建:

  1. 进程是执行中的程序实例,它由操作系统创建和管理。
  2. 进程的创建通常是通过调用一个特定的系统调用(例如fork())来实现的。
  3. 在进程创建时,操作系统会为新进程分配资源,包括内存空间、文件描述符等。

进程的退出:

  1. 进程可以正常退出或异常退出。
  2. 正常退出是进程完成其任务后自愿终止执行,通常使用exit()函数来终止进程。
  3. 异常退出是进程在遇到错误、异常或致命错误时非自愿地终止执行,操作系统会接收到相应的信号并处理。
  4. 进程退出时,操作系统会清理该进程占用的资源,释放内存、关闭文件描述符等。
  5. 在进程退出时,还可以返回一个退出状态码给父进程或操作系统,用来表示进程的执行结果。

小结:

  • 进程的创建是通过调用系统调用来实现的,操作系统为新进程分配资源并初始化其属性。
  • 进程的退出分为正常退出和异常退出,正常退出是进程自愿终止执行,异常退出是非自愿地终止执行。
  • 进程退出时,操作系统会清理资源并释放内存。可以返回一个退出状态码,用来表示进程的执行结果。

到此,本文的知识点便全部讲解完毕了。感谢大家的观看与支持!!!

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

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

相关文章

Docker中容器数据卷

容器数据卷 一.什么是容器数据卷二.使用数据卷方式一&#xff1a;直接使用命令来挂载 -v 三.具名和匿名挂载 一.什么是容器数据卷 docker理念 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们的容器删除&#xff0c;数据就会…

oracle 19c打补丁遭遇OPATCHAUTO-72043OPATCHAUTO-68061

最近&#xff0c;在AIX上的新装oracle 19C数据库基础版本&#xff0c;使用opatchauto打PSU补丁集35037840时遇到了OPATCHAUTO-72043报错&#xff0c;无法正常应用GI补丁。 一、环境描述 操作系统&#xff1a;AIX 数据库版本&#xff1a;oracle rac 19.3.0新装基础版 应用PS…

2023拒绝内卷!两年转行网络安全真实看法!

我目前转行网络安全两年&#xff0c;没啥天分&#xff0c;全靠努力&#xff0c;基本能够得上中级的水平了。看到大家对转行网络安全挺感兴趣&#xff0c;也有挺多争议&#xff0c;想把我的建议和经验告诉大家。 有很多人觉得网络安全已经饱和了&#xff0c;现在选择这个工作&a…

Spring Boot : ORM 框架 JPA 与连接池 Hikari

数据库方面我们选用 Mysql &#xff0c; Spring Boot 提供了直接使用 JDBC 的方式连接数据库&#xff0c;毕竟使用 JDBC 并不是很方便&#xff0c;需要我们自己写更多的代码才能使用&#xff0c;一般而言在 Spring Boot 中我们常用的 ORM 框架有 JPA 和 Mybaties &#xff0c;本…

面试之多线程案例(四)

1.单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时&#xff0c;为了防止频繁地创建对象使得内存飙升&#xff0c;单例模式可以让程序仅在内存中创建一个对象&#xff0c;让所有需要调用的地方都共享这一单例对象。…

[React]生命周期

前言 学习React&#xff0c;生命周期很重要&#xff0c;我们了解完生命周期的各个组件&#xff0c;对写高性能组件会有很大的帮助. Ract生命周期 React 生命周期分为三种状态 1. 初始化 2.更新 3.销毁 初始化 1、getDefaultProps() 设置默认的props&#xff0c;也可以用duf…

【数据结构】实现单链表的增删查

目录 1.定义接口2.无头单链表实现接口2.1 头插addFirst2.2 尾插add2.3 删除元素remove2.4 修改元素set2.5 获取元素get 3.带头单链表实现接口3.1 头插addFirst3.2 尾插add3.3 删除元素remove3.4 判断是否包含元素element 1.定义接口 public interface SeqList<E>{//默认…

关于拓扑排序

又重新学了一下拓扑排序&#xff0c;这次发现就十分简单了&#xff0c;拓扑排序的步骤 1.他必须是一个有向无环图&#xff0c;起点我们就是入度为0的点 2.我们首先要输出的就是入度为0的点&#xff0c;然后依次删除这些点连向的点&#xff0c;使这些点的入度-1&#xff0c;如果…

Java集合数组相互转换

1.集合转换成数组 &#xff08;1&#xff09;集合a通过toArray()方法进行转换为数组&#xff0c;可以转换成为指定类型的数组&#xff0c;但是这些类型都必须是object类型的子类&#xff0c;基本类型不可以。 必要时我们使用强制类型转换&#xff0c;转成我们需要的类型。 Li…

葡萄叶病害数据集

1.数据集 分成训练集和测试集 训练集有四个文件夹 第一个文件夹为 Grape Black Measles&#xff08;葡萄黑麻疹&#xff09;病害&#xff08;3783张&#xff09; Grape Black rot葡萄黑腐病病害数据集&#xff08;3596张&#xff09; Grape Healthy 健康葡萄叶&#xff08;25…

Maven项目解决cannot resolve plugin maven-deploy-plugin:2.7

导入maven项目后&#xff0c;编辑的时候提示一些插件加载失败&#xff01;大概率是你的网络有问题&#xff0c;插件下载失败。 如下图&#xff1a;&#xff08;网络突然好了&#xff0c;我想截图但是没有复现&#xff0c;用网上找到的截图代替&#xff0c;明白意思就行&#x…

Anaconda创建虚拟环境

参考文章 1 Win10RTX3060配置CUDA等深度学习环境 pytorch 管网&#xff1a;PyTorch 一 进入 Anaconda 二 创建虚拟环境 conda create -n pytorch python3.9注意要注意断 VPN切换镜像&#xff1a; 移除原来的镜像 # 查看当前配置 conda config --show channels conda config…

4-百度地图

4-百度地图 一 百度地图 1 前期准备 H5端和PC端,对接百度提供JavaScript API。 移动端,对接百度android SDK或ios SDK (1)打开百度地图开放平台 地址:https://lbsyun.baidu.com/ (2)选中开发文档——JavaScript Api 按照文档步骤开通百度开放平台并申请密钥 2 展示地…

Java多线程(四)

目录 一、线程的状态 1.1 观察线程的所有状态 1.2 线程状态和状态转移的意义 1.2.1 NEW、RUNNABLE、TERMINATED状态转换 1.2.2 WAITING、BLOCKED、TIMED_WAITING状态转换 1.2.3 yield()大公无私让出cpu 一、线程的状态 1.1 观察线程的所有状态 public class Demo9 {public st…

在linux中进行arm交叉编译体验tiny6410裸机程序开发流程

在某鱼上找了一个友善之臂的Tiny6410开发板用来体验一下嵌入式开发。这次先体验一下裸机程序的开发流程&#xff0c;由于这个开发板比较老旧了&#xff0c;官方文档有很多过期的内容&#xff0c;所以记录一下整个过程。 1. 交叉编译器安装 按照光盘A中的文档《04- Tiny6410 L…

快速开发框架若依的基础使用详解

Hi I’m Shendi 快速开发框架若依的基础使用详解 最近在为公司制作新的项目&#xff0c;经过了一段时间的技术沉淀&#xff0c;我开始尝试接触市面上用的比较多的快速开发框架&#xff0c;听的最多的当属若依吧 于是就选用了若依 介绍 为什么选&#xff1f;目的是为了提高开发…

Linux下.py文件只读问题以及解决过程

一、问题描述 如图&#xff0c;在Ubuntu Linux系统中使用pycharm管理项目文件时&#xff0c;无法编辑&#xff0c;提示文件为只读&#xff1a; 点击"OK"后仍旧无法清除只读模式&#xff0c;并报错&#xff1a; 二、问题解决 将问题定性为文件权限相关问题&#…

使用Three.js创建旋转的立方体

使用Three.js创建旋转的立方体 在本篇技术博客中&#xff0c;我们将介绍如何使用Three.js创建一个简单的场景&#xff0c;其中包含一个旋转的立方体。我们将学习如何设置场景、摄像机、立方体和渲染器&#xff0c;以及如何使用OrbitControls和gsap库来实现立方体的旋转动画和交…

VBA技术资料MF38:VBA_在Excel中隐藏公式

【分享成果&#xff0c;随喜正能量】佛祖也无能为力的四件事&#xff1a;第一&#xff0c;因果不可改&#xff0c;自因自果&#xff0c;别人是代替不了的&#xff1b;第二&#xff0c;智慧不可赐&#xff0c;任何人要开智慧&#xff0c;离不开自身的磨练&#xff1b;第三&#…

svn工具使用

svn 介绍 解决之道&#xff1a; SCM&#xff1a;软件配置管理 所谓的软件配置管理实际就是对软件源代码进行控制与管理 CVS&#xff1a;元老级产品 VSS&#xff1a;入门级产品 ClearCase&#xff1a;IBM 公司提供技术支持 SVN&#xff1a;主流产品 什么是SVN&#xff…