Linux进程等待

一、进程等待是什么

通过系统调用wait/waitpid,来进行对子进程进行状态检测与回收的功能

二、进程等待必要性

子进程退出,父进程如果不管不顾,就可能造成"僵尸进程"的问题,进而造成内存泄漏。另外,进程一旦变成僵尸状态,那就没有办法去杀死,kil -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

三、进程等待的方法

3.1wait

在这里插入图片描述
通过让父进程调用wait/waitpid对僵尸进程的等待来达到对僵尸进程的一个回收
wait函数返回值:
成功返回子进程的pid,失败则返回-1
wait函数参数:
输出型参数,获取子进程退出状态,不关心则可以设置为NULL

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 int main()
  7 {
  8  pid_t id=fork();
  9  if(id<0)
 10  {
 11printf("进程创建失败\n");
 12  }
 13  else if(id==0)
 14  {
 15//子进程
 16int cnt=5;
 17while(cnt--)
 18{
 19  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 20  sleep(1);
 21}
 22exit(0);
 23  }
 24  else if(id>0)
 25  {
 26//父进程
 27int cnt =10;
 28while(cnt--)
 29{
 30  printf("i am father pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 31  sleep(1);
 32}
 33pid_t ret=wait(NULL);
 34   if(ret==id)
 35   {
 36 printf("recycle success, ret is %d\n",ret);
 37   }
 38   sleep(5);
 39  }
 40   return 0;
 41 }

父子进程先运行五秒,子进程先于父进程退出,子进程陷入僵尸状态,父进程再运行五秒钟后退出while函数,回收子进程
在这里插入图片描述
在这里插入图片描述
目前为止,进程等待是必须的

3.2wait等待多个子进程

wait是等待任意一个子进程退出的

  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 #define N 5
  7 void runchild()
  8 {
  9   int cnt=5;
 10   while(cnt)
 11   {
 12  printf("i am child pid is %d, ppid is %d\n",getpid(),getppid());
 13  sleep(1);
 14 cnt--;
 15   }
 16 }
 17 int main()
 18 {
 19   for(int i=0;i<N;i++)
 20   {
 21 pid_t id=fork();
 22 if(id==0)
 23 {
 24   runchild();
 25   exit(0);
 26 }
 27 printf("run child success child pid is%d\n",id);
 28   }
 29   sleep(10);   
 30   for(int i=0;i<N;i++)
 31   {
 32 pid_t ret=wait(NULL);
 33 if(ret)
 34 {
 35   printf("wait success pid is %d\n",ret);
 36 }
 37   }
 38   sleep(5);
 39   return 0;
 40 }

在这里插入图片描述
通过循环来创建一批子进程,同时子进程去执行自己的任务,子进程结束之后退出,否则子进程会继续进入循环创建子进程,为了观察僵尸状态我们要先让父进程等待几秒钟时间,再回收

3.2.1阻塞等待

上面所述的是子进程会僵尸的情况,那么子进程永远不死呢
在这里插入图片描述
如果子进程不退出父进程也不退出,父进程在默认调用wait的时候,也就不返回,默认叫做阻塞状态

3.3waitpid

wait所能提供的作用是waitpid的子集
在这里插入图片描述
waitpid返回值
成功返回子进程的pid
如果options设置为WORNING,而调用中waitpid发现没有已退出的子进程可收集,则返回0
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在
参数
pid=-1,等待任意一个子进程
pid>0,等待pid=子进程ID
status
WIFEXITED:若为正常终止子进程返回的状态,则为真.(查看进程是否是正常退出)
WEXITSTATUS(若WIFEXITED非零,提取子进程退出码.(查看进程的退出码)
options
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

 pid_t ret=waitpid(-1,NULL,0);

在这里插入图片描述
由此可见waitpid和wait的效果是类似的

3.4status

status保存着进程退出时候的退出信息

pid_t waitpid(pid_t pid, int *status, int options);

接下来就让我来带大家详细剖析一下这个status
首先父进程等待子进程退出之前,我们需要先定义一个status整型变量用来存储子进程的退出信息(退出码)
waitpid函数里面的status是int*,由此可见里面保存着子进程退出信息的地址,取整型的地址,它的类型就是int*

 41 int main()
 42 {
 43  pid_t id=fork();
 44  if(id<0)
 45  {
 46printf("进程创建失败\n");
 47  }
 48  else if(id==0)
 49  {
 50//子进程
 51int cnt=5;
 52while(cnt--)
 53{
 54  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 55  sleep(1);
 56}
 57exit(1);   
 58  }
 59  else if(id>0)
 60  {
 61//父进程
 62int cnt =10;
 63while(cnt--)
 64{
 65  printf("i am father pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 66  sleep(1);
 67}
 68int status=0;
 69pid_t ret=waitpid(id,&status,0);
 70   if(ret==id)
 71   {
 72 printf("recycle success, ret is %d,status is %d\n",ret,status);
 73   }
 74   sleep(5);
 75  }
 76   return 0;
 77 }

在这里插入图片描述
子进程的退出码为1,status的退出信息却是256,这是什么一回事呢

3.4.1父进程等待,期望得到子进程的哪些信息呢

进程退出有三种场景,运行成功结果正确,运行成功结果不正确,代码异常
父进程关心子进程
1.子进程代码是否异常
2.没有异常,结果是否正确,退出码不对是因为什么原因
不同的退出码,表示不同的出错原因
所以父进程关心如此多的子进程退出信息这就注定status不能单单被看作是一个整数要被划分为多个部分

3.4.2status的划分

在这里插入图片描述
整数一共有32个bit位,我们谈论的是后16位
首先异常终止的信息保存到了第7位,core dump等到了信号再做处理,
在这里插入图片描述
这就能解释我们信号为什么只有64位以及为什么没有0号信号
1.算到2^6刚好是64位
2.没有0号信号的原因就是进程先检查status的后7位是否为0,通过按位与等方法,不等于0进程异常退出就可以检验是收到了几号信号,等于0就可以检验正常终止情况下的退出信息也就是进程的退出码是多少
3.由于此次设置的退出码为1,在2^8刚好就是256对应了退出码为1

3.4.3为什么一定要调用waitpid

如果通过定义全局变量的方式,子进程对status进行修改,但是父子进程是具有独立性的,子进程对变量修改父进程是看不到的,所以要通过系统调用也就是操作系统来拿

3.4.4正确划分status

 41 int main()
 42 {
 43  pid_t id=fork();
 44  if(id<0)
 45  {
 46printf("进程创建失败\n");
 47  }
 48  else if(id==0)
 49  {
 50//子进程
 51int cnt=5;
 52while(cnt--)
 53{
 54  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 55  sleep(1);
 56}
 57exit(1);
 58  }
 59  else if(id>0)
 60  {
 61//父进程   
 62int cnt =10;
 63while(cnt--)
 64{
 65  printf("i am father pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 66  sleep(1);
 67}
 68int status=0;
 69pid_t ret=waitpid(id,&status,0);
 70   if(ret==id)
 71   {
 72 printf("recycle success, ret is %d,exit sig is %d,exit code is %d\n",id,status&0x7F,(status>>8)&0xFF);
 73   }
 74   sleep(5);
 75  }
 76   return 0;
 77 }

在这里插入图片描述
0x7F是0111 1111按位与可以得到后7位上为1的数字
0xFF是1111 1111右移八位可以得到退出码上为1的数字
在这里插入图片描述
我们再来验证异常

[wyx@hcss-ecs-000a proc_wait]$ kill -9 7329

在这里插入图片描述
很显然这是收到了9号信号

3.4.5status原理

在这里插入图片描述
子进程退出的时候代码和数据可以释放,但是子进程的PCB要保留下来,里面保存着子进程的退出信息和异常信息,sigcode和exitcode通过按位与等操作拼成了status,
CPU调度父进程的时候,父进程通过调用waitpid系统调用去检查子进程是否是Z状态(僵尸状态)是僵尸状态,如果是,将Z状态改为X状态并提取错误信息和错误码
父进程不直接去拿子进程的,而是通过操作系统的手的原因是进程间都是独立的,父进程本质也是用户的操作,让用户直接去操作本身就是不安全的

3.4.6等待失败

当父进程等待不属于自己的子进程pid是就会等待失败

 41 int main()
 42 {
 43  pid_t id=fork();
 44  if(id<0) 
 45  {
 46printf("进程创建失败\n");
 47  }   
 48  else if(id==0)
 49  {  
 50//子进程
 51int cnt=5;  
 52while(cnt--)
 53{   
 54  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 55  sleep(1); 
 56}
 57exit(1);
 58  }
 59  else if(id>0)
 60  { 
 61int cnt =10;
 62while(cnt--)
 63{
 64  printf("i am father pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 65  sleep(1); 
 66}
 67int status=0;
 68pid_t ret=waitpid(id+2,&status,0);
 69   if(ret==id)
 70   { 
 71 printf("recycle success, ret is %d,exit sig is %d,exit code is %d\n",id,status&0x7F,(status>>8)&0xFF);
 72   }
 73   else{ 74 printf("wait failed\n");
 75   }
 76   sleep(2);
 77  }  
 78   return 0;
 79 }      

在这里插入图片描述

3.4.7status的宏函数

WIFEXITED:若为正常终止子进程返回的状态,则为真.(查看进程是否是正常退出)
WEXITSTATUS(若WIFEXITED非零,提取子进程退出码.(查看进程的退出码)
因为0x7F和0xFF有可能我们也不是很熟悉
WIFEXITED就相当于检查进程是否有异常退出
WEXITSTATUS就是检查进程的退出码

   if(WIFEXITED(status))
   70   {
   71 printf("exit success,exit code is %d\n",WEXITSTATUS(status));
   72   //  printf("recycle success, ret is %d,exit sig is %d,exit code is %d\n",id,status&0x7F  ,(status>>8)&0xFF);  
   73   }
 else
 74 printf("进程出异常了\n");   
 75}

单进程是在你waitpid已经将子进程的Z状态改成了X状态,等待成功了,再检验status里面的信息
多进程也是如此

  7 void runchild()
  8 {
  9   int cnt=3;
 10   while(cnt)
 11   {
 12  printf("i am child pid is %d, ppid is %d\n",getpid(),getppid());
 13  sleep(1);
 14  cnt--;
 15   }
 16 }
 17 int main()
 18 {
 19   for(int i=0;i<N;i++)
 20   {
 21 pid_t id=fork();
 22 if(id==0)
 23 {
 24   runchild();
 25   exit(0);
 26 }
 27 printf("run child success child pid is%d\n",id);
 28   }
 29   sleep(3);
 30   for(int i=0;i<N;i++)
 31   {
 32 int status=0;
 33 pid_t ret=waitpid(-1,&status,0);
   34 if(ret>0)
 35 {
 36 if(WIFEXITED(status))
 37 {
 38   printf("wait success pid is %d,exit code is %d\n",ret,WEXITSTATUS(status));
 39 
 40 }
 41 }
 42   }  
 43  sleep(2);  
 44  return 0;  
 45 } 

在这里插入图片描述
如果是bash进程看的就是你main函数的退出码

3.5options–非阻塞轮询

WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
在这里插入图片描述
在默认option是0的情况下,当子进程循环持续运行的时候,父进程只能阻塞等待,等待子进程运行结束回收,那么这种情况下父进程就会被投入子进程的等待队列里面,子进程结束的时候操作系统会将父进程从子进程的等待队列中唤醒

3.5.1非阻塞轮询

那么干等这种情况是非常拖慢运行效率的
父进程可以向子进程发起询问,询问子进程是否结束,检查不成功立马返回这叫做非阻塞,一直向子进程询问这就是非阻塞轮询
但是非阻塞轮询也是十分少见的,由上面的话语我们也可以感觉到这也是拖慢运行效率的
我们父进程可以隔一段时间向子进程发起询问,在这一段时间父进程可以去执行一些其他的任务
waitpid的返回值大于0,表示父进程等待成功,子进程结束
waitpid的返回值小于0,表示父进程等待失败
waitpid的返回值等于0,表示父进程问过子进程还没结束,返回0,表示还要再等

 46 int main()
 47 {  
 48  pid_t id=fork();
 49  if(id<0)
 50  {
 51printf("进程创建失败\n");
 52  }
 53  else if(id==0)
 54  {
 55//子进程
 56int cnt=5;
 57while(cnt--)
 58{
 59  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
 60  sleep(1);
 61}
 62exit(1);
 63  }
 64  else if(id>0)
 65  {
 66int status=0;
 67while(1)
 68{
 69pid_t ret=waitpid(id,&status,WNOHANG); 70if(ret>0)
 71{
 72   if(WIFEXITED(status))
 73   {
 74 printf("exit success,exit code is %d\n",WEXITSTATUS(status));
 75   //  printf("recycle success, ret is %d,exit sig is %d,exit code is %d\n",id,status&0x7F,(status>>8)&0xFF);
 76   break;
 77   }
 78   else 
 79 printf("进程出异常了\n");
 80}
 81   else if(ret<0){
 82 printf("wait failed\n");
 83   break;
 84   }
 85   else if(ret==0)
 86   {
 87 printf("子进程还没结束,再等\n");
 88   }
 89   sleep(2);
 90  }
 91 }
 92 return 0;
 93 }

在这里插入图片描述

3.5.2测试任务

46 #define NUM 5
 47 typedef void(*task_t)();
 48 task_t TASK[NUM];
 49 void task1()
 50 {
 51   printf("测试网络,pid is %d\n",getpid());
 52 }
 53 void task2()
 54 {
 55   printf("写日志,pid is %d\n",getpid());
 56 }
 57 void task3()
 58 {
 59   printf("检查内存,pid is %d\n",getpid());
 60 }
 61 int AddTask(task_t t);
 62 void InitTask()
 63 {
 64   for(int i=0;i<NUM;i++) TASK[i]=NULL;
 65   AddTask(task1);
 66   AddTask(task2);
 67   AddTask(task3);
 68 }
 69 int AddTask(task_t t)
 70 {
 71   int pos=0;
 72   for(;pos<NUM;pos++)
 73   {
 74 if(!TASK[pos]) break;
 75   }   
 76   if(pos==NUM) return -1;
 77   TASK[pos]=t;
 78   return 0;
 79 }
 80 void EXCUTE()
81 { 
 82   int pos=0;
 83   for(;pos<NUM;pos++)
 84   {
 85if(TASK[pos])
 86  TASK[pos]();
 87   }
 88 }
 89 int main()
 90 {
 91  pid_t id=fork();
 92  if(id<0)
 93  {
 94printf("进程创建失败\n");
 95  }
 96  else if(id==0)
 97  {
 98//子进程
 99int cnt=5;
100while(cnt--)
101{
102  printf("i am child pid is %d, ppid is %d,cnt is %d\n",getpid(),getppid(),cnt);
103  sleep(1);
104}
105exit(1);
106  }
107  else if(id>0)
108  {
109int status=0;
110   InitTask();
111while(1)
112{
113pid_t ret=waitpid(id,&status,WNOHANG);
114if(ret>0)
115{
116   if(WIFEXITED(status))
117   {
118 printf("exit success,exit code is %d\n",WEXITSTATUS(status));
119   //  printf("recycle success, ret is %d,exit sig is %d,exit code is %d\n",id,status&0x7F,(status>>8)&0xFF);
120   break;
121   }
122   else 
123 printf("进程出异常了\n");
124}
125   else if(ret<0){
126 printf("wait failed\n");
127   break;
128   }
129   else if(ret==0)
130   {
131 EXCUTE();
132 usleep(100000);
133// printf("子进程还没结束,再等\n");
134   }
135  }
136 }
137 return 0;
138 }

在这里插入图片描述
但是我们只是在父进程等待子进程中执行任务,所以等待子进程才是主要任务,父进程在这期间执行的任务只能是轻量化的任务
注意:waitpid能够保证父进程是最后一个退出的进程(子进程全部被回收)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值