进程退出
进程退出主要分为两种:正常退出、异常退出
正常退出
正常退出分为以下几种:
1.main函数调用return
2.进程调用exit(),标准c库
3.进程调用 _exit() 或者 _Exit() ,属于系统调用
4.进程最后一个线程返回
5.最后一个线程调用pthread exit(4和5与线程有关)
异常退出
异常退出分为以下几种:
1.调用abort函数
2.当进程收到某些信号时,如 ctrl+C
3最后一个线程对取消 (cancellation) 请求做出响应
退出函数的解析
退出函数分别有exit()、_exit()、_Exit(),其函数原型和参数如下:
函数原型 | 所包含头文件 | 参数 |
void exit(int status) | <stdlib.h> | exit(0)正常退出;exit(1)异常退出 |
void _exit(int status) | <unistd.h> | _exit(0)正常退出;_exit(1)异常退出 |
void _Exit(int status) | <stdlib.h> | _Exit(0)正常退出;_Exit(1)异常退出 |
status 是一个整型的参数,可以利用这个参数传递进程结束时的状态。一般来说,0表示正常结束;其它的值表示出现了错误,进程非正常结束在实际编程时,可以用 wait()系统调用接收子述程的返回值,针对不同的情况进行不同的处理。
第二种与第三种类似,但第一种与其他两种有一定的区别,区别如下:
从图里可以看出来,_exit()函数的作用是: 直接使进程停止运行,清除其使用的内存空间,并清除其在内核中的各种数据结构; 而exit()函数则在这 些基础上做了一些包装,在执行退出之前加了若干道工序. exit()函数和_exit()函数的最大区别就在于exit()函数在终止当前进程之前要检查该进程 打开过那些文件,把文件缓冲区中的内容写回文件,也就是图中的 "清楚I/O缓冲" 一项.
不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。对上述任意一种终止情形、我们都希望终止进程能够通知其父进程它是如何终止的。对于三个终止函数(exit._exit和_Exit),实现这一点的方法是,将其退出状态((exit status)作为参数传送给函数。在异常终止情况下,内核(不是进程本身)产生一个指示其异常终止原因的终止状态(termination status)。在任意一种情况下,该终止进程的父进程都能用wait或waitpid函数取得其终止状态。
子进程退出的收集
为什么要等待子进程退出?
父进程得管理子进程,所以父进程派给子进程的任务完成的如何,我们都需要知道,如,子进程运行完成,运行结果对还是不对,或者是否正常退出 、父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
不收集子进程状态会发生什么?
子进程状态不被收集会变成僵尸进程,造成内存泄漏
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int vfork_t = 0;
int count = 0;
vfork_t = vfork();
if(vfork_t > 0)
{
while(1)
{
printf("count = %d\n",count);
printf("This is father,pid =%d \n",getpid());
sleep(1);
}
}
else if(vfork_t == 0)
{
while(1)
{
printf("This is child,pid =%d \n",getpid());
sleep(1);
count++;
if(count == 3)
{
exit(0);
}
}
}
return 0;
}
可见父进程显示S+(正常运行),而子进程显示Z+(僵尸体(死)进程)
收集子进程的函数调用
收集子进程可以调用的函数有wait(),waitpid(), waitid()
waiit()
包含的头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t wait(int *status);
参数解读
status参数:
一个整型数指针
非空:子进程退出状态放在它所指向的地址中
空:不关心退出状态
返回值
wait():如果成功,返回终止子进程的进程ID;错误,返回-1。
针对wait函数的参数做出试验
1.空:wait(NULL)
将上面函数vfork换成fork并调用wait函数,如下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int fork_t = 0;
int count = 0;
fork_t = fork();
if(fork_t > 0)
{
wait(NULL);
while(1)
{
printf("count = %d\n",count);
printf("This is father,pid =%d \n",getpid());
sleep(1);
}
}
else if(fork_t == 0)
{
while(1)
{
printf("This is child,pid =%d \n",getpid());
sleep(1);
count++;
if(count == 3)
{
exit(0);
}
}
}
return 0;
}
‘
可见,在子进程运行三次后,收集子进程,轮到父进程运行,此时运行程序没有子进程,即子进程不会变成僵尸进程,而父进程正常运行
2.非空:wait(*status)
将上面函数的NULL改成定义的任意值,并通过观察该只判断子进程状态
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int fork_t = 0;
int count = 0;
int status = 9;
fork_t = fork();
if(fork_t > 0)
{
wait(&status);//观察子进程状态
printf("child quit,status is %d\n",WEXITSTATUS(status));//打印子进程状态,值应为exit括号里的返回值
while(1)
{
printf("count = %d\n",count);
printf("This is father,pid =%d \n",getpid());
sleep(1);
}
}
else if(fork_t == 0)
{
while(1)
{
printf("This is child,pid =%d \n",getpid());
sleep(1);
count++;
if(count == 3)
{
exit(3);
}
}
}
return 0;
}
可见,当指针为非空时,子进程的退出状态会保存在指向的地址中,即exit函数括号里的值会指向status的地址,从而打印判断子进程的状态。
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);
参数解读
pid参数:
pid的值 | 功能 |
pid<-1 | 等待进程组识别码为 pid 绝对值的任何子进程 |
pid=-1 | 等待任何子进程,相当于 wait() |
pid=0 | 等待进程组识别码与目前进程相同的任何子进程 |
pid>0 | 等待任何子进程识别码为 pid 的子进程 |
status参数:
一个整型数指针
非空:子进程退出状态放在它所指向的地址中
空:不关心退出状态
options参数:
WCONTINUEL | 若实现支持作业控制。那么由pid指定的任一子进程在暂停后已经继续,但其状态尚未报告,则返回其状态(POSIX.1的XSI护展) |
WNOHANG (常用) | 若由pid指定的子进程并不是立即可用的,则waitpid不阻塞,此时其返回值为0 |
WUNTRACED | 若某实现支持作业控制,而由pid指定的任一子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态。WIFSTOPPED宏确定返回值是否对应于一个暂停子进程 |
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int fork_t = 0;
int count = 0;
int status = 9;
fork_t = fork();
if(fork_t > 0)
{
waitpid(fork_t,&status,WNOHANG);//第一个参数:父进程的返回值大于0,同时该返回值就是子进程的pid,而这里的参数就是子进程pid的值
printf("child quit,status is %d\n",WEXITSTATUS(status));
while(1)
{
printf("count = %d\n",count);
printf("This is father,pid =%d \n",getpid());
sleep(1);
}
}
else if(fork_t == 0)
{
while(1)
{
printf("This is child,pid =%d \n",getpid());
sleep(1);
count++;
if(count == 3)
{
exit(3);
}
}
}
return 0;
}
调用该函数后,子进程和父进程会一起运行,即父进程不会挂起,在运行3次之后,只有父进程运行,同时子进程此时表现为僵尸进程。
孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程叫做孤儿进程。 Linux避免系统存在过多孤儿进程,==init进程(系统初始化进程,进程ID为1)==收留孤儿进程,变成孤儿进程的父进程。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int fork_t = 0;
int count = 0;
int status = 9;
fork_t = fork();
if(fork_t > 0)
{
printf("This is fther pid,pid is %d\n",getpid());//打印一次父进程的pid
}
else if(fork_t == 0)
{
while(1)
{
printf("This is child,pid =%d,my father is %d\n",getpid(),getppid());
sleep(1);
count++;
if(count == 5)
{
exit(3);
}
}
}
return 0;
}
可见,父进程打印一次,打印完之后结束,此时只剩下子进程,不难发现,子进程的父进程pid值为1,相当于系统分配了个pid值为1的父进程收留这些孤儿进程;同时子进程还在运行,不为僵尸进程。