知道了进程怎么创建,接下来就来看看怎么终止一个进程终止函数exit()和_exit()。
函数 | 头文件 | 声明 |
---|---|---|
exit | stdlib.h | void exit(int status) |
_exit | unistd.h | void _exit(int status) |
参数作用:可以利用这个参数传递进程退出状态。0表示正常退出,其他情况表示非正常结束。可以用wait接受来自子进程的退出码。在任意
⼀种情况下,该终⽌状态的⽗进程都能使⽤wait或waitpid函数取得其终⽌状态
1. exit()、_exit()
首先,exit()和_exit()都是用来终止进程的。当进程执行exit()或_exit()函数时,系统会立即停止所以操作,清除进程相关的各种数据结构,包括进程PCB,并且终止当前进程的运行。
可以看到,exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
那么exit()在结束调用它的进程之前都做了什么呢?
exit调用系统调用exit()前先对文件的打开情况进行检查,关闭所有打开的流,这将导致写所有被缓冲的输出,删除用TMPFILE函数建立的所有临时文件。这里的缓冲就是图中的“I/O缓冲”。系统会为每个已经打开的文件,在内存定义一片缓冲区,这样我们每次写入数据都是先写入到缓冲区中,然后满足一定条件后再由缓冲区一次性写入到文件中。这样大大加快了文件读写的速度。
简单的说就是,exit函数将终止调用进程。在退出程序之前,所有文件关闭,缓冲输出内容将刷新定义,并调用所有已刷新的“出口函数”(由atexit定义)。
而_exit同样终止调用进程,但不关闭文件,不清除输出缓存,也不调用出口函数。
所以当文件进行读写操作后,数据有可能在缓冲区,而这时候调用_exit则会将缓冲区的数据丢失,使得出现错误,数据不完整。因此当读写文件操作后,尽量使用exit()终止进程,避免意想不到的错误。
使用实例:
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 0;
int main()
{
pid_t id = fork();
if( id < 0 ){
exit(1);
}
else if( id == 0 ){
printf("clild is run, child pid is : %d\n",getpid());
sleep(1);
exit(100);
}
else{
printf("father is run,father pid is : %d\n",getpid());
}
int ret;
if( wait(&ret) < 0 ){
printf("wait error, error code is : %d\n", errno);
return 1;
}
printf("wait success, status code is : %d\n",ret);
return 0;
}
结果:
2. 进程终止的特殊情况
1)子进程终止时,父进程并不正在执行 wait()调用。
2)当子进程尚未终止时,父进程却终止了。
在第一种情况中,要终止的进程就处于一种过渡状态 ,称为 僵死状态(zombie ),处于这种状态的进程不使用任何内核资源,但是要占用内核中的进程处理表那的一项。当其父进程执行wait()等待子进程时,它会进入睡眠状态,然后把这种处于过渡状态的进程从系统内删除,父进程仍将能得到该子进程的结束状态。
第二种情况中,对于⽗进程已经终⽌的所有子进程,他们的⽗进程都改变为init进程。我们称这些子进程由init领养。⼀个init的⼦进程(包括领养进程)终⽌时, init会调⽤⼀个wait函数取得其终⽌状态。