1、进程退出
退出方式有两种:正常退出,异常退出
正常退出有5种:
1、Main函数调用return
2、进程调用exit(),标准C库
3、进程调用_exit()或者_Exit(),属于系统调用
和线程有关的
4、进程最后一个线程返回
5、最后一个线程调用pthread_exit
异常退出有三种:
1、调用abort
2、当进程收到某些信号时,如Ctrl + C
3、最后一个线程对取消(cancellation),请求作出响应
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态((exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数,取得其终止状态。
二、子进程退出的信息收集
为什么要等待子进程退出
1、防止僵尸进程,造成内存泄漏
2、父进程要管理子进程,所以父进程交代给子进程的任务完成的如何,都需要知道,如,子进程运行完成,运行结果对还是不对,或者是否正常退出
3、父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
如果不收集子进程的退出信息会怎么样?
如果子进程的退出状态不被收集,子进程会变成僵死进程(僵尸进程)。
例如
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int data = 3;
pid_t pid;
pid = vfork();
if(pid > 0){
while(1){
printf("Father pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
data--;
printf("Child pid = %d\n",getpid());
sleep(3);
if(data == 0){
exit(0);
}
}
}
return 0;
}
子进程的PID号为20024
Z是单词(zombie)的缩写,意思是僵尸
收集子进程退出信息的函数wait和waitpid
头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t wait(int *status);
status:是一个整型数指针
指针为空:不关心退出状态
指针非空:子进程退出状态放在它所指向的地址中
函数作用:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
wait()要与fork()配套出现,如果在使用fork()之前调用wait(),程序出错时wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.
wait函数的两种参数情况
1、status为NULL时
int main()
{
int data = 3;
pid_t pid;
pid = vfork();
if(pid > 0){
wait(NULL);
while(1){
printf("Father pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
data--;
printf("Child pid = %d\n",getpid());
sleep(3);
if(data == 0){
exit(0);
}
}
}
return 0;
}
2、status非空时
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int data = 3;
pid_t pid;
pid = vfork();
int status = 10;
if(pid > 0){
wait(&status);
printf("child quit status = %d\n",WEXITSTATUS(status));
while(1){
printf("Father pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
data--;
printf("Child pid = %d\n",getpid());
sleep(3);
if(data == 0){
exit(3);
}
}
}
return 0;
}
可以看到用wait将子进程的返回信息收集了起来,并且将返回的信息放到了status指向的内存中,并且没有出现进程号为20319的僵尸进程,进程被关闭一些宏
waitpid
头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,wait使调用者阻塞,waitpid 可以使调用者不阻塞
PID:
pid > 0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid = -1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid == 0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid < -1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
status:是一个整型数指针,和wait一样
options:options提供了一些额外的选项来控制waitpid,目前 在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:
ret = waitpid(-1, NULL, WNOHANG | WUNTRACED);
如果我们不想使用它们,也可以把options设为0,如:
ret = waitpid(-1, NULL, 0);
如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。
返回值:
1、当正常返回的时候,waitpid返回收集到的子进程的进程ID
2、如果设置了选项WNOHANG,在调用中waitpid发现没有已经退出的子进程可收集时,返回0
3、如果调用中出错,则返回-1
例:
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int data = 3;
pid_t pid;
pid = fork();
int status = 10;
if(pid > 0){
waitpid(pid,&status,WNOHANG);
printf("child quit status = %d\n",WEXITSTATUS(status));
while(1){
printf("Father pid = %d\n",getpid());
sleep(1);
}
}else if(pid == 0){
while(1){
data--;
printf("Child pid = %d\n",getpid());
sleep(3);
if(data == 0){
exit(3);
}
}
}
return 0;
}
在这里可以看到进程号为20830的子进程变成了僵尸进程,这是因为用的是fork()函数创建进程,它不会先执行子进程,,再执行父进程,所以在执行父进程的时候waitpid这里没有返回子进程的pid号,又因为设置了选项WNOHANG,所以返回0,当真正的子进程信息返回来的时候,就没有收集,所以变成了僵尸进程。
当把fork改为vfork时,就可以解决
pid = vfork();
在这里并没有出现进程号为20917的僵尸进程
三、孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程
Linux为了避免孤儿进程过多,init进程收留孤儿进程,变成孤儿进程的父进程(init进程为系统初始化进程,进程ID为1)
例如
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
int data = 3;
pid_t pid;
pid = fork();
if(pid > 0){
printf("Father pid = %d\n",getpid());
}
else if(pid == 0){
while(1){
data--;
printf("my Father pid = %d Child pid = %d\n",getppid(),getpid());
sleep(3);
if(data == 0){
exit(3);
}
}
}
return 0;
}
此时进程号为21311的子进程的父进程就为init了。