8.5 exit函数如同7.3节所述,进程有三种正常终止法及两种异常终止法。

(1)正常终止:

(a) 在main函数内执行return语句。如在7.3节中所述,这等效于调用exit。

(b) 调用exit函数。此函数由ANSI C定义,其操作包括调用各终止处理程序(终止处理程序在调用atexit函数时登录),然后关闭所有标准I/O流等。因为ANSI C并不处理文件描述符、多进程(父、子进程)以及作业控制,所以这一定义对UNIX系统而言是不完整的。

(c) 调用_exit系统调用函数。此函数由exit调用,它处理UNIX特定的细节。_exit是由POSIX.1说明的。

(2)异常终止:

(a) 调用abort。它产生SIGABRT信号,所以是下一种异常终止的一种特例。

(b) 当进程接收到某个信号时。(第10章将较详细地说明信号。)进程本身(例如调用abort函数)、其他进程和内核都能产生传送到某一进程的信号。进程越出其地址空间访问存储单元,或者除以0,内核就会为该进程产生相应的信号。不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等等。

对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于exit和_exit,这是依靠传递给它们的退出状态(exit status)参数来实现的。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数(在下一节说明)取得其终止状态。注意,这里使用了“退出状态”(它是传向exit或_exit的参数,或main的返回值)和“终止状态”两个术语,以表示有所区别。在最后调用_exit时内核将其退出状态转换成终止状态(回忆图7-1)。

下一节中的表8-1说明了父进程检查子进程的终止状态的不同方法。如果子进程正常终止,则父进程可以获得子进程的退出状态。在说明fork函数时,一定是一个父进程生成一个子进程。上面又说明了子进程将其终止状态返回给父进程。但是如果父进程在子进程之前终止,则将如何呢?其回答是对于其父进程已经终止的所有进程,它们的父进程都改变为init进程。我们称这些进程由init进程领养。其操作过程大致是:在一个进程终止时,内核逐个检查所有活动进程,以判断它是否是正要终止的进程的子进程,如果是,则该进程的父进程ID就更改为1 (init进程的ID)。这种处理方法保证了每个进程有一个父进程。

另一个我们关心的情况是如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?对此问题的回答是内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到有关信息。这种信息至少包括进程ID、该进程的终止状态、以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储器,关闭其所有打开文件。在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死。