进程退出
linux下进程退出有8中方式
- 从main函数返回
- 调用exit函数
- 调用_exit函数或者_EXIT函数
- 最后一个线程从启动历程返回
- 最后一个线程调用pthread_exit
- 调用abort函数
- 接到一个信号并终止
- 最后一个线程对取消请求做出响应
这里区分一下exit和_exit
exit
我们知道父进程要wait子进程的退出状态,在子进程退出到父进程调用wait()期间,子进程就处于僵尸状态。因此,exit()将进程正常退出,并将(status & 0377)返回到父进程的wait(),其中status可以是EXIT_SUCCESS或EXIT_FAILURE。
exit()在返回到父进程前要做的事情包括:按出栈顺序(反序)依次调用atexit()/on_exit()注册的函数。然后将所有打开的stdio流进行flush并关闭,如果有通过tmpfile()创建的文件也会被删除。(这里要注意,如果你注册的某个清理函数中调用_exit()或把自己kill结果退出了,那么后面的清理函数以及刷stdio等就不会执行了。所以,清理函数里不要调用exit()或_exit()。)
子进程exit()后会发送一个SIGCHLD信号给父进程(如果父进程设置了SA_NOCLDWAIT,那这个信号是否发送是未定义的)。
如果子进程在exit()后,父进程已经在等待(wait()系列函数)子进程的状态,或者父进程设置了SA_NOCLDWAIT或者将SIGCHLD的处理置为SIG_IGN,子进程都会立即退出。而如果父进程既没有wait,又没有设置忽略子进程退出,子进程就会变成僵尸进程(除了一个字节的exit status,什么都没有,用来确保以后某个时刻父进程wait的时候仍能拿到status)。
_exit 和 _Exit
而_exit()是企图让程序“立即”退出,它不会调用atexit()/on_exit()注册的函数。它也是会关闭自己打开的所有文件描述符的,但是否flush stdio以及是否删除tmpfile创建的文件则是与具体实现相关的(即没有明确规定)。
当然,由于_exit()会关闭文件描述符,所以可能也会有些delay(例如还没写完就close),如果想达到“立即”的目的,在_exit()之前调用一下tcflush()可能会有帮助。
atexit
我们可以用atexit()注册一个或多个函数退出清理函数(或者on_exit()但这个函数不建议用),这些清理函数按照注册时的反顺序,在exit()或main函数return时被调用。
fork子进程时,这些函数会被继承到子进程。而exec系列执行成功后,所有函数会被清空。
总结
exit()和_exit()以及_Exit()函数的本质区别是是否立即进入内核,_exit()以及_Exit()函数都是在调用后立即进入内核,而不会执行一些清理处理,但是exit()则会执行一些清理处理(调用执行各终止处理程序,关闭所有标准I/O流),这也是为什么会存在atexit()函数的原因,因为exit()函数需要执行清理处理,需要执行一系列的操作,这些终止处理函数实际上就是完成各种所谓的清除操作的实际执行体。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
void func1(void)
{
printf("func1\n");
}
void func2(void)
{
printf("func2\n");
}
int main()
{
atexit(func1);
atexit(func2);
printf("hello\n");
exit(0);
}
执行结果:
hello
func2
func1
一个进程可以登记若干个函数,这些函数由exit自动调用,这些函数被称为终止处理函数,atexit函数可以登记这些函数,exit调用终止处理函数的顺序和atexit等级的顺序相反,如果一个函数被登记多次,也会被多次调用。