终止进程,回收子进程
进程终止
#include<stdlib.h>
void exit(int status);
对exit()的调用通常会执行一些基本的关闭步骤,然后通知内核终止这个进程.参数status用于标识进程的退出状态.具体来,会返回给父进程status&0377这个值.
EXIT_SUCCESS和EXIT_FAILURE这两个宏分别表示成功和失败,而且是可移植的.在LINUX中,0通常表示成功;非0值,如1或-1,表示失败.
因此,成功退出时,只需要加上这样一行代码,
exit(EXIT_SUCCESS);
在终止进程之前,C库会按顺序执行以下关闭进程的步骤.
1.调用任何由 atexit() 或 on_exit()注册的函数,和在系统中注册时顺序相反.
2.清空所有已打开的标准I/O流.
3.删除由tmpfile()函数创建的所有临时文件.
这些步骤完成了在用户空间需要做的所有工作,最后exit()会调用系统调用_exit(),内核可以处理终止进程的剩余工作.
#include <unistd.h>
void _exit(int status);
当进程退出时,内核会清理进程所创建的\不再使用的所有资源.这包括但不限于:分配内存,打开文件和Symstem V的信号量.清理完成后,内核会摧毁进程,并告知父进程其子进程已经终止.
应用可以直接调用_exit(),但这通常并不合适:绝大多数应用在完全退出前需要做一些清理工作,例如清空stdout流.需要注意,vfork()用户终止进程时必须调用_exit(),而不是exit().
终止进程的其他方式
终止进程的典型方式不是通过显示系统调用,而是采用"直接跳到结束"的方式.在C和C++语言中,当main()函数返回时会发生这种情况.然而,这种直接跳转的方式还是会执行系统调用:编译器会在最后关闭代码后插入隐式exit()调用.
如果一个进程收到一个信号,这个信号对应的处理函数是终止进程,进程也会终止.这样的信号包括:SIGTERM和SIGKILL.
最后一种进程终止方式是被内核强制终止,内核可以杀死执行非法指令,引起段错误,耗尽内存,消耗资源过多的任何进程.
atexit()
系统调用atexi()是用来注册一些在进程结束时要调用的函数:
#include<stdlib.h>
int atexit(void (*function)(void));
atexit()调用成功时,会注册指定的函数作为终止函数,在程序正常结束时(即进程通过调用exit()或从main()函数中返回)运行。如果进程调用了exec函数,会清空所注册的函数列表(这些函数不再存在于新进程的地址空间中)。如果进程是通过信号结束,就不会调用这些注册的函数。
指定函数必须是无参的,且没有返回值。函数形式如下:
void my_function(void);
函数调用的顺序和函数注册的顺序相反,可见这些函数是存储在栈上的,以后进先出的方式调用(LIFO)。注册的函数不能调用exit(),否则会导致递归调用死循环。如果需要提前结束进程,应该调用_exit()。一般不推荐这种行为,因为它会使得一些重要的关闭函数不会被调用到。
POSIX标准要求atexit()至少支持注册ATEXIT_MAX个注册函数,而且这个值至少是32。
成功时,atexit()返回0。错误时,返回-1。
on_exit()
#include<stdlib.h>
int on_exit(void (*function)(int,void *),void *arg);
该函数的工作方式和atexit()一样,只是注册函数的形式不同;
void my_function(int status,void *arg)
参数status是传给exit()的值或者是从main()函数返回的值。arg是传给on_exit()的第二个参数。需要注意的是 ,当调用该注册函数时,要保证arg所指的内存地址必须是合法的。
SIGCHLD
当一个进程终止时,内核会向其父进程发送SIGCHILD。默认情况下,父进程会忽略此信号量,也不会采取任何措施但是,进程也可以选择通过signal()或sigaction()系统调用来处理这个信号。
SIGCHLD信号可能会在任意时刻产生,并在任意时刻被传递给父进程,因为对于父进程而言,子进程的终止是异步的。通常情况下,父进程都希望能更多的了解子进程的终止,或者显示的等待子进程终止。
等待子进程终止
可以通过信号通知父进程,但是很多父进程想知道过于子进程终止的更多信息——比如子进程的返回值。
如果终止时,子进程完全消失了,父进程就无法获取关于子进程的任何消息。所以,UNIX的最初设计者们决定:如果子进程在父进程之前结束,内核应该把该子进程设置成特殊的进程状态。处于这种状态的进程称为僵尸进程。
僵尸进程只保存最小的概要信息——一些基本的内核数据结构,保存可能有用的信息。僵尸进程会等待父进程来查询自己的状态(这个过程称为在僵尸进程上等待)。只有当父进程获取到了已终止的子进程的信息,这个子进程才会正式消失,不在处于僵尸状态。
内核提供一些接口,可以获取已终止子进程的信息。其中最简单的就是wait()。
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
调用wait()成功时,会返回已终止子进程的pid;出错时,会返回-1.如果没有子进程终止,调用会阻塞,直到有一个子进程终止。如果有个子进程已经终止了,调用会立即返回,不会被阻塞。
等待特定进程
waitpid()可以用于等待特定的子进程。
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
比起wait(),waitpid()是一个更加强大的系统调用,它的额外参数可以支持细粒度调整。参数pid指定要等待的一个或多个进程的pid。它的值必须是下面四种情况之下:
<-1 等待一个指定进程组中的任何子进程退出,该进程组的ID等于pid的绝对值。比如-500,表示等待在进程组500中的任何进程。
-1 等待任何一个子进程退出,行为和wait()一致。
0 等待同一个进程组中的任何子进程
正数 等待进程pid等于pid的子进程。比如500,等待pid为500的子进程。
参数status的作用和wait()函数的唯一参数是一样的。
等待子进程的其他方法
waitid()
wait3()
wait4()
下一篇: gcc linux利用CAS实现无锁编程