本篇文章,继续来和大家分享与Linux相关的知识。本篇文章的内容主要会涉及到进程的创建和进程的终止。
进程创建
进程创建的方式,有两种,一种是我们在执行指令的时候,bash会创建子进程来帮助它执行指令。另一种是调用fork函数,来创建子进程。
fork
fork在前面的文章已经说过了,我们这里简单的回顾一下。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
· 分配新的内存块和内核数据结构给子进程
· 将父进程部分数据结构内容拷贝至子进程
· 添加子进程到系统进程列表当中
· fork返回,开始调度器调度
也就是fork在return之前,子进程就已经创建好了。
我们可以简单验证一下
程序运行结果,fork之后的代码,被执行了两次
写实拷贝
在fork之后,父子进程具有同样的页表,共享一份代码和数据。为了保证父子进程数据的独立性,页表中原来可以读写的数据权限,被设置成了只读权限,当父进程或子进程对这些数据做修改时,会引发缺页中断,进行写实拷贝。然后,再对需要修改的数据进行修改。下图中出现了一个新的名词,虚拟内存,你未来可能还会听到虚拟地址空间,这两者其实和进程地址空间是一个意思。
创建多个进程
我们如何使用fork来创建多个进程呢?使用for循环即可,对于创建好的子进程,我们可以让它执行完runChild函数,再用exit函数来退出进程。
编译运行,再把监控脚本一开
[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ while :; do ps axj | head -1 && ps axj | grep myproc | grep -v grep;sleep 1;done;
我们就可以看到,5个进程被创建出来了,
进程终止
我们写了这么久的C语言,每次写main函数的时候,最后,都会写一个return 0。你有没有想过为什么是return 0,而不是1或者2,甚至3,可不可以是其他的数?这个数字返回给谁?
在回答这两个问题前,我们得先了解一个进程退出有那几种情况
进程退出场景
进程退出场景,有三种。一是代码运行完毕,结果正确。二是代码运行完毕,结果不正确。第三种,代码异常终止。
那我们怎么知道进程是怎么退出的?为什么每次main函数都会return 0,这不就是在告诉我们,它是怎么退出的吗?main函数 返回值的本质 用来表示进程运行完成时是否正确,如果不是,可以用不同的数字,表示不同的出错原因!退出码0表示sucess的意思,程序正常运行结束。
那我们如何查看一个程序的退出码呢?
变量?
问号这个变量,会保存最近一个程序运行时的退出码,我们可以使用如下指令来查看,我们刚刚运行的myproc程序的退出码
[common_108@iZf8zaj27gxmvq7veqrekfZ test]$ echo $?
退出码为0
退出码
退出码,都是一个个的数字,计算机认识,可我们不认识呀!这怎么行?你爸问你,考了多少分?你说零。他怎么知道你零的意思是100分,不得直接把你打一顿。还记得我们在C语言时学过的strerror函数吗?
strerror
我们可以通过strerror函数来将退出码,转换成我们能看懂的信息
写一段下面这样的代码
编译运行,我们就知道不同的退出码对应着什么意思
上面这些退出码是为系统设置的,我们可以根据自己的需求设置一套自己的退出码
了解完退出码,我们也就明白,main函数每次return 0的意义所在。我们可以return不同的数字,表征不同的出错原因。
有一个与退出码很像的名词,叫错误码
错误码
什么是异常?本质就是一个程序没有跑完。
当一个程序发生异常了,它的退出码,就不重要了。这个程序异常了,它的退出码也可能是错的,所以,对于一个程序我们首先会关心它是否出现了异常。那我们怎么知道这个程序错在哪?通过错误码。程序出错了,会把错误码保存到全局变量errno中。这里需要注意,errno只会保存最近一次异常的错误码。也就是说,如果你的程序中发生了多次异常,只会保存最新一次异常的错误码。
我们可以简单演示异常错误
编译运行,我们发现申请内存失败了,我们查看退出码是1。可为什么,我们多查看几次,就变成了0。这是因为最近一次运行的程序是echo指令,它的确成功运行了。
如果我们想了解具体原因,需要使用strerror打印具体的出错信息。
我们换一份代码
运行,这是段错误
再换一份代码
这是浮点数格式错误,除零会出现数据溢出
异常如何终止进程
还记得我们之间使用的kill命令吗?kill -9可以杀掉进程。它是怎么杀掉进程的?通过给进程发信号。那异常是怎么终止进程的?其实,也是通过信号,异常会转换成信号,来让进程退出。
光说无凭,我们来验证一下。首先,我们写一个死循环。
其次,我们使用kill -l指令看看都有那些信号,sig是signal的简写,信号的意思。8号信号FPE不就是浮点数格式错误的简写吗?再看看11号信号SEG是不是有点眼熟?
我们分别发送一次8号信号和11信号,就得到了,段错误和浮点数格式错误的异常提示
exit和return
exit可终止进程,return也可以进程,那它们之间有什么区别呢?
编译运行,exit确实终止了进程,它传的参数13会作为退出码
我们在exit前面加一个return
此时,退出码变成了12
我们更换一下return和exit的位置。
此时,退出码是多少,13
从上面的例子中,你有没有看出啥?exit在任何地方被调用,都表示调用进程直接退出。return 只有在main函数的里才表示进程退出,在其他函数里只表示当前函数返回
exit和_exit
有一个和exit很像的函数叫_exit,在man手册第二章,是一个系统调用
我们先来看几个代码的运行结果
前两个程序运行结果,没什么区别
如果我们把\n去掉呢?
调用exit的程序,依然打印了you can see me,但调用_exit的什么也没打印。这是为什么?我们之前说过,缓冲区中的数据,只有遇到\n才会自动向显示器刷新数据。_exit是直接终止了进程,没做别的。而exit,它在终止进程前,先把缓冲区的数据刷新了,才进行终止进程。
你们觉得缓冲区在用户区还是内核区?我敢肯定,这个缓冲区肯定在用户区,为什么这么说呢?exit是库函数,他没法直接终止进程,那它怎么终止进程,只能调用系统接口_exit。如果缓冲区在内核区,怎么会出现exit刷新缓冲区,而_exit没有,你要知道内核区只有系统调用才能访问呀!
好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。