Linux:历程等待究竟是什么?如何解决子历程僵尸所带来的内存走漏问题? ...

打印 上一主题 下一主题

主题 1047|帖子 1047|积分 3141

一、历程等待的概念

 历程等待通常是指:父历程通过wait()/waitpid()的方式,让父历程对子历程进行资源回收的等待过程!!
二、历程等待存在的意义

 历程等待通常是为了解决以下两种环境:

  • 解决子历程僵尸所带来的内存走漏问题,对僵尸子历程进行资源回收! 缘故原由在于当子历程僵尸后,便“刀枪不入”了。即使是操纵系统也没法对僵尸历程进行资源回收,进而导致内存走漏问题。
  • 让父历程得到子历程运行效果(代码运行正常效果精确、代码运行正常效果错误、代码非常)。父历程创建子历程,通常是希望子历程帮父历程执行某些任务。但子历程任务执行的如何,父历程需要得到反馈。此时父历程可以通过历程等待的方式来获取子历程的退出信息(退出码和退出信号)。
三、如何进行历程等待

下面依次介绍历程等待所需调用的接口:wait()/waitpid()。
3.1 wait()是实现历程等待

1、wait()原型

  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t wait(int *status);
复制代码

  • 返回值:如果乐成返回被等待历程的pid,否则返回-1.
  • 参数:ststus为输出型参数,获取子历程的退出信息,由操纵系统自动填充。(后续会单独详细介绍,这里我们临时不关心该参数,设为NULL)
  • wait()用于等待任意历程,而waitpid则可以等待任意历程!!
2. 验证wait()能回收僵尸子历程的空间

 下面如许一段代码:fork()创建子历程,让子历程运行约5秒后退出但父历程不退出。此时子历程变为僵尸历程,历程状态为Z。此时调用父历程调用wait()接口回收僵尸子历程的资源空间。
【源代码】:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. #include <unistd.h>
  7. void worker()
  8. {
  9.     int cnt = 5;
  10.     while(cnt)
  11.     {
  12.         printf("I am child process, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);
  13.         sleep(1);
  14.     }
  15. }
  16. int main()
  17. {
  18.     pid_t id = fork();
  19.     if(id == 0)
  20.     {
  21.         //child
  22.         worker();
  23.         exit(0);  //子进程执行完worker()后直接退出,变成僵尸状态  
  24.     }   
  25.     else{
  26.         //parent   
  27.         sleep(10);   
  28.         pid_t rid = wait(NULL);//对子进程进行回收                                                                                       
  29.         if(rid == id)   
  30.         {   
  31.             printf("child process being recyceled sucess!, pid:%d, rid:%d\n", getpid(), rid);   
  32.         }
  33.         sleep(3);
  34.     }
  35.     return 0;
  36. }
复制代码
【运行效果】:

 我们观察左边监视脚本发现,子历程在执行5次代码后退出,历程状态变为Z。一段时间后,父历程调用wait()函数对子历程进行回收,子历程消散。即父历程通过wait()实现了对子历程的回收!!
tips:

  • 父历程调用wait()后,如果子历程没有退出,父历程会在wait上发生历程壅闭。直到子历程僵尸,wait自动回收后,返回被回收的子历程pid。
  • 对于多个历程来说,谁先被调理是未知的,由内核调理算法决定。但可以肯定的是,父历程肯定是最后退出的!
3.2 waitpid()实现历程等待

1、系统调用接口waitpid()原型

  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t waitpid(pid_t pid, int *status, int options);
复制代码

  • 参数pid: 如果pid=-1,等待任意历程,和wait效果一样。如果pid>0,等待历程ID和pid值相等的子历程!
  • 参数status:子历程的退出信息。status为NULL,表现不关心子历程的退出状态信息,否则操纵系统会将子历程的退出码和错误码相关信息写入该参数中。(后续具体介绍实在现机制)
  • 参数options: 为0表现壅闭等待。options除了0外,还可以被设置为WNOHANG,此时表现父历程以非壅闭方式进行等待(非壅闭 + 轮询方案)。
  • 返回值:当正常退出时,waitpid返回网络到的子历程ID;如果历程非常,返回-1,此时errno会被设置为对于的错误码;如果历程接纳非壅闭轮询方案,即将options设置为WNOHANG,如果子历程waitpid网络到的子历程没有退出,此时返回0!!
四、获取子历程status实现机制

 在wait()/waitpid()中,均存在参数status,该参数是一个输出型参数,由操纵系统自动填充。如果该参数被设为NULL,表现不关心子历程的退出信息;否则OS会通过status的值,来将子历程相关退出信息返回给父历程!!
status如何保存相关信息?
 status是int范例,32bit。这里我们仅研究低16位!!
 其中status的最低7位保存子历程的退出信号(exit signal);第8位表现的是core dump标志;9~16位表现的是历程的退出码(exit code)

status中保存信息验证
 下面我们来做实验:我们通过fork()创建出子历程,然后让子历程运行约3秒;此时父历程通过waitpid以壅闭方式对子历程进行等待回收。然后通过位运算对status进行处置惩罚,获取status中的子历程退出码和退出信号。
【源代码】:
  1. #include <stdio.h>   
  2. #include <unistd.h>      
  3. #include <sys/types.h>   
  4. #include <sys/wait.h>   
  5.    
  6. int main()   
  7. {   
  8.     int status = 0;   
  9.     pid_t id = fork();   
  10.     if(id == 0)   
  11.     {   
  12.         int cnt = 3;   
  13.         while(cnt)   
  14.         {   
  15.             printf("I am child, cnt:%d\n", cnt--);   
  16.             sleep(1);   
  17.         }   
  18.         exit(3);   
  19.     }   
  20.     else if(id > 0)   
  21.     {   
  22.         pid_t rid = waitpid(id, &status, 0);//以阻塞方式等待   
  23.         printf("I am parent, pid:%d, rid:%d, status:%d, exit code:%d, signal:%d\n", getpid(), rid, status, (status>>8)&0xFF, status&0x7F);               
  24.     }   
  25.     return 0;   
  26. }  
复制代码
【运行效果】:

 我们发现status变量中确实保存着子历程的退出码和退出信号等相关信息。
在系统中提供了一些宏函数,用于直接获取历程的相关退出信息,具体如下:


  • WIFEXITED(status): 若为正常终止子历程返回的状态,则为真。(查看历程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子历程退出码。(查看历程的退出码)
父历程如何得知子历程的退出信息(底层执行流程)
 在子历程pcb中存在如下几个变量,分别用于保存历程的状态、退出码、退出信号:(Linux为例)
  1. strucr task_struct{
  2.         int exit_state;        //退出状态
  3.         int exit_code;        //退出码
  4.         int exit_signal;//退出信号
  5. }
复制代码
 当子历程退出时,操纵系统会将子历程的退出码和退出信号保存到子历程PCB的exit_code变量和exit_signal变量中。
 而父历程通过waitpid/wait等待子历程时,OS会将子历程PCB中的退出码和退出信号通过组合放入status变量中,并将子历程的状态从Z改成S!!
 此时,父历程便可通过status来获取子历程退出信息,子历程可以被操纵系统回收。
五、壅闭等待和非壅闭等待

 前面我们介绍waitpis接口时提到过,options参数设为0,表现父历程进行的时壅闭等待;设为WNOHANG表现父历程以==(非壅闭方式),即非壅闭轮询方式==进行历程等待。
 那两种等待方式究竟是什么?有什么区别呢?
5.1 壅闭等待

 父历程以壅闭方式进行等待和平凡壅闭历程一样。
 当父历程调用waitpid接口等待子历程时,如果此时子历程没有退出,操纵系统会将父历程设置为壅闭历程,然后将父历程的PCB链入到子历程的等待队列中。一旦子历程退出,操纵系统会将父历程PCB重新加载到运行队列中等待调理!
5.2 非壅闭等待(非壅闭 + 轮询方案)

  非壅闭等待是指父历程在等待子历程时发现子历程还未退出,此时父历程和壅闭等待一样不停在"原地等地子历程运行结束"。父历程会执行一些其他任务,并每隔一段时间查看子历程是否退出。一旦子历程退出后,父历程才会开始执行后续步伐。
非壅闭等待的好处就是让父历程在等待时,可以做一些本身占据时间不多的任务!!
六、非壅闭轮询方案示例演示

 下面我们通过fork创建子历程。然后让子历程做一些工作(打印输出一些信息,整个过程约10s),此时父历程通过waitpid接口进行非壅闭轮询方案进行等待。在等待过程中,我们让父历程做一些本身的“小任务”(这些小任务,博主同样接纳输出信息取代,各位可根据现实环境修改)
【源代码】:
轮询时,父历程执行任务:
 博主将任务简化为输出一些信息,各位可自行更改任务
  1. void download()
  2. {
  3.     printf("This download task is running!\n");
  4. }
  5. void writelog()
  6. {
  7.     printf("This write log task is running!\n");
  8. }
  9. void printinfo()
  10. {
  11.     printf("This print info task is running!\n");
  12. }
复制代码
大致框架,父历程和子历程执行任务:
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. #include <unistd.h>
  7. #define TASK_NUM 5
  8. void worker(int cnt)                                                                                                                                    
  9. {
  10.     printf("I am child, pid:%d, cnt:%d\n", getpid(), cnt);
  11. }
  12. int main()
  13. {
  14.         //下面5行模拟父进程的任务被加载好了,方便父进程等待子进程时被执行
  15.     task tasks[TASK_NUM];
  16.     Init(tasks, TASK_NUM);//初始化
  17.     taskadd(tasks, download); //加载任务
  18.     taskadd(tasks, writelog);
  19.     taskadd(tasks, printinfo);
  20.     pid_t id = fork();
  21.     if(id == 0)
  22.     {//child
  23.          int cnt = 5;
  24.          while(cnt)
  25.          {
  26.              worker(cnt--);
  27.              sleep(1);
  28.          }
  29.          exit(3);
  30.     }
  31.                                                                                                                                                         
  32.     //parent
  33.     while(1)
  34.     {
  35.          int status = 0;
  36.          pid_t rid = waitpid(id, &status, WNOHANG);
  37.          if(rid > 0)
  38.          {//子进程正常退出
  39.               printf("wait sucess!\n, pid:%d, rid:%d, exit code:%d, signal:%d\n",getpid(), rid, (status>>8)&0xFF, status&0x7F);
  40.               break;
  41.          }
  42.          else if(rid == 0)
  43.          {//父进程等待成功,但子进程没有退出。父进程开始做自己的小任务,一段时间后在查询子进程是否退出
  44.              printf("------------------------------------------------\n");
  45.              printf("wait sucess, but chils alive, wait again!\n");
  46.              executeTask(tasks, 3);
  47.              printf("------------------------------------------------\n");
  48.          }
  49.          else
  50.          {//子进程退出异常
  51.              printf("wait failed!\n");
  52.              break;
  53.          }
  54.          sleep(1);
  55.     }
  56.     return 0;
  57. }
复制代码
父历程加载任务代码:
  1. void Init(task tasks[], int num)
  2. {
  3.     for(int i = 0; i < num; i++)
  4.     {
  5.         tasks[i] = NULL;
  6.     }                                                                                                                                                   
  7. }
  8. int taskadd(task tasks[], task t)
  9. {
  10.     for(int i = 0; i < TASK_NUM; i++)
  11.     {
  12.         if(tasks[i] == NULL)
  13.         {
  14.             tasks[i] = t;
  15.             return 1;//增加任务成功
  16.         }
  17.     }
  18.     return 0;//增加任务失败
  19. }
  20. void executeTask(task tasks[], int num)
  21. {
  22.     for(int i = 0; i < num; i++)
  23.     {
  24.         tasks[i]();
  25.     }
  26. }
复制代码
运行效果:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表