进程就像人的生命一样,有出生,也有死亡。我们可以通过fork或vfork函数来创建一个进程,同样也有方法来终止一个进程。
在linux中有两种进程终止方式:正常终止和异常终止。
正常终止有5种方式:
(1)在main函数中执行return语句。
(2)调用exit函数。
(3)调用_exit或_Exit函数。
(4)、(5)为线程终止,再次不做介绍。
异常终止
(1)调用abort。
(2)进程接受某些信号,该信号使程序终止。
(3)线程的异常终止,不作介绍。
不管进程是哪种方式终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开的文件描述符,释放它所使用的内存和其他资。
(1)exit和return 的区别:
a.exit是一个函数,有参数。exit执行完后把控制权交给内核。
b.return是一个关键字,函数执行完后的返回。return执行完后把控制权交给调用函数。
注意:成功时,exit(0),或者是从mian()函数返回0,具有同等的效应。
(2)exit和abort的区别:
a.exit是正常终止进程
b.abort是异常终止。
现在我们重点了解exit()和_exit()函数:
这两个函数都是用来终止进程的,exit()先要进行处理操作,然后将控制权交给内核,而_exit()执行后立即返回内核。对于这类函数终止进程,可以从这两个方面来看,终止进程首先要完成用户空间进程终止工作,这由exit()函数来完成。其次,有内核完成内核级别的进程终止工作,这通过_exit()函数来实现。
对于exit()函数主要完成用户空间所需要做的事情,原型如下:
#include <stdlib.h>
void exit (int status);
Status参数用来标示进程终止的状态。其它进程—例如shell的用户可以检测这个值。
EXIT_SUCCESS和 EXIT_FAILURE两个宏分别标示成功和失败,而且是可以移植的。在Linux中,0通常表示成功;非0值,例如1或-1,表示失败。进程成功退出时只需简单写上:exit (EXIT_SUCCESS);
如果这些函数在调用时候不带终止状态,或者main执行了一个无返回值的return语句,或者main没有声明返回类型为整型,则该进程的终止状态是未定义的。但是,若main返回类型是整型,并且main执行到最后一条语句时返回(隐式返回),那么该进程的终止状态是0.
对exit()的调用通常会执行一些清理处理,然后通知内核终止这个进程。这些清除处理时这样来进行的:
(1)exit函数逆序调用通过调用atexit或者on_exit函数登记的终止处理程序;
(2)然后按需多次调用fclose,关闭所有标准I/O流;
(3)删除由tmpfile所创建的所有临时文件。
完成在用户空间所需要做的事情,这样exit()就可以调用_exit()来让内核处理终止进程的剩余工作。
对于_exit()涵数
#include<unistd.h>
void _exit(int status);
status参数与exit()函数中的status参数有同样的意义。
进程退出时,内核会清理进程所创建的、不在用到的任何资源。这包括(但不仅仅限于这些):关闭进程打开的所有的进程描述符、清理内存、system V的信号量以及其它的一些内核清理函数。注意:但不会刷新流(stdin, stdout, stderr ...),这个工作在用户空间由exit()函数完成,exit()是在_exit()函数上的封装,其会调用_exit(),并在调用之前先刷新流。理完成之后,内核摧毁进程,并告知父进程其子进程已经结束。应用进程可以直接调用_exit,进行内核级别的进程处理,但这是不合适的。由于Linux的标准函数库中,有一种被称作“缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续的读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。这种技术大大增加了文件读写的速度,但也给编程代来了一点儿麻烦。比如有一些数据,认为已经写入了文件,实际上因为没有满足特定的条件,它们还只是保存在缓冲区内,这时用_exit()函数直接将进程关闭,缓冲区的数据就会丢失。因此,要想保证数据的完整性,就一定要使用exit()函数。注意:vfork的使用者终止进程时必须使用_exit(),而不是exit().后来,ISO C99标准中增加了_Exit()函数,它的功能和_exit是一样的:
#include<stdlib.h>
void _Exit(int status);