exit函数
之前说过进程正常结束的五种主法:
- 在main函数中执行return函数。
- 调用exit函数。这个函数被 ISO C定义并且会调用所有被atexit注册的句柄,之后再关闭所有标准I/O流。因为ISO C并不操作文件描述符、多进程和任务控制,所以这个函数对于UNIX系统来说它的定义并不完整。
- 调用_exit或_Exit函数。ISO C定义的_Exit函数用于结束进程但并不运行退出句柄或信号句柄(signa handlers)。标准I/O流是否被刷新这依赖于具体实现。在UNIX系统上,_Exit和_exit是相同的,都不刷新标准I/O流。_exit函数被exit调用,并处理unix系统特别定的细节。_exit是被POSIX.1定义的。
- 在进程的最后一个线程的开始例程(start routine)中执行return。线程的返回值并不做为进程的返回值。当最后一个线程从开始例程中返回时,进程的结束状态是0。
- 在进程的最后一个线程中调用pthread_exit函数。无论传给pthread_exit的参数是什么进程的结速状态一直都是0。
下面是三种非正常结束的情况:
- 调用abort。它产生SIGABRT信号。
- 当进程收到某些信号。信号能被进程自己产生,如调用abort,或由内核产生。由内核产生包含引用了不是自己地址空间的内存或除数为零。
- 最后一个线程响应中止请求。默认情况下,中止发生在推迟方式中:一个线程请求了一个稍后被其它线程结束的目标。
无论进程如何结束,内核最终都会执行相同的代码。内核关闭所有打开的描述符、释放占用的内存。
结束的进程都可以通知它的父进程,它自己是如何结束的。对于三个exit函数(exit, _exit , _Exit)是通过它们的参数传递退出状态的。在一些非正常终止的情况下,由内核而不是进程产生终止状态,来指出非正常结束的原因。在任意情况下父进程都可以通过wait或waitpid函数获得退出状态。
注意,我们要区分退出状态(来自三个exit函数中的参数,还是从main函数中的返回值)和终止状态。当内核最后调用_exit时,会将“退出状态”转换成“终止状态”。
如果父进程在子进程之前结束,那么init进程会变成该进程的父进程。这种方法可以保证每个进程都有父进程。
另一种情况是子进程先结束。如果子进程完全隐藏,父进程无法得到它的结束状态,但是父进程最终必须检查子进程是否结束。内核为每个正在结束的进程保存少量信息,所以当这个正在结束的进程的父进程调用wait或waitpid时,可以使用这些信息。这些信息的最小组成是由:进程ID、进程结速状态和进程的CPU时间总量。内核可以销毁进程使用的所有内存并且关闭所有打开的文件。在UNIX系统的术语中,当一个进程已经结束,但是它的父进程没有对它进行善后处理(获得子进程的有关信息,释放它占用的资源等)(but whose parent has not yet waited for it, is called a zombie),这被称作zombie。ps命令输出zombie进程的参数是Z。如果写了一个一直运行的程序,它fork了很多的子进程,除非父进程等待它的子进程并得到它们的结束状态,否则它们会变成zombie进程。
最后一种情况是:当一个被init进程领养的进程结束时会发生什么?会变成zombie进程么?当然不会!因为当init的一个子进程终止时,它会调用wait函数中一个去获得终止状态。这么做是防止系统被zombie堵塞。