一、进程终止方式:进程有5种正常终止及三种异常终止方式
5种正常终止方式具体如下。
(1) 在main函数内执行return语句。(等效于调用exit)。
(2) 调用exit函数。
(3) 调用_exit或_Exit函数。
(4) 进程的最后一个线程在其启动例程中执行return语句。
(5) 进程的最后一个线程调用pthread_exit函数。
3种异常终止:
(1) 调用abort。
(2) 当进程接收到某些信号时。
(3) 最后一个线程对“取消”(cancellation)请求作出响应。
二、不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。
对上述任意一种终止情形,我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit、_exit、_Exit),实现这一点的方法是,将其退出状态(exit status)作为参数传给函数。在异常终止情况,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
三、函数exit
#include
void _exit(int status);
①功 能: 关闭所有文件,终止正在执行的进程。
②参数status:程序退出的返回值。
注:exit()就是退出,传入的参数是程序退出时的状态码,0表示正常退出,其他表示非正常退出,一般都用-1或者1,标准C里有EXIT_SUCCESS和EXIT_FAILURE两个宏,用exit(EXIT_SUCCESS);
四、等待子进程
如果子进程在父进程之前终止,那么父进程又如何能在做相应检查时得到子进程的终止状态呢?
如果子进程完全消失了,父进程在最终准备好检查子进程是否终止时是无法获取它的终止状态的。内核为每个终止子进程保存了一定量的信息,所以当终止进程的父进程调用wait或waitpid时,可以得到这些信息。这些信息至少包括进程ID、该进程的终止状态以及该进程使用的CPU时间总量。内核可以释放终止进程所使用的所有存储区,关闭其所有打开文件。
僵尸进程:
在UNIX术语中,一个已经终止、但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵尸进程(zombie)。ps(1)命令将僵尸进程的状态打印为Z。如果编写一个长期运行的程序,它fork了很多子进程,那么除非父进程等待取得子进程的终止状态,不然这些子进程终止后就会变成僵尸进程。
孤儿进程:
父进程在子进程之前终止,其子进程被init进程收养的进程被称为孤儿进程(孤儿进程的父亲更改为 init 进程),该进程在孤儿进程退出后回收它的内核空间资源。
僵尸进程实例:
#include
#include
#include
int main()
{
int i = 0;
pid_t pid;
pid = vfork();
if(pid > 0) {
while(1) {
printf("this is father process:%d\n",getpid());
sleep(1);
}
} else if(pid == 0) {
while(1) {
printf("this is child process:%d\n",getpid());
i++;
if(i == 3) {
exit(0);
}
}
}
return 0;
}
当父进程等待子进程(pid:12541)退出时,父进程(pid:12540)才执行,这时候子进程变为僵尸进程。如图下所示
孤儿进程实例:
#include
#include
#include
int main()
{
pid_t pid;
pid = fork();
if(pid > 0) {
sleep(1);
printf("this is father process:%d\n",getpid());
} else if(pid == 0) {
printf("this is child process:%d,father's pid = %d\n",getpid(),getppid());
sleep(2);
printf("this is child process:%d,father's pid = %d\n",getpid(),getppid());
}
return 0;
}
由运行结果可看出父进程未结束后,子进程pid=1收留,称为孤儿进程。
一个由init进程收养的进程终止时,它不会变成僵尸进程,因为init被编写成无论何时只要有一个子进程终止,init就会调用一个wait函数取得其其终止状态。这样也就防止了在系统中塞满僵尸进程。
为什么要等子进程退出?
因为子进程结束后变成了僵尸进程,造成了内存泄露;其父进程创建并管理子进程,安排给子进程的任务进度如何,并不知道;父进程通过等待的方式(wait使其阻塞),回收子进程的资源,获取退出信息。如果父进程阻塞而且它有多个子进程,则在其某一子进程终止时,wait就立刻返回。因为wait返回终止子进程的进程ID,所以它总能了解是哪一个子进程终止了。
五、等待相关函数
wait, waitpid, waitid - wait for process to change state
#include
#include
1.pid_t wait(int *status);
//返回值:若成功,立即返回子进程结束状态值;如失败返回-1
2.pid_t waitpid(pid_t pid, int *status, int options);
1、函数说明:waitpid()会暂时停止目前进程的执行, 直到有信号来到或子进程结束. 如果在调用wait()时子进程已经结束, 则wait()会立即返回子进程结束状态值. 子进程的结束状态值会由参数status 返回, 而子进程的进程识别码也会一快返回. 如果不在意结束状态值, 则参数status 可以设成NULL.
2、参数:
*pid 为欲等待的子进程识别码, 其他数值意义如下:
①pid<-1 等待进程组识别码为pid 绝对值的任何子进程.
②pid=-1 等待任何子进程, 相当于wait().
③pid=0 等待进程组识别码与目前进程相同的任何子进程.
④pid>0 等待任何子进程识别码为pid 的子进程
*statloc是一个整型指针。如果不是一个空指针,则终止进程的终止状态就存放在它所指的单元内。如果不关心终止状态,则可将该参数指定为空指针。
*options参数使我们能进一步控制waitpid的操作。此参数或者是0,或者是下图常量按位或运算的结果
4.父进程等待子进程退出,并收集子进程的退出状态:
3、这两个函数的区别:
①在一个子进程终止前,wait使其调用者阻塞,而waitpid有一个选项,可使调用者不阻塞。
②waitpid并不等待在其调用之后的第一个终止子进程,它有若干个选项,可以控制它所等待的进程。
4、调用wait或waitpid的进程可能会发生什么?
·如果其所有子进程都还在运行,则阻塞。
·如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立刻返回。
实例:
#include
#include
#include
int main()
{
pid_t pid;
int i = 0;
int status = 10;
pid = fork();
if(pid > 0) {
wait(&status);
printf("child quit,child status = %d\n",WEXITSTATUS(status));
while(1) {
printf("this is father:%d\n",getpid());
sleep(1);
}
} else if(pid == 0) {
while(1) {
printf("this is child:%d\n",getpid());
sleep(1);
i++;
if(i == 3) {
exit(3);
}
}
}
return 0;
}