当前位置: 首页 > news >正文

进程控制之进程创建与终止

进程创建

1 fork函数初识

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

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

父进程调用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之后,谁先执行完全由调度器决定。

2 写时拷贝

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

当子进程尝试写入数据时,操作系统就会“报错”(不是传统意义上的报错,类似于触发了判断机制),操作系统要对当前情况进行判断分类

1 子进程想要写入数据的地址不属于它,而是一个未知的地址,即野指针,该情况是真的错误,终止进程

2 子进程需要在它的数据段中写入数据,则会触发写时拷贝,同时更改回权限(即将权限更改为之前的设置)

因为有写时拷贝技术的存在,所以父子进程得以彻底分离离!完成了进程独立性的技术保证!

写时拷贝,是一种延时申请技术,它的本质是“按需获取”可以提高整机内存的使用率

**扩展问题:**当你在C/C++上申请空间malloc/new的时候,需要在物理内存中开辟空间吗?不需要!

当你申请空间时开辟虚拟空间即可,只有当这个空间要被用到时,操作系统才会做内存级申请,而后再在虚拟空间和内存空间之间构建完整的映射关系,这是一种惰性空间开辟,同样可以提高内存利用率

这些操作对用户来说都是透明的,也就是说用户不知道也不需要知道

3 fork常规用法

• 父进程希望子进程复制自己,使父子进程同时执行同一个程序不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。

• 父进程希望子进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

fork调用失败的原因:

• 系统中有太多的进程

• 实际用户的进程数超过了限制

进程终止

1 背景知识

进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。

main函数最后会返回一个0,这个0是什么?

实际上,这个0是退出码,该退出码会被父进程拿到,表示进程的执行情况

为什么一定要有退出码?

因为父进程创建子进程是为了完成交给他的任务,任务完成的怎么样父进程要知道

0表示成功

!0表示失败,但失败总有原因,1、2、3…表示不同的失败原因

echo $?//查看最近一次进程结束的退出码 ?是变量,存储了退出码 $是显示变量中的内容

我们在命令行中运行的进程的父进程都是bash,因此bash可以拿到这些进程的退出码

2 退出码

退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表示执行成功,没有问题。代码 1 或 0 以外的任何代码都被视为不成功。

Linux Shell 中的主要退出码:

• 可以使用strerror函数来获取退出码对应的描述。

3 进程退出场景

• 代码运行完毕,结果正确

• 代码运行完毕,结果不正确

• 代码异常终止

前两种场景均与退出码有关·,根据退出码可以判断是哪一种退出

最后一种场景中由于代码异常退出,退出码无意义,要看信号,如kill -9 杀掉进程

4 进程常见退出方法

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

异常退出:

• ctrl + c,信号终止

main函数return n

return表示函数调用结束,main函数return表示进程退出

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

exit(n)

exit表示的是进程结束,在你的代码中任何地方调用都会导致进程退出

_exit(n)

_exit同样表示的是进程结束,在你的代码中任何地方调用都会导致进程退出

exit vs _exit

1

我们可以看到,exit输出了退出语句,_exit没有输出退出语句,这是为什么呢?

这是因为exit终止进程时会主动刷新缓冲区,_exit终止进程不会刷新缓冲区

2

exit是C库函数,_exit是系统调用

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

总结:

exit - 优雅的终止 _exit - 粗暴的终止

简单类比:

exit就像 礼貌地离开派对:你会感谢主人(调用清理函数),拿走你的杯子并洗干净(刷新缓冲区),然后才离开(调用内核)。

_exit 就像 突然离场:你一句话不说,直接开门就走。你的杯子还留在桌上,里面的酒(缓冲数据)也没人收拾。

终止进程的最佳实践是exit

到此,进程控制之进程创建与终止就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。

如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路

如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正

博语小屋将持续为您推出文章

http://www.dtcms.com/a/361771.html

相关文章:

  • Vue3+TS 流星夜景
  • TensorFlow 2.10 是最后一个支持在原生Windows上使用GPU的TensorFlow版本
  • Redisson和Redis实现分布式锁的对比
  • 【免费数据】2019年我国36个主要城市的高分辨率城市空地分布矢量数据
  • 【2025ICCV】
  • FOUPK3云服务平台旗下产品
  • Python 实战:内网渗透中的信息收集自动化脚本(7)
  • GD32入门到实战24--RTC实时时钟
  • 恶意软件概念学习
  • 【游戏开发】Houdini相较于Blender在游戏开发上有什么优劣势?我该怎么选择开发工具?
  • 【Java】Redis(中间件)
  • 订单后台管理系统-day07菜品模块
  • 域名备案后不解析可以吗
  • 五、导入现有模型
  • Docker基本介绍
  • 面试记录8 Linux/c++中级开发工程师(智能座舱)
  • 六大关键步骤:用MES系统重构生产计划管理闭环
  • Linux开发必备:yum/vim/gcc/make全攻略
  • 如何使用 JMeter 进行接口测试。
  • Java 常见异常系列:NumberFormatException 数字格式异常
  • ROS1系列学习笔记之ROS的调用,示例为激光雷达N10P的运行(含常见问题与踩坑解答)
  • 数据结构:计数排序 (Counting Sort)
  • 逻辑门编程(一)——与或非门
  • 接口响应慢 问题排查指南
  • MongoDB 内存管理:WiredTiger 引擎原理与配置优化
  • GraalVM Native Image:让 Java 程序秒启动
  • 植物中lncRNA鉴定和注释流程,代码(包含Identified,Classification,WGCNA.....)
  • shell编程 函数、数组与正则表达式
  • 预处理——嵌入式学习笔记
  • day06——类型转换、赋值、深浅拷贝、可变和不可变类型