【Linux系统】进程等待:告别僵尸进程深入理解Linux进程同步的核心密码

Linux系列



前言

Linux系统中,进程等待(Process Waiting)是多进程编程中的核心机制,指父进程通过系统调用(如wait()、waitpid()等)主动等待子进程的结束,并回收其资源。


一、进程等待的核心目的

在进程状态篇,我给大家介绍了僵尸进程的问题。子进程执行结束后,如果父进程不及时处理子进程的状态信息,子进程就会进入僵尸状态,处于僵尸状态的进程无法通过信号杀死(如kill、ctrl+c),若父进程一直不对子进程结束信息处理,子进程的资源就无法得到释放,就会造成内存泄漏问题。

回收子进程资源

子进程退出后,内核会保留其退出状态和系统调用资源,直到等到父进程调用系统接口对子进程等待。如果不等待,子进程会变成僵尸进程,占用系统资源。

同步父子进程的信息

父进程创建子进程的目的就是让子进程执行一些任务的,有时父进程需要根据子进程的执行结果来决定后续的执行。

获取子进程状态

父进程通过wait()获取子进程的代码(成功/失败/异常),决定后续逻辑。

其实子进程的创建目的已经注定了父进程需要得到子进程的执行结果。

二、进程等待的实现方式

2.1 wait()函数


头文件
#include<sys/tyoes.h>
#include<sys/wait.h>
参数
整形指针,这个参数是一个输出型参数,目的是将函数内部信息带出(后面我们详细介绍)。
返回值
等待成功返回对应子进程的PID,失败则返回-1.

示例:

  1 #include<stdio.h>
  2 #include<sys/wait.h>
  3 #include<unistd.h>
  4 int main()
  5 {
    
    
  6   pid_t id=fork();
  7   if(id==0)
  8   {
    
    
  9     int cnt=5;
 10     while(cnt--)
 11     {
    
    
 12        printf("I am child,pid:%d,ppid%d\n",getpid(),getppid());
 13        sleep(1);  
 14     }  
 15   }  
 16   else  
 17   {
    
      
 18     int cnt=10;  
 19     while(cnt--)  
 20     {
    
      
 21       printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());                                                          
 22       sleep(1);  
 23     }  
 24    int status;
 25    pid_t ret= wait(&status);  
 26    if(ret==id)printf("父进程等待成功\n");
 27     sleep(20);
 28   }
 29   return 0;
 30 }

在这里插入图片描述

在这里插入图片描述
可以看到子进程结束后变为,僵尸状态当父进程对子进程等待成功后,子进程被释放,父进程继续自己的工作。

while :; do ps ajx|head -1&&ps ajx|grep test;sleep 1;echo "------------" ;done

接下来我们来介绍exit的参数:
在介绍进程等待原因时我们就说,父进程要获取子进程的退出状态信息,而这个获取方法就是通过status这个整形。

 31                  16 15       8 7        0
+---------------------+----------+----------+
|     保留位 (0)       | 信号编号  | 退出码   |
+---------------------+----------+----------+
|       高位           |   次高位   |   低位   |

在这个整形中0~7位存储的是进程退出码,8~15存储的是信号编号,要想知道终止信息,我们就需要对这个整形进行解析,当然我们可以通过位运算进行解析,不过库中给我们提供了两个用来解析的宏:

  • WIFEXITED(status):判断子进程是否正常退出(通过 exit()return)。
  • WEXITSTATUS(status):若子进程正常退出,提取其退出状态码(0~255)。
  1 #include<stdio.h>                                                                               
  2 #include<sys/wait.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
    
    
  7   pid_t id=fork();
  8   if(id==0)
  9   {
    
    
 10     int cnt=3;
 11     while(cnt--)
 12     {
    
    
 13        printf("I am child,pid:%d,ppid%d\n",getpid(),getppid());
 14        sleep(1);
 15     }
 16     exit(1);
 17   }
 18   else 
 19   {
    
    
 20     int cnt=5;
 21     while(cnt--)
 22     {
    
    
 23       printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
 24       sleep(1);
 25     }
 26    int status;
 27    pid_t ret= wait(&status);                                                                    
 28    if(ret==id)
 29    {
    
    
 30      if(WIFEXITED(status))
 31      printf("父进程等待成功,并且子进程正常终止,退出码:%d\n",WEXITSTATUS(status));
 32    }
 33   return 0;
 34   }
 35 }

在这里插入图片描述
你也可以尝试使用信号杀死子进程。

2.2 waitpid()函数

在这里插入图片描述

头文件

#include<sys/tyoes.h>
#include<sys/wait.h>

参数

  1. pid:要等待进程的PID(如果父进程有多个子进程,我们可以指定等待,若为-1则同wait,随机等待)
  2. status:同wait
  3. options:控制等待行为(0为阻塞等待,WNOHANG为非阻塞轮询)。

返回值

成功时返回子进程的PID如果为,失败返回-1,如果第三个参数为WNOHANG未等待到返回0
先了解,后面会详细分析

在这里指定子进程PID访问不方便演示,大家可以自己创建多个子进程并查询某个子进程的PID,然后指定等待达到验证效果。

阻塞等待

在父进程等待子进程期间,父进进入阻塞状态,程序停止运行,直到等到子进程终止状态信息,父进程才会继续执行,在这期间父进程不会有任何行为。(这是给我自己看到->怎么等待呢?进入子进程PCB的等待队列,所以等待不一定等待是硬件资源

非阻塞轮询

父进程等待子进程时,不会进入阻塞状态,每隔一段时间程序就会看看waitpid是否完成对子进程的等待,如果没有完成,父进程就会利用空闲资源去做一些轻量级任务(如打印给网卡状态等)。

    1 #include<stdio.h>
    2 #include<sys/wait.h>
    3 #include<unistd.h>
    4 #include<stdlib.h>
    5 int main()
    6 {
    
    
    7   int status;
    8   pid_t id=fork();
    9   if(id==0)
   10   {
    
    
   11     int cnt=3;
   12     while(cnt--)
   13     {
    
    
   14        printf("I am child,pid:%d,ppid%d\n",getpid(),getppid());
   15        sleep(1);
   16     }
   17     sleep(10);
   18     exit(1);
   19   }
   20   else
   21   {
    
                                                                                                                                 
   22     pid_t ret;
   23     int cnt=5;
   24     while(cnt--)
   25     {
    
    
   26       ret= waitpid(0,&status,WNOHANG);
   27       if(ret==0)  
   28       printf("还没有等到子进程终止状态,再等等....\n"); //可在else语法块中执行轻量级任务,这里没展示                                                                          
   29       printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());  
   30       sleep(1);  
   31     }  
   32    if(ret==id&&WIFEXITED(status))  
   33    {
    
      
   34      printf("父进程等待成功,并且子进程正常终止,退出码:%d\n",WEXITSTATUS(status));  
   35    }  
   36   }  
   37   return 0;  
   38 }  

在这里插入图片描述
这里还可以使用信号进行等待,到后面会给大家分享。

总结

进程等待是Linux多进程的基石,主要用来进行,资源等待、同步等待、I/O操作等待,进程等待是操作系统实现多进程并发执行和资源有效管理的重要手段,它使多个进程能够合理的共享资源,提高系统整体的性能和效率。