全部博文(842)
分类: 系统运维
2012-05-14 16:48:27
正如我们在7.3节描述的,一个进程可以通过以下5种方式退出:
1、从main函数里执行一个return。正如我们在7.3节看到的,这和调用exit等价。
2、调用exit函数。这个函数由ISO C定义并包含调用所有通过调用atexit注册的退出处理器,和关闭所有的标准I/O流。因为ISO C不处理文件描述符、多进程(父进程和子进程)、和工作控制,所以这个函数的定义对于UNIX系统来说是不完全的。
3、调用_exit或_Exit函数。ISO
C定义_Exit为一个进程提供不运行exit处理器或信号处理器的终止方法。标准I/O流是否被冲洗取决于实现。在UNIX系统上,_Exit和
_exit是同义词,并不冲洗标准I/O流。_exit函数被exit函数调用并处理UNIX系统相关的细节;_exit由POSIX.1规定。在多数
UNIX系统实现里,exit在标准C库里是一个函数,而_exit是一个系统调用。
4、在进程的最后一个线程的start函数里执行一个return。但是这个线程的返回值不被用途进程的返回值。当最后一个线程从它的start函数返回时,进程以终止状态0退出。
5、在进程的最后一个线程里调用ptherad_exit函数。和上一种情况一样,进程的返回状态始终为0,而不管传递给pthread_exit的参数。我们将在11.5节看到更多关于pthread_exit的细节。
以下是三种异常退出的形式:
1、调用abort。这是下一项的特殊情况,因为它产生SIGABRT信号。
2、当进程收到特定信号时。(我们在第10章描述更多细节。)系统可以由进程本身产生--例如,通过调用abort函数--由别的进程产生,或者由内核产生。由内核产生信号的例子包括进程引用一块不在其地址空间的内存地址,或者尝试除以0。
3、最后一个线程响应一个取消请求。默认情况下,取消发生在一个延后的行为:一个线程请求另一个取消,在一段时间后,目标线程终止。我们将在11.5节和12.7节更详细讨论取消请求。
不管进程如何终止,内核总是执行相同的代码。内核代码为进程关闭所有打开的描述符,释放它使用的内存,等等。
对于之前任何一种情况,我们都想终止的进程能够通知它的父进程它是如何终止的。对于三个exit函数(exit,_exit和_Exit),这是通过函数
的参数传递退出状态来完成的。然而,在异常终止情况,内核,而不是进程,产生一个指明异常终止原因的终止状态。在任何情况下,进程的父进程都可以从
wait或waitpid函数(在下节描述)里获得终止状态。
注意我们把由exit函数的参数或main的返回值表示的退出状态,和终止状态区分开来。当_exit最终被调用时,内核把退出状态转换成终止状态。下表描述了父进程可以检查一个子进程的终止状态的各种方法。如果子进程正常终止,父进程可以得到子进程的退出状态。
检查wait和waitpid返回的终止状态的宏 | |
宏 | 描述 |
WIFEXITED(status) | 如果status由正常终止的子进程返回时为真。在这种情况下,我们可以执行WEXITSTATUS(status)来得到子进程传给exit、_exit或_Exit的参数的低8位。 |
WIFSIGNALED(status) | 如果status由因为收到一个它没有catch的信号而异常终止的子进程返回时为真。在这种情况下,我们可以执行WTERMSIG(status)来得 到导致终止的信号号。此外,一些实现(而不是SUS)定义了宏WCOREDUMP(status),如果一个终止进程的核心文件被产生时会返回true。 |
WIFSTOPPED(status) | 如果status由当前停止的子进程返回时为真。在这种情况下,我们可以执行WSTOPSIG(status)来得到导致子进程停止的信号号。 |
WIFCONTINUED(status) | 如果status由在工作控制停止后继续的子进程返回时为真(POSIX.1的XSI扩展,仅限于waitpid。) |
当我们在讨论fork函数的时候,很明显在fork调用后子进程有一个父进程。现在我们正在讨论的是把终止状态返回给这个父进程。然而如果父进程比子进程
更早终止会发生什么呢?答案是init进程变成任何其父进程终止的进程的父进程。我们说进程已经由init继承。通常发生的事是不管何时一个进程终止,内
核会遍历所有的活动进程来看正在终止的进程是否是任何仍存在的进程的父进程。如果是的话,存活下来的进程的父进程ID被变为1(init的进程ID)。通
过这种方法,我们被保证每个进程都有一个父进程。
另一个我们必须担心的情况是当一个子进程在其父进程之前终止。如果当父进程准备来来检查子进程是否已经终止时,子进程已经完全消失,那么父进程不能得到它
的终止状态。内核为每一个终止的进程保存了一小量的信息,所以当终止进程的父进程调用wait或waitpid时可以使用那个信息。这个信息至少由进程
ID、进程的终止状态和这个进程使用过的CPU时间。内核可以丢弃这个进程使用的所有的内存并关闭它打开的文件。在UNIX系统术语里,一个已终止而其父
亲还未等待它的进程,被称为僵尸进程。ps命令用Z打印一个僵尸进程的状态。如果我们写了一个长时间运行并fork了很多子进程的程序,除非我们等待它来
得到它们的终止状态,否则它们都变为僵尸进程。
一些系统提供了避免创建僵尸进程的方法,我们会在10.7节介绍。
最后需要考虑的情况是当一个由init继承的进程终止时会发生什么?它会就一个僵尸吗?答案是“否”,因为init被设定为无论何时它的子进程终
止,init调用某个wait函数来得到终止状态。通过这样做,init避免系统被僵尸阻碍。当我们说“init的某个子进程”时,我们表示任何一个由
init直接产生的进程(比如getty,在9.2节讨论),或一个其父进程终止随后继承init的进程。