进程控制,处理僵尸进程(父进程如何等待子进程的退出)
exit 进程退出
#include<stdlib.h>
void exit(int status);//是标准c库函数
//内部调用系统调用
#include<unistd.h>
void _exit(int status);
#include<stdio.h>
#include<unistd.h>
int main()
{
printf("hello\n");//这里有\n刷新缓冲区,将缓冲区的内容写出
printf("world");//没\n刷新缓冲区,所以在_exit程序退出后,没有打印
_exit(0);
return 0;
}
而exit标准C函数会进行刷新IO缓冲,关闭文件描述符
孤儿进程
父进程结束,而子进程还在运行,这时此子进程就叫孤儿进程
内核会把孤儿进程的父进程设置为init进程,init会不断的wait它的子进程,孤儿进程的生命周期结束后由init进程来进行处理
init进程pid = 1
//使用sleep,让子进程后于父进程结束
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid>0){//parent
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}else if(pid==0){
sleep(1);
printf("i am chiild process,pid:%d,ppid:%d\n",getpid(),getppid());
}
for(int i = 0;i<3;i++)
{
printf("i:%d\n",i);
}
return 0;
}
僵尸进程
对于多进程程序,父进程一般需要跟踪子进程的退出状态,所以子进程退出后,内核不会立即释放改进程的进程表表项,来让父进程可以对子进程的退出信息进行查询
僵尸态:(1)子进程结束,父进程读取其退出状态之前,子进程处于僵尸态
(2)父进程结束或异常终止,子进程继续运行,此时init进程接管该子进程,那么在父进程退出后,子进程退出前,该子进程处于僵尸态
如果父进程没有正确处理子进程的返回信心,子进程都将处于僵尸态,占据内核资源
内核区的PCB无法自己释放,需要父进程来释放
僵尸进程无法被kill -9发送的信号杀死
//产生一个僵尸进程
//让进程用while阻塞,无法获取子进程的退出状态
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid = fork();
if(pid>0){//parent
while(1){
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
sleep(1);
}
}else if(pid==0){
printf("i am chiild process,pid:%d,ppid:%d\n",getpid(),getppid());
}
for(int i = 0;i<3;i++)
{
printf("i:%d\n",i);
}
return 0;
}
处理的一种方法:
结束父进程,使子进程被init进程托管
wait
父进程调用wait等待子进程结束
wait取得子进程结束时传给exit的值
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//即为调用:
waitpid(-1,&status,0);
//此函数会回收子进程的资源
//返回pid,失败返回-1(调用的进程没有子进程也没有得到终止状态值(所有的子进程都结束且没有得到终止状态值)),设置errno
//默认是阻塞的
//参数为进程退出的状态
pid_t waitpid(pid_t pid, int *status, int
options);
//功能与wait相同,可以设置否阻塞进程,可以指定要回收的进程的pid,
The value of pid can be:
< -1 meaning wait for any child process whose 回收abs(组id)的进程(某进程组的某个进程)
process group ID is equal to the abso‐
lute value of pid.
-1 meaning wait for any child process. 回收所有子进程中的任一子进程
0 meaning wait for any child process whose 回收当前进程组的任一子进程
process group ID is equal to that of the
calling process.
> 0 meaning wait for the child whose process
ID is equal to the value of pid.
The value of options is an OR of zero or more //设置阻塞或非阻塞
of the following constants:
//0为阻塞
WNOHANG return immediately if no child has
exited. //如果没有了子进程,则立刻返回,非阻塞
WUNTRACED also return if a child has stopped
(but not traced via ptrace(2)).
Status for traced children which
have stopped is provided even if
this option is not specified. //
WCONTINUED (since Linux 2.6.10)
also return if a stopped child has
been resumed by delivery of SIG‐
CONT.
//当option为WNOHANG时,如果pid指定的目标子进程还没有结束或意外结束,则waitpid返回0,如果目标子进程确实正常退出则返回该子进程的PID,如果调用失败返回-1
在事件已经发生的情况下执行非阻塞调用才能提高程序效率,所以waitpid最好在某个子进程确定退出之后再调用
我们使用SIGCHILD
信号来让父进程得知某个子进程已经退出
一个进程结束时,会发送给其父进程发送SIGCHILD
信号,因此:捕获此信号,在信号处理函数中调用waitpid函数
static void handle_child(int sig){
pid_t pid;
int stat;
while((pid=waitpid(-1,&stat,WNOHANG))>0){//直到所有的子进程都处理结束,退出循环
//善后处理
}
}
父进程执行wait,内核挂起父进程直到子进程结束,直到它的一个子进程退出或者它收到一个不可忽略的信号,如果没有子进程存在了,函数会立刻返回-1,所有子进程都结束了,也会返回-1
一次只能应对一个子进程,所以多个子进程需要使用循环来处理
如果一个子进程被杀死或已经退出则对wait的调用立刻返回,wait返回结束进程的PID,如果status非NULL,则wait将退出状态或者信号序号浮直到其中(指向的整数),使用<sys/wait.h>中的宏检测
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
//创建五个子进程
for(int i = 0;i<5;i++){
pid = fork();
if(pid==0)break;//防止子进程再创建子进程
}
if(pid>0){
while(1){
printf("parent pid:%d\n",getpid());
sleep(1);
}
}else if(pid==0){
printf("child pid:%d\n",getpid());
}//让子进程先死亡,得到五个僵尸进程
return 0;
}
//调用wait,等待子进程退出,并且输出结束的子进程的id
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
//创建五个子进程
for(int i = 0;i<5;i++){
pid = fork();
if(pid==0)break;//防止子进程再创建子进程
}
if(pid>0){
while(1){
printf("parent pid:%d\n",getpid());
int ret = wait(NULL);
printf("child end ,pid = %d\n",ret);
sleep(1);
}
}else if(pid==0){
printf("child pid:%d\n",getpid());
}//让子进程先死亡,得到五个僵尸进程
return 0;
}
使用宏来检测退出状态
(1)wait执行通知操作:
waitpid中的pid是指的是任意pid,而不是所有,waitpid一次只回收一个进程的资源
//尝试使用waitpid函数
//首先是非阻塞情况:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
//创建五个子进程
for(int i = 0;i<5;i++){
pid = fork();
if(pid==0)break;//防止子进程再创建子进程
}
if(pid>0){
while(1){
printf("parent pid:%d\n",getpid());
int stat;
int ret = waitpid(-1,&stat,0);//设置为阻塞,如果没有子进程结束,则下面的代码都没有执行
if(ret==-1)break;
//使用kill命令结束子进程,父进程就会恢复为非阻塞,执行下列代码
if(WIFEXITED(stat)){
//正常退出
printf("正常退出的状态码:%d\n",WEXITSTATUS(stat));
}
if(WIFSIGNALED(stat)){
//异常终止
printf("被%d信号终止\n",WTERMSIG(stat));
}
printf("child end ,pid = %d\n",ret);
sleep(1);
}
}else if(pid==0){
while(1){
printf("child pid:%d\n",getpid());
sleep(1);
}
}
return 0;
}
阻塞情况:
//只用更改一行调用waitpid的代码
int ret = waitpid(-1,&stat,WNOHANG);
因为return 0
所以退出的状态码为0