【Linux修炼】11.进程的创建、终止、等待、程序替换(一)

简介: 【Linux修炼】11.进程的创建、终止、等待、程序替换(一)

进程的创建、终止、等待、程序替换


本节重点

1. 进程的创建

1.1 fork函数初识

1.2 fork的返回值问题

1.3 写时拷贝

1.4 创建多个进程

2. 进程终止

2.1 进程退出码

2.2 进程如何退出

3. 进程等待

3.1 进程等待的原因

3.2 进程等待的方法

3.3 再谈进程退出

3.4 进程的阻塞和非阻塞等待

4. 进程的程序替换

4.1 见见猪跑

4.2 理解原理(是什么、为什么、怎么办)

4.3 一个一个调用对应的方式

4.4 应用场景:模拟shell命令行解释器



本节重点

进程的创建,终止,等待,进程的程序替换(和进程地址空间强相关)


1. 进程的创建


1.1fork函数初识


在之前的进程创建中,已经提到过fork,因此在这里的初识是在原有基础上进一步了解。


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

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

那么在调用fork函数之前只有一个进程,当进程调用fork时,当控制转移到内核中的fork代码后,内核做:


  • 分配新的内存块和内核数据结构给子进程(内核数据结构:PCB地址空间+页表,构建对应的映射关系)

  • 将父进程部分数据结构内容拷贝至子进程

  • 添加子进程到系统进程列表当中

  • fork返回,开始调度器调度


对于第三点的添加系统进程列表,我们在之前的进程的章节中介绍是由链表存储,而实际上当时是为了便于理解,操作系统实际上没有那么笨,其实际上是由哈希表存储的,通过struct task_struct类型的指针数组存储,当运行需要的进程时则将会通过指针找到对应的进程控制块。


微信图片_20230224213011.png


1.2fork的返回值问题



对于这个问题,从三个层次去理解。


1. 如何理解fork函数有两个返回值问题?


微信图片_20230224213059.png

对于fork函数,当调用时,fork函数内部会有两个执行流,对应父进程和子进程,当fork函数内部代码执行完毕后,子进程也就被创建好了并有可能在OS的运行队列中准备被调度了,父进程和子进程各自执行return,这样在main()函数中调用fork函数时,从fork返回的两个执行流就会分别执行main()调用fork之后的代码,因此我们之前所了看到的两个结果就是父子进程对应的执行流所造成的。


2. 如何理解fork返回之后,给父进程返回子进程pid,给子进程返回0?


父亲:孩子 = 1:n, n>=1,因此孩子找父亲具有唯一性。而由于子进程多,父进程想具体调用某一个子进程时就需要这个子进程得有一个名字才能调用这个子进程,因此给父进程返回对应子进程的pid。


3. 如何理解同一个id值,怎么会保存两个不同的值,让if else if同时执行?


对于pid_t id = fork(),我们知道返回的本质就是写入,所以谁先返回,谁就先写入对应的id,由于进程具有独立性,因此进程就会进行写时拷贝(上一篇详细描述了),因此同一个id,地址是一样的,但内容却不同


1.3写时拷贝


上一篇的进程地址空间中,我们已经提到过什么是写时拷贝,但不是单独分一个专题去写的,因此,这里总结一下写时拷贝。


通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。(虚拟内存就是进程地址空间)


微信图片_20230222015540.png

即当我们不修改数据时,父子进程的虚拟内存所对应的物理内存都是同一块物理地址(内存),当子进程的数据被修改,那么就会将子进程修改所对应数据的物理内存出进行写时拷贝,在物理内存中拷贝一份放在物理内存的另一块空间,将子进程虚拟内存与这个新的地址通过页表进行关联。


1.4创建多个进程


创建多个进程,可以使用如下代码:


微信图片_20230222015639.png

由于开的进程过多,会导致整个OS崩掉,只需要重启服务器就可以解决了。

2.进程终止



2.1进程退出码


我们在C/C++中,在代码最后都会写上return 0;,对于这个返回值我们称它为进程退出码。对于正确的进程一般都以0作为进程退出码,而非0就作为错误的进程的退出码,因此不同的错误对应的退出码也是不同的。


退出码的意义: 0:success, !0:表示失败。!0具体是多少,即表示不同的错误。——数字对人不友好,对计算机友好。


对于如下代码:


微信图片_20230224213252.png

这个函数的返回值是4950,因此退出码是1。当进程执行之后可以通过一个命令查看具体的进程退出码:echo $?

微信图片_20230224213339.png

但当继续执行这个命令时,发现结果是0,这是因为这个命令只会显示最近一次的进程退出码,而下一个为0的原因就是echo本身也是一个进程,并且正确执行因此显示的是0。


在这里回顾一下之前的函数:strerror(n),n为自然数,即n的不同的值就代表着不同的错误。那我们就可以执行这样的一段代码:


for(int i=0; i<200; i++)
{
    printf("%d: %s\n", i, strerror(i));
}

微信图片_20230224213416.png

执行结果发现,只有0代表着success,其他的都对应不同的错误,并且有133个不同的错误,一共有134个进程结果。


微信图片_20230224213437.png

而对于我们指定指令的随意选项造成的错误:No such or diectory就就对应着数值为2的错误。


总结一下:


  • ./mytest ———— 运行一个进程
  • echo $? ———— $?永远记录最近一个进程在命令行中执行完毕时对应的退出码(main->return?😉


进程退出的情况:


  1. 代码跑完了,结果正确 ——— return 0;
  2. 代码跑完了,结果不正确———return !0; (退出码这个时候起效果。确定对应的错误)
  3. 代码没跑完,程序异常了,退出码无意义。


那么进程如何退出呢?接下来就来解释一下(前两种情况)


2.2进程如何退出


1. main函数return返回


这也是我们经常用的方式


2. 任意地方调用 exit(code)退出


微信图片_20230224213545.png

结果显而易见,当我们查看这个进程是如何结束的,直接观察退出码:


微信图片_20230224213620.png

此外,在函数内部exit时,进程也会直接结束,函数也不会有返回值,下面就来看看这个例子:

微信图片_20230224213650.png

微信图片_20230224213654.png

到exit语句就会将进程结束,后面的代码也就不会再去执行了。


3. _exit()退出


我们看一下_exit()是如何退出的。

微信图片_20230224213757.png


微信图片_20230224213804.png

我们发现其也是和exit()一样的功能。事实上,_exit()是系统调用的函数,也就是OS,而exit()是库函数,库函数是OS之上的函数,调用exit实际上就是exit内部调用_exit,但二者之间也会有区别,我们将换行符去掉,来演示一下:exit

微信图片_20230224213845.png

结果:


微信图片_20230224213850.gif


可以看出,进程结束后,会刷新缓冲区,打印的结果暂停2秒也会显示出来。再来看看_exit:


微信图片_20230224213940.png

微信图片_20230224213943.gif

这样并没有打印出结果,也就是说_exit并没有刷新缓冲区。


因此总结一下二者:


  1. exit终止进程,主动刷新缓冲区

  2. _exit终止进程,不会刷新缓冲区



微信图片_20230224214026.png


因此用户级的缓冲区一定在系统调用之上,具体位置会在基础IO的时候说明。

微信图片_20230224214056.png


相关文章
|
4天前
|
资源调度 Linux 调度
Linux c/c++之进程基础
这篇文章主要介绍了Linux下C/C++进程的基本概念、组成、模式、运行和状态,以及如何使用系统调用创建和管理进程。
14 0
|
4天前
|
消息中间件 Linux API
Linux c/c++之IPC进程间通信
这篇文章详细介绍了Linux下C/C++进程间通信(IPC)的三种主要技术:共享内存、消息队列和信号量,包括它们的编程模型、API函数原型、优势与缺点,并通过示例代码展示了它们的创建、使用和管理方法。
12 0
Linux c/c++之IPC进程间通信
|
4天前
|
Linux C++
Linux c/c++进程间通信(1)
这篇文章介绍了Linux下C/C++进程间通信的几种方式,包括普通文件、文件映射虚拟内存、管道通信(FIFO),并提供了示例代码和标准输入输出设备的应用。
10 0
Linux c/c++进程间通信(1)
|
4天前
|
Linux C++
Linux c/c++之进程的创建
这篇文章介绍了在Linux环境下使用C/C++创建进程的三种方式:system函数、fork函数以及exec族函数,并展示了它们的代码示例和运行结果。
10 0
Linux c/c++之进程的创建
|
4天前
|
Linux C++
Linux c/c++进程之僵尸进程和守护进程
这篇文章介绍了Linux系统中僵尸进程和守护进程的概念、产生原因、解决方法以及如何创建守护进程。
11 0
|
4月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
100 13
|
3月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
3月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
103 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
2月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
3月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
155 1