目录
[2.1 进程退出的场景](#2.1 进程退出的场景)
[2.2 main()函数的返回值](#2.2 main()函数的返回值)
[2.3 用exit和_exit进行退出](#2.3 用exit和_exit进行退出)
[2.4 进程异常终止](#2.4 进程异常终止)
一、进程的创建
关于一个进程是如何被创建出来的,我们已经有所介绍这里我在给大家补充些东西。
首先,我们知道当一个子进程被父进程创建时,子进程会拷贝父进程的PCB,进程地址空间,页表等,然后跟父进程指向同一块物理地址执行接下来的代码。而如果,当我们的子进程进行写入、修改某些值时,就会触发写时拷贝,从而重新申请物理地址空间,拷贝父进程的代码,修改页表。很明显,这一过程我们用户是不知道的,也就是说这一切都是操作系统自行完成的。
那写时拷贝这一过程是怎么实现的呢?这里先来给大家初步的说明一下:当我们的父进程创建子进程时,会将自己的读写权限修改成只读,然后在创建我们的子进程。用户并知道这一点,那我们的用户就可能对某一批数据进行写入!那么我们的页表就会因为权限问题而出错(这里出错会有两个原因---1、是真的出错了---2、不是真的出错,是触发我们进行重新申请内存拷贝内容的策略机制),那么我们的OS就会进行介入,从而进行我们的写时拷贝。也就是说我们通过将内容权限修改成只读,就可以触发异常,从而让OS来介入处理,达到写时拷贝的目的。这里上一张图,来加深大家的理解:
二、进程的终止
2.1 进程退出的场景
要想认识进程的终止,我们首先就要先清楚进程是什么时候退出的呢,或者说会在哪些场景下退出呢?
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
以上就是进程退出的场景。一个进程运行完毕无非就是两种结果,一是结果正确,二是结果错误。还有一种特殊的情况就是代码一场终止了。那么接下来我们就需要明确一个概念,在我们多进程环境中,我们创建子进程的目的是什么?
当然是去帮我们干事,那么子进程把事情办的怎么样呢?(我们需要知道),同样其父进程也需要知道。 那么我们的父进程该怎么去知道我们的子进程完成的怎么样呢?根据子进程的返回值。说到这里,我们就可以来说一说,我们的main()函数了。
2.2 main()函数的返回值
其实我们的main()函数也是有返回值的,因为其最终也会是一个进程,像我们每次写代码在main中都会有的return 0。这个0就是我们main()函数的返回值,也可以叫做该main()函数所在进程的退出码。
其中 0 表示success而 !0 则表示的是 Failed----光知道进程失败肯定是不够的,我们肯定还需要知道我们的进程是因为什么而失败的,所以我们就可以用1、2、3....这些不同的数字来表示不同的退出原因,也就是说我们返回什么都可以,只是不同的值会代表不同的涵义。
而我们的父进程通过获取子进程的退出码,来判断子进程的运行结果。
那么返回 1,2,3...这些值都代表着什么呢?这里就需要提到我们的老朋友了--strerror这个函数让我们先通过定义来回顾一下这个函数吧。
其可以将错误码转换成对应的错误信息,那么接下来让我们通过实操看看到底都有些什么错误信息吧。同样,我们先写一段代码:
让我们看看结果:
可以看到有非常多的结果,不过也不是无穷无尽的,在133之后是没有有效信息的。当然这些错误信息我们还可以自己进行定义。
这里提及一个指令,可以查看最近一个进程的退出码。
echo $? // ?这个环境变量中就是保存着最近一个进程执行完毕时的退出码
2.3 用exit和_exit进行退出
我们可以通过man这个指令先简单的看一下这个指令
可以发现其是我们 <stdlib.h> 库里的一个函数,其作用也是使我们的进程退出,同样会返回一个值。那它和我们的return比较又有什么区别呢。
在main()函数中直接进行直接return,表示的是该进程的退出,返回退出码。
在其他函数中进行return,表示的是函数的调用结束。
但是exit不同,在任意地点调用都表示进程的退出,不进行后续执行。
这里我们同样可以用 echo $? 去获取该进程的退出码。那么这个_exit又是个什么东西呢,它与exit的区别又在哪里呢?那就先查一查手册吧:
可以发现其大致的功能是和exit是一致的,那他们的区别是什么呢?让我们通过实际运用来观察,先写一段代码:
这一段代码的结果是这样的:
让我们在试试_exit:
修改了,我们会发现,我们的hello world 没有了,这是为什么呢?相信细心的读者已经发现了我在这段代码中并没有刷新我们的缓冲区,也就是说我们的exit是会帮助我们刷新缓冲区的,而我们的_exit却不会,这就是他们的区别,这也就说明我们目前所知道的缓冲区,绝对不在操作系统内部!!。
在更深一点理解,就是exit 是库函数,而_exit是系统调用,也就是说,其实我们的exit可以简单的认为是对我们的系统调用进行的一个封装。关于这点,我们之后再详细说明。
2.4 进程异常终止
一个进程如果异常终止了,其实进程还会返回一个值,这个值就是信号,信号为0表示没有异常,为其他值,就表示发生了异常。发生异常,退出码就没有意义了。
那么,哪些情况会导致我们的进程异常呢?比如说:我们主动通过信号来杀死该进程导致其异常终止;或者访问空指针;数组越界等等。