目录
一、进程创建
1.认识fork()函数
fork()函数想必不用多说了,就是一个创建新的子进程的函数。在进程中我们已经用过很多次了。
(1)fork()函数有两个返回值
该函数在创建成功时会给父进程返回子进程pid,给子进程返回0。该函数有两个返回值的原因我们也知道是“进程具有独立性”。
但该函数要分别返回子进程pid和0的原因就在于“子进程可以有多个,父进程只有一个”。就好比我们只有一个亲生父亲,但是我们的父亲可能不只我们一个儿子。父子进程也是一样,对于父进程来说,子进程有多个,为了让父进程区分子进程,fork()函数就为父进程返回子进程的pid。如果子进程之下没有子进程,则给该子进程返回0。就好比父亲为了区分儿子,给儿子取了不同的名字,但是儿子之下没有儿子的话,儿子就没有给儿子取名的必要了
(2)同一个值保存不同的fork()返回值
虽然fork()创建的子进程与父进程“共享代码”,但是在遇到父子进程产生不同值的代码时,操作系统就会为对应的进程在物理内存中重新开一块空间供该进程使用。
但是,大家有没有想过,这个拷贝开空间的操作是在有不同进程共享代码时执行的。我们用来接收该函数的返回值的函数在定义时是只有一个进程的。那为什么这一个变量能够接收两个返回值呢?
如下图的程序:
父子进程在执行了if判断时会根据自己得到的返回值进行不同的条件判断。但是在fork()没有返回的时候,此时应该只有一个进程才对,那为什么变量id可以接收两个变量呢?如果理解进程的话大家应该就很清楚,此时肯定不是只存在一个进程,而是存在两个进程。
而此时存在两个进程的原因就是,当一个函数准备return的时候,其内部的核心代码已经执行完了。我们假设fork()函数内部的逻辑是如下图所示:
那么在即将执行最后一步“return pid”的时候,前面的所有步骤都执行完了。此时系统中的子进程已经被创建出来了,仅仅只剩下返回值还没有返回。因此,id能接收两个值的原因就在于fork()即将执行return的时候,已经有两个进程在运行id,此时id的值会出现不同,因此出现“写时拷贝”。对于后接收返回值的进程,操作系统会给它分配一块存储id的空间,将id的值拷贝过去,然后让进程去使用这块空间并修改id的值
如果想知道自己的系统最多能同时存在多少个进程,可以执行如下程序:
要注意,该程序可能会使你的系统挂掉,谨慎执行
二、进程终止
1.进程退出码
在以前,我们每写一个程序时,都会在主函数main()中写上“return 0”。大家有没有想过,为什么我们要写这个“return 0”?这其实就是因为,我们写一个程序,最终这个程序运行结束后,我们要判断该程序执行的情况如何,是否执行成功,结果是否正确。而标识该进程执行结果是否正确的值就是进程退出码,即我们在main()函数中写的return返回值
2.查看进程退出码
假设我们现在有以下程序:
此时我们的main()函数中就会根据程序执行结果的不同返回不同的值。 该程序因为少加了一次100,因此结果不会是5050。在这里我们能够知道1到100相加的结果5050,但是在绝大多数情况下我们是不知道执行结果的值的。这里我们为了方便测试,就假定了我们知道执行结果的值的情况。
在这里我们return的0和1就是该进程的进程退出码,标定了进程执行结果是否正确
当我们执行该程序后,该程序虽然能够正常运行,但是我们并不知道它的执行结果是否正确。于是我们通过“echo $?”命令来查看该进程的进程退出码:
此时的返回结果为1,即执行结果错误。
这里我们所使用的“echo $?”命令中的“$”是提取变量名,“?”则是用于永久记录最近一个进程在命令行中执行完毕时对应的退出码的环境变量,即main()函数中的return返回值
例如,我们在执行了上述myprocess程序之后,再次执行“echo $?”命令:
当我们再次执行该命令后,就会发现进程退出码变成了0。原因就是“echo $?”命令也是一个进程。执行该命令后,最近一次进程就变成了“echo $?”,而非我们的myprocess进程。同时该命令是正确执行了的,因此返回码是0
这也就说明了“?”变量可以永久记录最近一个进程在命令行中执行完毕时对应的退出码
3.根据不同情况返回不同的进程退出码
进程退出码“0”表明的就是“程序执行成功”,“非0”表示的则是程序失败,“非0”的具体数字,如1,2这种,表示的就是具体错误
以后我们在写程序时,就最好不要每个程序的返回值都是0,而是要根据需要来写。当我们不关心进程退出码时,直接return 0 即可。但是如果我们需要关心进程退出码给我们的反馈,就要返回特定的数据表明特定的错误
(1)进程退出码的不同含义
在系统中,进程退出码使用的是数字表示,虽然这个数字对于系统来讲很容易理解,但对于用户来讲却并不好理解。就好比我们在打10086客服电话咨询时,对方会先有一个语音播报,告诉你按1是查询话费,按2是查询流量,按#是转接人工客服。它为什么不直接说请按1,2,#选择服务,而要告诉你每个选项对应的服务呢?就是因为如果不告诉你对应数字的服务而只告诉你数字你很难明白对应数字的信息是什么
在系统中也是一样,退出码的数字用户是很难理解的,因此每个退出码,都必须要有对应的退出码的文字描述,以供用户参考
当然,为了应对不同的情况,进程退出码的含义可以自定义,也可以使用系统的映射关系。不过系统映射的方式我们不太常使用,只有在频繁调用了库函数之类的场景才会较多使用
如果我们想查看系统中的进程退出码的不同含义,就可以使用“strerror()”函数来查看:
该函数会将对应的数字转换成进程信息。我们可以写以下程序来查看:
运行该程序后,我们往下翻,就可以发现,在系统中一共定义了134种进程码含义:
这些进程退出码标识了不同的进程退出信息。比如我们在命令行上执行ls命令并随便输入内容:
此时就会出现提示没有该文件或目录的错误信息,然后进程退出码为2,我们在看看之前打印的退出码信息:
这里面的退出码2就刚好对应了ls命令报出的错误信息。这也说明了命令执行起来时其实也是进程
4.进程退出的不同情况
(1)进程退出情况
在进程中,只有三种进程退出情况。分别是:
1.代码运行完毕,结果正确,进程退出。此时就是return 0
2.代码运行完毕,结果错误,进程退出。此时就是return 非0。进程退出码起作用
3.代码没有运行完,程序异常,进程退出。在这种情况下,退出码无意义
(2)进程退出方法
要退出一个进程,一般有三种方法。
1.main()函数return返回
这种方法也是我们之前写程序时使用的最多的方法。注意,需要主函数main()return返回,而非其他函数调用返回
2.任意地方调用exit(code)函数
该函数我们之前并没有怎么用过,也没怎么见过。我们查看该函数的信息:
该函数可以在任意地方使用,传入对应的进程退出码,使进程直接退出并返回对应的退出码。
我们以以下程序来进行测试:
在以前,我们调用函数一般是写的return。但是这次我们将return修改为调用exit()函数。然后我们来执行该程序:
可以看到,该程序在进入对应的函数调用后碰到exit()函数就直接结束了,因此在该函数调用下面的内容并没有打印。且进程退出码就是我们所设置的10
3.使用_exit()函数
_exit()函数同样是用于终止进程的:
我们将上面的那个程序的exit()修改为_exit():
然后我们再来运行该程序:
可以看到, 这个函数同样是终止程序,但此时看起来好像和exit()函数没有什么差别。因此,我们再用以下一个程序来测试:</