本篇文章,继续来和大家分享与Linux相关的知识。本篇文章的内容主要会涉及到进程的创建和进程的终止。

进程创建

进程创建的方式,有两种,一种是我们在执行指令的时候,bash会创建子进程来帮助它执行指令。另一种是调用fork函数,来创建子进程。

fork

fork在前面的文章已经说过了,我们这里简单的回顾一下。

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

· 分配新的内存块和内核数据结构给子进程

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

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

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

也就是fork在return之前,子进程就已经创建好了。

Linux-进程控制(1)_退出码

我们可以简单验证一下

Linux-进程控制(1)_进程终止_02

程序运行结果,fork之后的代码,被执行了两次

Linux-进程控制(1)_错误码_03

写实拷贝

在fork之后,父子进程具有同样的页表,共享一份代码和数据。为了保证父子进程数据的独立性,页表中原来可以读写的数据权限,被设置成了只读权限,当父进程或子进程对这些数据做修改时,会引发缺页中断,进行写实拷贝。然后,再对需要修改的数据进行修改。下图中出现了一个新的名词,虚拟内存,你未来可能还会听到虚拟地址空间,这两者其实和进程地址空间是一个意思。

Linux-进程控制(1)_进程终止_04

创建多个进程

我们如何使用fork来创建多个进程呢?使用for循环即可,对于创建好的子进程,我们可以让它执行完runChild函数,再用exit函数来退出进程。

Linux-进程控制(1)_进程创建_05

编译运行,再把监控脚本一开

[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep;sleep 1;done;
  • 1.

我们就可以看到,5个进程被创建出来了,

Linux-进程控制(1)_进程控制_06

Linux-进程控制(1)_退出码_07

进程终止

我们写了这么久的C语言,每次写main函数的时候,最后,都会写一个return 0。你有没有想过为什么是return 0,而不是1或者2,甚至3,可不可以是其他的数?这个数字返回给谁?

在回答这两个问题前,我们得先了解一个进程退出有那几种情况

进程退出场景

进程退出场景,有三种。一是代码运行完毕,结果正确。二是代码运行完毕,结果不正确。第三种,代码异常终止。

那我们怎么知道进程是怎么退出的?为什么每次main函数都会return 0,这不就是在告诉我们,它是怎么退出的吗?main函数 返回值的本质 用来表示进程运行完成时是否正确,如果不是,可以用不同的数字,表示不同的出错原因!退出码0表示sucess的意思,程序正常运行结束。

那我们如何查看一个程序的退出码呢?

Linux-进程控制(1)_退出码_08

变量?

问号这个变量,会保存最近一个程序运行时的退出码,我们可以使用如下指令来查看,我们刚刚运行的myproc程序的退出码

[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ echo $?
  • 1.

退出码为0

Linux-进程控制(1)_进程控制_09

退出码

退出码,都是一个个的数字,计算机认识,可我们不认识呀!这怎么行?你爸问你,考了多少分?你说零。他怎么知道你零的意思是100分,不得直接把你打一顿。还记得我们在C语言时学过的strerror函数吗?

strerror

我们可以通过strerror函数来将退出码,转换成我们能看懂的信息

Linux-进程控制(1)_进程控制_10

写一段下面这样的代码

Linux-进程控制(1)_进程控制_11

编译运行,我们就知道不同的退出码对应着什么意思

Linux-进程控制(1)_错误码_12

上面这些退出码是为系统设置的,我们可以根据自己的需求设置一套自己的退出码

Linux-进程控制(1)_错误码_13

了解完退出码,我们也就明白,main函数每次return 0的意义所在。我们可以return不同的数字,表征不同的出错原因。

有一个与退出码很像的名词,叫错误码

错误码

什么是异常?本质就是一个程序没有跑完。

当一个程序发生异常了,它的退出码,就不重要了。这个程序异常了,它的退出码也可能是错的,所以,对于一个程序我们首先会关心它是否出现了异常。那我们怎么知道这个程序错在哪?通过错误码。程序出错了,会把错误码保存到全局变量errno中。这里需要注意,errno只会保存最近一次异常的错误码。也就是说,如果你的程序中发生了多次异常,只会保存最新一次异常的错误码。

我们可以简单演示异常错误

Linux-进程控制(1)_退出码_14

编译运行,我们发现申请内存失败了,我们查看退出码是1。可为什么,我们多查看几次,就变成了0。这是因为最近一次运行的程序是echo指令,它的确成功运行了

Linux-进程控制(1)_进程控制_15

如果我们想了解具体原因,需要使用strerror打印具体的出错信息。

Linux-进程控制(1)_进程控制_16

Linux-进程控制(1)_进程创建_17

我们换一份代码

Linux-进程控制(1)_进程控制_18

运行,这是段错误

Linux-进程控制(1)_进程创建_19

再换一份代码

Linux-进程控制(1)_退出码_20

这是浮点数格式错误,除零会出现数据溢出

Linux-进程控制(1)_进程终止_21

异常如何终止进程

还记得我们之间使用的kill命令吗?kill -9可以杀掉进程。它是怎么杀掉进程的?通过给进程发信号。那异常是怎么终止进程的?其实,也是通过信号,异常会转换成信号,来让进程退出。

光说无凭,我们来验证一下。首先,我们写一个死循环。

Linux-进程控制(1)_进程控制_22

其次,我们使用kill -l指令看看都有那些信号,sig是signal的简写,信号的意思。8号信号FPE不就是浮点数格式错误的简写吗?再看看11号信号SEG是不是有点眼熟?

Linux-进程控制(1)_错误码_23

我们分别发送一次8号信号和11信号,就得到了,段错误和浮点数格式错误的异常提示

Linux-进程控制(1)_进程终止_24

Linux-进程控制(1)_退出码_25

Linux-进程控制(1)_错误码_26

exit和return

exit可终止进程,return也可以进程,那它们之间有什么区别呢?

Linux-进程控制(1)_进程终止_27

编译运行,exit确实终止了进程,它传的参数13会作为退出码

Linux-进程控制(1)_进程控制_28

我们在exit前面加一个return

Linux-进程控制(1)_退出码_29

此时,退出码变成了12

Linux-进程控制(1)_进程创建_30

我们更换一下return和exit的位置。

Linux-进程控制(1)_进程终止_31

此时,退出码是多少,13

Linux-进程控制(1)_进程创建_32

从上面的例子中,你有没有看出啥?exit在任何地方被调用,都表示调用进程直接退出。return 只有在main函数的里才表示进程退出,在其他函数里只表示当前函数返回

exit和_exit

有一个和exit很像的函数叫_exit,在man手册第二章,是一个系统调用

Linux-进程控制(1)_进程创建_33

我们先来看几个代码的运行结果

Linux-进程控制(1)_进程控制_34

Linux-进程控制(1)_进程创建_35

Linux-进程控制(1)_进程创建_36

Linux-进程控制(1)_进程控制_37

前两个程序运行结果,没什么区别

如果我们把\n去掉呢?

Linux-进程控制(1)_进程创建_38

Linux-进程控制(1)_退出码_39

Linux-进程控制(1)_进程控制_40

Linux-进程控制(1)_退出码_41

调用exit的程序,依然打印了you can see me,但调用_exit的什么也没打印。这是为什么?我们之前说过,缓冲区中的数据,只有遇到\n才会自动向显示器刷新数据。_exit是直接终止了进程,没做别的。而exit,它在终止进程前,先把缓冲区的数据刷新了,才进行终止进程。

Linux-进程控制(1)_退出码_42

你们觉得缓冲区在用户区还是内核区?我敢肯定,这个缓冲区肯定在用户区,为什么这么说呢?exit是库函数,他没法直接终止进程,那它怎么终止进程,只能调用系统接口_exit。如果缓冲区在内核区,怎么会出现exit刷新缓冲区,而_exit没有,你要知道内核区只有系统调用才能访问呀!

Linux-进程控制(1)_错误码_43

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。