终止进程
终止进程
POSIX和C89都定义了一个标准函数,可以终止当前进程:
1 |
|
对exit()的调用通常会执行一些基本的关闭步骤,然后通知内核终止这个进程。这个函数无法返回错误值——实际上他也从不返回。因此在exit()之后执行任何指令都没有意义
参数status用于标识进程的退出状态。EXIT_SUCCESS和EXIT_FAILURE这两个宏分别表示成功和失败,而且时可移植的。在Linux中,0通常表示成功,非0值表示失败
在终止进程之前,C库会按顺序执行以下关闭进程的步骤:
- 调用任何由atexit()或on_exit()注册的函数,和在系统中注册时顺序相反
- 清空所有已打开的标准I/O流
- 删除由tmpfile()函数创建的所有临时文件
这些步骤完成了用户空间所需要做的所有工作,最后exit()会调用系统调用_exit(),内核可以处理终止进程的剩余工作
1 |
|
当进程退出时,内核会清理进程所创建的、不再使用的所有资源。这包括但不局限于:分配村村、打开文件和System V的信号量。清理完成后,内核会摧毁进程,并告知父进程其子进程已经终止
应用可以直接调_exit(),但这通常不合适,绝大多数应用在完全退出前需要做一些清理工作
终止进程的其他方式
终止进程的典型方式不是通过显示系统调用,而是采用“直接跳到结束(falling off the end)”的方式。在C和C++中,当main()函数返回时会发生这种情况。然而,这种直接跳到结束的方式还是会执行系统调用:编译器会在最后关闭代码后插入隐式exit()调用。在main()函数返回时显式给出返回状态,或这调用exit()函数,这时一个良好的编程习惯
如果进程接收到一个信号,并且这个信号对应的处理函数是终止进程,进程也会终止,这样的信号包括SIGTERM和SIGKILL
最后一种进程终止方式是被内核强制终止。内核可以杀死执行非法指令、引起段错误、耗尽内存、消耗资源过多的任何进程
atexit()
系统调用atexit()是由POSIX 1003.1-2001所定义,Linux也实现了该函数。它是用来注册一些进程结束时需要调用的函数:
1 |
|
atexit()调用成功时,会注册指定的函数作为终止函数,在程序正常结束时运行,如果进程调用了exec函数,会清空所注册的函数列表。如果进程是通过信号结束,就不会调用这些注册函数
指定函数必须是无参且没有返回值的函数
函数调用的顺序和函数注册的顺序相反。也就是说,这些函数是存储在栈中,以后进先出的方式调用。注册的函数不能调用exit(),否则会导致递归调用死循环。如果要提前结束进程,应该调用_exit()。一般不推荐这种行为,因为它会是的一些重要的关闭函数不会被调用到
POSIX标准要求atexit()至少支持注册ATEXIT_MAX个注册函数,而且这个值至少是32。具体的最大值可以通过sysconf()得到,参数是_SC_ATEXIT_MAX
成功时,atexit()返回0。错误时,返回-1:
1 |
|
on_exit()
SunOS 4 自己定义了一个和atexit()等价的函数:on_exit()。Linux的glibc也支持该函数:
1 |
|
该函数的工作方式和atexit()函数一样,只是注册的函数形式不同:
1 |
|
参数status是传给exit()的值或者从main()函数返回的值。arg是传给on_exit()的第二个参数。需要注意的是,当调用该函数时,要保证arg所指向的内存地址必须是合法的
最新版的Solaris不再支持on_exit()函数了。因此,应该使用和标准兼容的atexit()
SIGCHILD
当一个进程终止时,内核会向其父进程发送SIGCHILD信号。默认情况下,父进程会忽略此信号量,也不会采取任何操作。但是进程可以通过signal()或sigaction()系统调用来处理这个信号
SIGCHILD信号可能会在任意时刻产生,并在任意时刻被传递给父进程,因为对于父进程而言,子进程的终止是异步的。通常情况下,父进程都希望能更多地了解到子进程的终止,或是显示等待子进程终止。这可以通过系统调用来实现