• 【Linux】进程等待





    tips

    下面的代码可以循环检测进程。

    while :; do ps ajx | head -1; ps ajx | grep testwait | grep -v grep; sleep 1; echo "-------------------------------------"; done
    
    • 1

    一、进程等待是什么?

    通过系统调用wait/waitpid,来对子进程进行状态检测和回收。

    二、为什么要有进程等待?

    • 1.僵尸进程无法被杀死,需要通过进程等待来杀掉它,从而解决内存泄露的问题。 ——这是必须解决的
    • 2.需要通过进程等待,来获得子进程的退出情况。也就是我要知道布置给它的任务做的怎么样了。——这是可选做的

    进程等待的重点就是为了解决上述两个问题。

    三、怎么做到进程等待?

    先看看什么是进程等待

      7 int main()
      8 {
      9 
     10      pid_t id = fork();
     11      if(id < 0)
     12      {
     13          perror("fork");
     14          return 0;
     15      }
     16      else if(id == 0)
     17      {
     18          //child
     19          int cnt = 5;
     20          while(cnt)
     21          {
     22              printf("I am child,pid : %d , ppid : %d\n",getpid(),getppid());
     23              sleep(1);
     24              cnt--;
     25          }
     26 
     27          exit(0);
     28      }
     29      else
     30      {                                                                                                                                                                                     
     31          //parent
     32          int cnt = 10;
     33          while(cnt)
     34          {
     35              printf("I am parent,pid : %d , ppid : %d\n",getpid(),getppid());
     36              sleep(1);
     37              cnt--;
     38          }
     39 
     40          pid_t ret = wait(NULL);
     41          if(ret == id)
     42          {
     43              printf("wait success,ret = %d\n",ret);
     44          }
     45 
     46          sleep(5);
     47      }
     48 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    上面代码的意思是:
    先创建一个子进程,父子进程同时跑5s,前5s都处在阻塞状态,即S状态。
    中间5秒子进程退出,等待父进程来回收,此时子进程变成僵尸状态,即Z状态。
    后5秒父进程通过wait等待子进程退出后,回收子进程,此时只有父进程还在运行,
    随后父进程也退出。

    在这里插入图片描述

    下面是循环创建10个子进程:

      7 void Runchild()
      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         cnt--;
     15     }
     16 }
     17 
     18 int main()
     19 {
     20     int i = 0;
     21     //创建10个子进程
     22     for(;i<10;i++)
     23     {
     24         pid_t id = fork();
     25         if(id == 0)
     26         {
     27             Runchild();
     28             exit(0);
     29         }
     30     }
     31 
     32     sleep(10);
     33 
     34     //目前来说,进程等待是必须的
     35     for(i=0;i<10;i++)                                                                                                                                                                      
     36     {
     37         pid_t ret = wait(NULL);
     38         printf("wait success: %d\n",ret);
     39     }
     40 
     41     sleep(5);
     42     return 0;
     43 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    前5秒执行代码,创建10个子进程,父子进程同时运行。
    中间5秒,10个子进程退出,变成僵尸状态,等待父进程回收
    后5秒,父进程调用10次wait系统调用,对10个僵尸进程进行回收。
    回收5秒后,父进程退出。

    总结:目前来说,进程等待是必须的,这样才能杀掉僵尸状态,回收资源,防止内存泄露。


    问题1:
    如果父进程一直在等待子进程,而子进程一直不退出呢?

    如果父进程等不到子进程退出,父进程就会一直等,wait不返回结果,父进程就会一直处于阻塞状态。

    wait和waitpid

    在这里插入图片描述

    status参数

    wait函数只需要传入一个参数:status,代表进程的退出状态,一般传入的时候是一个整数,整数是32个比特位,一般只考虑低16位。

    在这里插入图片描述

    其中,退出core dump后面会讲,进程的退出信号可以查询:

    通过 kill -l命令查询到到有64个退出信号,由status变量的0~7个比特位表示。

    在这里插入图片描述

    进程的退出码由8~15个比特位表示。
    在这里插入图片描述

    其中0就表是进程正常运行,结果正确。


    问题:能不能自己创建一个全局变量status,子进程退出时将status变量设置成不同的退出码,代表不同的退出状态返回给父进程呢?

    答案是不可能的。**因为父子进程之间具有独立性。**当子进程要修改status时,会发生写时拷贝,父子进程的资源互相独立,子进程返回的status父进程是拿不到的,所以只能通过系统调用让操作系统帮父进程从子进程中拿资源。

    通过父进程对子进程进行等待,将status参数传入waitpid中,获取子进程的退出信息和退出码。

    if(id > 0)
    {
    	int status=0; // 低16位有效
    	pid_t ret = waitpid(id,&status,0);
    	if(ret == id)
    	{
    	    //低七位是退出信号,次低八位是退出码
    	    printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    核心代码如上:

    status&0x7F,即可获取到子进程的退出信号
    在这里插入图片描述
    通过status的次低8位,获取到子进程的退出码。
    (status>>8)&0xFF

    其实,库里面还提供了两个宏,来代替上面的stauts&0x7F, 和(status>>8)&0xFF

    • 1.WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) ——相当于把status&0x7F再加上一个逻辑取反。

    • 2.WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)——相当于(status>>8)&0xFF

     if(ret == id)
     {
         //低七位是退出信号,次低八位是退出码
         //printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
         if(WIFEXITED(status))
         {
             printf("父进程等待它的子进程成功,退出码为:%d\n",WEXITSTATUS(status));
         }
     }
     else
     {
         printf("父进程等待它的子进程失败!\n");
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    核心代码如上,进入if说明父进程等待的是它的子进程成功。
    进入else,说明父进程等待的不是它自己的子进程。

    所以,父进程只有等待它自己创建的子进程时才会成功。


    总结:

    • 1.只要通过wait/waitpid函数的status参数低7位判断是否为0,就能知道进程是否跑完。
    • 2.只要通过8~15位判断进程的退出码是多少,就知道进程运行结果如何。

    这就对应了进程的退出情况无非只有三种:

    • 1.进程正常退出,结果正确
    • 2.进程正常退出,结果不正确
    • 3.进程异常退出

    从此,就能通过waitpid,等待对应的进程,进而获取到对应进程的退出情况。

    options参数

    options参数的意思就是,让父进程选择什么样的方式等待子进程退出!

    下面提供了两种等待方式,阻塞等待和非阻塞等待!

    非阻塞轮询

    WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

    WNOHANG是一个宏,就表示非阻塞等待。

    先讲讲什么是轮询:

    将近期末考试,小吴是一个努力型学霸,小邓是一个摆烂型学渣,老师说:明天要考c语言了!
    小邓一听,顿时慌了,下课后马上打电话给小吴,对小吴说:“小吴,你的笔记和你这个人可以借我用一下吗?”
    小吴一听就知道了,这是小邓想要我教他呢!
    小吴说:“好,你等等,我在复习,等会就下去找你。”
    小邓听完就挂电话了,过了10秒中,小邓又打电话给小吴:“你好了没。”
    小吴说:“还没。” 啪的一声,小邓挂断了电话。又过了十秒,又打电话给小吴:“你好了没。” “还没。” 就这样,打了20几个电话,小吴说:“好了,我现在下去找你”。

    这个每隔一段时间小邓就打电话给小吴询问情况的过程就叫做轮询!!!

    而什么叫做非阻塞轮询呢?

    在小邓不断打电话给小吴询问情况的过程中,小邓学聪明了,傻等这也不是办法,所以小邓拿着一本c语言的教材在看,边看边等。意味着小邓在等待的过程中,能够做自己的事情!!!

    这就意味着非阻塞!!!

    所以,两者结合起来,就是非阻塞轮询!!!

    小吴就相当于操作系统,小邓就相当于父进程,父进程在不断向操作系统询问等待返回结果的过程,就是轮询的过程。
    而在轮询时父进程可以做自己的事情,就叫做非阻塞轮询!!!

    总结:WNOHANG等待方式就是非阻塞等待方式,如果等待的子进程没有结束,就返回0,如果等待成功,返回子进程的id。

    int main()
    {
        pid_t id = fork();
    
        if(id < 0)
        {
            perror("fork");
            exit(0);
        }
        else if(id == 0)
        {
            //child
            int cnt = 10;
            // int a = 10;
            // a/=0;
            while(cnt--)
            {
                printf("i am a child, pid %d, ppid %d \n",getpid(),getppid());
                sleep(1);
            }
            exit(11);
        }
        else 
        {
            //parent
            int cnt = 5;
            while(cnt--)
            {
                printf("i am a parent, pid %d, ppid %d \n",getpid(),getppid());
                sleep(1);
            }
    
            while(1)  //轮询
            {
                int status=0; // 低16位有效
                pid_t ret = waitpid(id,&status,WNOHANG); //非阻塞等待
                //pid_t ret = waitpid(id,&status,0); //阻塞等待
                //阻塞等待,父进程什么都做不了,只能在这里死等着子进程
    
                if(ret < 0) //等待失败
                {   
                    printf("等待子进程失败!\n");
                    break;
                }
    
                //等待成功
                else if(ret > 0)
                {
                    //低七位是退出信号,次低八位是退出码
                    //printf("wait success! id = %d , exit sig = %d, exit code = %d\n",ret,status&0x7F,(status>>8)&0xFF);
                    if(WIFEXITED(status))
                    {
                        printf("父进程等待它的子进程成功,退出码为:%d\n",WEXITSTATUS(status));
                    }
                    break;
                }
    
                else
                {
                    printf("子进程还未退出,再等等....\n");
                    //等的过程父进程可以做自己的事情
                    int a = 10;
                    int b = 20;
                    int c = a+b;
                    printf("父进程在进行相加运算,结果为:%d\n",c);
                }
    
                //每隔1秒等待1次
                sleep(1);
            }
            
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    pid_t ret = waitpid(id,&status,WNOHANG); 
    
    • 1

    这段代码,就是父进程在等待子进程时采用的是非阻塞等待(WNOHANG)
    不断执行while循环就是轮询。

    pid_t ret = waitpid(id,&status,0); 
    
    • 1

    这段代码,就是阻塞等待,如果子进程未退出,父进程就会在这里傻傻地等待子进程退出。自己什么事情都做不了。

    进程等待的原理

    父进程通过调用操作系统提供的系统调用waitpid,然后由操作系统接收到父进程的申请,去查询父进程对应的子进程的状态。
    如果子进程处于s状态,则操作系统会将子进程的状态结果返回给父进程。
    如果子进程处于z状态,则操作系统会将子进程的退出码exit_code,退出信号exit_signal拿出来返回给父进程。父进程收到信息后就知道该如何做下一步动作。

    在这里插入图片描述


    问题:为什么父进程不直接去子进程中获取子进程的运行状况呢?

    因为操作系统不相信任何人!!!

    这就比如说:A学校的有一个网络空间安全方面的学生特别厉害,B学校的校长想邀请A学校的那个学生帮助B学校参加一项网络空间安全的比赛,B校长直接找到A学校的那名学生叫他去参加比赛。
    这是不能这样做的,因为B校长想要找A学校的学生,还得问过A校长才行!!!

    操作系统对父进程也是如此,父进程不能直接获取子进程的数据,必须通过操作系统这一媒介!!!

    因为要是父进程修改了子进程的数据了呢???

    所以,操作系统不相信任何人!!!

    总结

    这篇文章详细介绍了进程等待的各种细节。

  • 相关阅读:
    Django系列:Django开发环境配置与第一个Django项目
    轻量级神经网络算法-总结对比
    P 算法与 K 算法
    流程图规范
    计算机组成原理---第三章存储系统---主存储器和CPU的连接
    电热毛巾架触摸芯片-DLT8SA11A-杰力科创
    ChatGPT如何训练自己的模型
    书签收藏难整理?这款书签工具管理超方便
    Servlet的使用手把手教学(一)
    程序员凭本事赚钱被没收所得,是否体现了“重刑主义”?
  • 原文地址:https://blog.csdn.net/w2915w/article/details/134306363