目录
一、前言
在进程状态学习的那一章中,我们介绍了如果一个子进程在进程结束后,其进程状态会变为僵尸状态,等待父进程对其进行回收。那么在这一章中,我们就将为大家介绍进程回收的方式——进程等待
二、进程等待
1.什么是进程等待
进程等待是指一个进程因为某些原因暂时无法继续执行,必须等待某个条件满足或某个事件发生后才能继续执行的状态。在本文中主要是指父进程对子进程进行等待子进程退出并且将子进程进行资源回收的过程。
这是操作系统进行资源管理、任务调度和同步协调的重要机制之一
2.进程等待的必要性
●子进程进入僵尸进程无法被杀死,需要通过进程等待来杀掉它,进而解决内存泄漏问题
●我们需要通过进程等待,获取退出情况,因为作为用户或者说对父进程来说,我们想要知道布置给子进程的任务,它完成的怎么样(可选)
- 1 #include
- 2 #include
- 3 #include
- 4 #include
- 5 #include
- 6 #include
- 7
- 8
- 9 int main()
- 10 {
- 11 pid_t id=fork();
- 12 if(id<0)
- 13 {
- 14 perror("fork");
- 15 return 1;
- 16 }
- 17 else if(id == 0)
- 18 {
- 19 //child
- 20 int cnt=5;
- 21 while(cnt)
- 22 {
- 23 printf("I'm a child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
- 24 cnt--;
- 25 sleep(1);
- 26 }
- 27 exit(0);
- 28 }
- 29 else
- 30 {
- 31 //parent
- 32 printf("I'm a father, pid:%d, ppid:%d\n",getpid(),getppid());
- 33 }
- 34 sleep(100);
- 35 return 0;
- 36 }
当我们执行上述代码,并当子进程结束时,我们查询此时子进程的状态,的确是处于僵尸僵尸状态,等待父进程进行回收
3.进程等待的方法
进程等待常用的方法一共用两种:wait()和waitpid(),下面我们为大家介绍这两个系统接口。
①wait方法
#include
#include
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
对于该函数的参数status,我们会为下一个waitpid()为小伙伴们统一介绍,我们现在就设置成NULL来先简单使用下该接口,看是否处于僵尸进程的子进程能够被父进程回收
- 1 #include
- 2 #include
- 3 #include
- 4 #include
- 5 #include
- 6 #include
- 7 #include
- 8
- 9
- 10 int main()
- 11 {
- 12 pid_t id=fork();
- 13 if(id<0)
- 14 {
- 15 perror("fork");
- 16 return 1;
- 17 }
- 18 else if(id == 0)
- 19 {
- 20 //child
- 21 int cnt=5;
- 22 while(cnt)
- 23 {
- 24 printf("I'm a child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
- 25 cnt--;
- 26 sleep(1);
- 27 }
- 28 exit(0);
- 29 }
- 30 else
- 31 {
- 32 //parent
- 33 int cnt=10;
- 34 while(cnt)
- 35 {
- 36 printf("I'm a father, pid:%d, ppid:%d\n",getpid(),getppid());
- 37 cnt--;
- 38 sleep(1);
- 39 }
- 40 pid_t ret=wait(NULL);
- 41 if(ret == id)
- 42 {
- 43 printf("wait success,ret:%d\n",ret);
- 44 }
- 45 }
- 46 sleep(20);
- 47 return 0;
- 48 }
-
我们发现当子进程结束处于僵尸状态的时候,父进程确实可以进行wait()来对子进程进行回收。
于此同时,我们也发现当子进程不退出的时候,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,即处于阻塞状态,因为也说明如果一个进程阻塞,并不一定说明该进程在等待硬件资源,也可以等待软件资源,比如这里父进程等待子进程结束
②waitpid方法
#include
#include
pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
参数:
pid:
Pid=-1,等待任一个子进程。与wait等效。
Pid>0.等待其进程ID与pid相等的子进程。
status:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options:
WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
●如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
●如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
●如果不存在该子进程,则立即出错返回。
- //为避免冗余,提取了代码修改部分,其余部分读者可以自行参考上面代码
- 12 pid_t id=fork();
- 13 if(id<0)
- 14 {
- 15 perror("fork");
- 16 return 1;
- 17 }
- 18 else if(id == 0)
- 19 {
- 20 //child
- 21 int cnt=5;
- 22 while(cnt)
- 23 {
- 24 printf("I'm a child, pid:%d, ppid:%d, cnt:%d\n",getpid(),getppid(),cnt);
- 25 cnt--;
- 26 sleep(1);
- 27 }
- 28 exit(1); //退出码设置为1
- 29 }
- 30 else
- 31 {
- 32 //parent
- 33 int cnt=10;
- 34 while(cnt)
- 35 {
- 36 printf("I'm a father, pid:%d, ppid:%d\n",getpid(),getppid());
- 37 cnt--;
- 38 sleep(1);
- 39 }
- 40 int status=0;
- 41 pid_t ret=waitpid(id,&status,0);
- 42 if(ret == id)
- 43 {
- 44 printf("wait success,ret:%d, status:%d\n",ret,status);
- 45 }
- 46 }
- 47 sleep(3);
通过上述代码,我们发现与waitpid()函数也能够将子进程进行回收,同时参数ret也确实是我们想要回收的子进程id,但是第二个返回的参数ststus为256不知道小伙伴们是否有疑问,我们不是设置了子进程返回的退出码为1吗,为啥最后却打印的是256呢?这里我们就为大家详细介绍status这个参数。
③获取子进程status
●wait和waitpid,都有一个status参数,该参数是一个输出型参数,帮助我们查看子进程退出的场景(具体的三种场景在该文【Linux】——进程控制(上)提及),由操作系统填充。
●如果传递NULL,表示不关心子进程的退出状态信息。
●否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特位):
因此,在上面一个代码中,为什么打印出来是256,是因为进程的退出状态为status的第8位到第15位,我们将子进程的退出码设置为1,当子进程正常退出的时候,status只有第8位为1,其余位都为0,打印出来自然就是256,当然我们也可以验证一下。
- if(ret == id)
- {
- printf("wait success,ret:%d
- , exit sig:%d
- ,exit code:%d\n"
- , ret,status&0x7F,(status>>8)&0xFF);
- //7F即取出低16位中低7位,后者则是取出取出高八位
- }
④options参数
options参数是一个输入型参数,我们可以通过输入0或者WNOHANG(宏)来控制父进程在进行等待的时候是一直等待子进程退出(阻塞式轮询),还是如果子进程没完成,父进程先去做其他事(即非阻塞轮询)
对于阻塞式轮询来说,非阻塞可以让父进程等待子进程时时不会阻塞程序的执行,这意味着父进程可以继续处理其他任务,从而提高整体的响应速度和效率
下面就让我们来实现一个非阻塞轮询
- else
- 31 {
- 32 //parent
- 33 int status=0;
- 34 while(1)
- 35 {
- 36 //轮询
- 37 pid_t ret=waitpid(id,&status,WNOHANG); //非阻塞
- 38 if(ret>0)
- 39 {
- 40 if(WIFEXITED(status))
- 41 {
- 42 printf("进程是正常跑完的,退出码:%d\n",WEXITSTATUS(status));
- 43 }
- 44 else
- 45 {
- 46 printf("进程出异常了\n");
- 47 }
- 48 break;
- 49
- 50
- 51 }
- 52 else if(ret <0)
- 53 {
- 54 printf("wait failed\n");
- 55 break;
- 56 }
- 57 else
- 58 {
- 59 printf("子进程还没退出,我在等等...\n");
- 60 sleep(1);
- 61 }
- 62
- 63 }
三、结语
到此为止,本文有关进程等待的讲解就到此结束了,如有不足之处,欢迎小伙伴们指出呀!
关注我 _麦麦_分享更多干货:_麦麦_-CSDN博客
大家的「关注❤️ + 点赞? + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下期见!



评论记录:
回复评论: