进程终止和进程等待

一 进程终止

        (1)exit和return 

        先前已经了解了进程创建,以及进程大致相关的数据结构,但是有个小知识一直没提及,那就是exit,还有就是return 0。这两个的作用有点相似,都可以终止进程,但又有点不同,例如return 只有在main函数内才可以结束进程,而exit在任意地方都可以结束进程。

终止场景

1:代码正确跑完,结果正确

2:  代码跑完,结果不正确

3:  异常终止

        我们之前写代码的时候总是先写个main函数,然后return 0,那为什么返回0,返回给谁呢?在linux下是bash创建子进程是帮我们执行代码的,bash这个父进程是需要关心子进程执行的怎么样了,因为bash要向用户报告执行情况,所以需要子进程的退出信息。

        echo $?能获得最近进程的退出码,获得退出码后,lastcode貌似会被更新为0,所以这个时候再来一次echo $呢?,此时获得的是lastcode的初始值0,可能有人会说第二次echo &?获得的lastcode是echo获得的退出码,我认为echo此时没有创建子进程,为什么说echo $?的时候不创建子进程呢?  如果创建了子进程,第一次echo $?会更新lastcode,那退出码不就一直是子进程echo自己的吗,实际上如下:

退出码是1,如果是echo的退出码,那绝对是正常退出的返回码0,不会是1。

        后面我实现shell的时候浅浅想过这个问题,而且我认为echo有时候是会创建子进程的,例如重定向的时候,具体原因不好说,但和重定向有不小的关系,后续再谈。

(2)出错码errno介绍

        因为库函数的返回值并不能说明出错原因,所以想要知道出错码可以看——errno,errno会记录最后一个调用失败的库函数的错误码,但是要转为错误原因,还得用strerror,perror内部也是用了strerror和errno,strerror和perror的区别在于,perror可以给出错文字添加额外的字符串,例如函数名,可以提醒出错位置,可以精准锁定bug。

(3)出现异常,获得的退出码无效?

        有一种解释是说出现异常往往是return之前发生的,没有退出码,所以无效,实际上我去测试让子进程sleep的时候被kill掉,此时还没return,但是退出码却有值,虽然显示的是未知错误,我估计是调用main函数的CRT_START()函数发现main没有退出码,然后外部又要mian的退出码,只能给个随机值了,这个随机值可能就对应一个未知错误。

        此时这个退出码无疑是无意义的,但是还有种情况是我们正常return 了一个未知的退出码,此时进程还没结束(因为main函数结束了,但不代表进程结束了),此时进程被kill了,这个退出码是有用的。

        这两种情况都有异常和退出码,但无法区分这个异常是在返回前出的,还是已经main函数已经return了后出的,感觉也不好区分,所以只能认为退出码无效。简单理解就是你这个进程出了异常,多多少少进程本身都有点错误,所以我们认为执行结果—退出码不能相信。

        如何判断有无异常,看有无信号?(这个下面进程等待会提及)

(4)exit和_exit区别

        _exit是系统调用接口,而exit内部是封装了_exit的,我们来看看它们在结束程序时有什么不同。

test.c ? ?                                                                                                                                                             ?? buffers 
  1 #include<stdio.h>
  2 #include<sys/types.h>
  3 #include<unistd.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7    printf("begin\n");
  8    int ret = fork();
  9    if(ret==0)
 10    {
 11         while(1)
 12         {
 13             printf("我是子进程,id:%d",getpid());
 14             exit(0);
 15         }
 16    }
 17    else
 18    {                                                                                                                                                                               
 19        int cnt = 1;
 20        while(cnt)    
 21        { 
 22            printf("我是父进程,id:%d",getpid());
 23            cnt--;
 24            _exit(0);
 25        }
 26    }
 27     return 0;
 28 }
~

        执行结果如下,有个问题就是,父进程打印的数据没了,子进程的却会显示,奇怪这是为什么呢?

        这个就和缓冲区有关了,在windows我们对缓冲区没太多感觉,应该是vs做了特殊封装,让我们几乎感觉不到缓冲区,printf打印其实是往缓冲区打印的,而exit会刷新这个缓冲区,这个缓冲区是用户的,_exit这个系统调用看不见,简单理解缓冲区就是一段malloc的空间,设计者在写系统调用的时候,他怎么会去看你用户malloc的空间要不要刷新呢?

二 什么是进程等待

        是调用系统调用waitpid和wait查看子进程状态和回收子进程资源。

三 为什么要有进程等待

        首先子进程是父进程创建出来帮自己干活的,所以设计者认为需要给父进程提供能获取到子进程退出信息的接口,有很多场景我们都是必须要知道子进程的退出信息的,这个接口是必要的,这就是需要进程等待的原因

        那不能用全局变量吗,不行,因为进程具有独立性父进程看不到子进程的数据,所以拿不到。还有就是子进程退出后会把退出信息存到pcb数据结构中,这个数据结构存在内核数据区,是操作系统内部的数据,是不允许用户访问,简单理解,父进程是我们用户写的代码运行产生的,也就代表用户,所以父进程无法访问子进程的数据,所以必须通过系统调用返回数据。

        而为了让父进程能获取到,会让子进程退出后保持僵尸,不让系统将其处理掉,等待父进程读取状态信息,当父进程获取到子进程退出信息后,就可以销毁子进程了,所以进程等待还附有回收资源的功能。

四 进程等待代码

        目前来看,进程等待是必须的,因为要用于回收子进程资源。那如何进行进程等待,如何使用系统调用呢?

wait介绍

man 3 wait查看man手册中的wait函数介绍。wait参数先不管,等会介绍完waitpid也就知道了

先来看看如何使用。

  1 #include<unistd.h>
  2 #include<stdio.h>
  3 #include<stdlib.h>
  4 #include <sys/types.h>
  5 #include <sys/wait.h>
  6 int main()
  7 {
  8     int id = fork();//创建子进程
  9     if(id < 0)
 10     {
 11         perror("fork error:");
 12     }
 13     else if(id == 0)//子进程
 14     {
 15 
 16         int cnt = 5;
 17         while(cnt--)
 18         {
 19             printf("我是子进程: pid:%d , ppid: %d \n",getpid(),getppid());
 20             sleep(1);
 21         }
 22         exit(0);  退出
 23     }
 24     else
 25     {
 26         int cnt = 10;
 27         while(cnt--)
 28         {
 29             printf("我是父进程: id: %d pid: %d \n ",getpid(),getppid());
 30             sleep(1);
 31         }
 32          //等待子进程
 33         int ret = wait(NULL);                                                                                                                                                      
 34         if(ret == id )//返回正确
 35         {
 36             printf("回收完成\n");
 37         }
 38         else
 39         {
 40             perror("wait\n");
 41         }
 42         sleep(3);
 43     }
 44     return 0;
 45 }

让子进程先退出,父进程一直在printf,此时子进程保持僵尸状态,执行到wait后,回收子进程。

        若是子进程不退呢,父进程将会进入阻塞状态。之前在进程状态描述(scanf场景下)的阻塞状态是在等硬件资源,而现在是在等函数返回值,是在等软件资源,所以说等待资源就会处于阻塞状态。

        wait是等任意一个子进程,那多个子进程会先回收谁,这是不确定的。

waitpaid介绍

        可获得指定子进程的退出信息

参数1:传要回收子进程的pid,如果为-1,表示任意回收一个子进程。

参数2:status参数介绍,首先呢它是可以获取子进程的退出码,若不需要,传NULL即可。


 59           int id = fork();//创建子进程
 60          if(id == 0)
 61          {
 63              exit(1);
 64          }
 69         int status = 0;
 70         int ret = waitpid(-1, &status,0);
 71         printf("回收完成,id : %d 退出码: %d  \n",ret,status);
 73     return 0;
 74 }

        退出码为什么是256呢,子进程不是exit(1)吗,按道理退出码应该是1,这是为什么呢?这就要说status的构成了。0 - 6号比特位上存信号,第7位上是用于core dump,8-15用于退出码,其余位置暂时不关心。

        那如何提取退出码和信号呢?位运算呗。但是这个对程序员的要求有点高,它需要程序员了解status的构成,所以系统提供了一些宏来帮助提取。

        前一个是提取退出码,后一个是判断进程是否收到信号。0表示收到,未收到返回真。如果要看是收到什么信号,此时直接打印status即可。

返回值和等待失败

        当我们等的不是自己的子进程时,这个时候就会等待失败,然后waitpid返回-1,等待成功返回子进程的pid。

参数3 option介绍

        给0选择阻塞等待,就是父进程一直等子进程跑完代码,此时父进程啥也不干。这样有点浪费时间,所以就有了非阻塞轮询,给参数三WNOHANG传,

就是如果子进程没弄好,就直接返回,然后我们外部写个循环,循环调用waitpid,这就是非阻塞轮询时可以做自己的简单代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小何只露尖尖角

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值