Linux进程退出与等待

举报
卖寂寞的小男孩 发表于 2022/10/09 08:43:40 2022/10/09
【摘要】 本文介绍Linux系统下的进程退出与等待

@[toc]

零.前言

进程终止有多种可能的原因,也有多种方式可以导致进程终止。
父进程需要等待子进程结束从而来回收子进程的资源。

1.进程退出

(1)进程退出的本质

进程退出时会销毁PCB以及进程地址空间,释放掉页表以及其中的各种映射关系,代码段与数据段所占用的空间也要被还给系统。

(2)进程退出的三种情况

进程的退出有三种情况:

1.代码运行完毕,结果正确。
2.代码运行完毕,结果不正确。
3.代码异常终止。

其中代码异常终止指的是程序崩溃。比如我们进行了除0操作等,会引发程序崩溃。

(3)退出码

退出码的概念

当一个进程退出后,就会产生一个退出码。注意,我们研究的退出码的范围是第一种和第二种情况,即程序进行正常的退出,当程序由于崩溃而退出时,退出码没有意义。

查看退出码

在命令行使用:

echo $?

来查看最近的一个进程退出后的退出码。
比如我们随意写一份C语言的代码:

#include<stdio.h>    
#include<stdlib.h>    
#include<unistd.h>    
int main()    
{    
  printf("hello world!\n");    
  return 0;    
}      

执行后查看退出码:
在这里插入图片描述
可以观察到退出码是0,此时查看的是刚刚结束的进程mytest的退出码。
如果我们在命令行继续输入echo $?,我们会发现退出码依然是0,注意这个退出码指的是上一个进程即上一个echo $?命令的退出码(每一个命令也是一个进程)。
通常而言,命令行指令的退出码都是0。

查看所有退出码

C语言提供了退出码与退出原因对应函数:sterror(i),其中i表示退出码,sterror返回的是i所对应的错误的字符串。我们可以根据这一点来打印所有退出码代表的含义:

  int i=0;    
  for(i=0;i<140;i++)    
  {    
    printf("%d:%s\n",i,strerror(i));    
  }    

我们先假定有140种退出码,这段代码打印的结果是:
在这里插入图片描述
我们发现一共有134种退出码,超过133的数字就已经未知了。
同时我们发现:
当退出码为0时,才表示程序正常运行,其他的数字都代表一个程序没有正常运行的原因。
而通过这一点我们还可以验证一下,程序崩溃时退出码为什么没有意义:

int a=10;
a/=0;

此时我们观察到退出码是136:
在这里插入图片描述
对比上表,是没有意义的。

(4)控制进程退出的方式

我们了解了进程退出的原因,那么如何控制进程退出呢?

return

在书写C语言代码的时候,我们习惯在末尾加上return 0,来表示代码执行完毕。这就是一种进程的退出方式。其中0就代表该进程退出的退出码。因此我们是可以随意地修改C语言进程的退出码的。比如我们可以return 10,那么该进程的退出码就变成了10。

int main()
{
  return 10;
}

在这里插入图片描述
但是由于退出码都有特定的含义,所以不建议随便修改。

exit()与_exit()

exit()和exit()也可以控制进程退出,其中括号中的内容为退出码。两者的区别在于:exit()退出后缓冲区会被刷新,而_exit退出后缓冲区不会进行刷新。

  printf("hello exit");    
  sleep(5);    
  exit(1);                                                                                                                                               
  printf("wrong\n");    
  printf("hello exit");    
  sleep(5);    
  _exit(1);                                                                                                                                               
  printf("wrong\n");  

其中使用exit退出可以打印出hello exit,因为刷新了缓冲区;而使用_exit退出没有打印出该字符串,因为没有刷新缓冲区。
这里将进程的退出码设为了1,可以在终端自行验证。
注意,虽然没有刷新缓冲区,操作系统也一定将缓冲区中的内容释放掉了,否则会造成内存泄漏。

2.进程等待

(1)进程等待的原因

进行进程等待的通常是父进程,父进程需要等待子进程执行结束之后再进行进程退出。

1.通过获取子进程退出的信息,来获得子进程退出的结果。
2.可以保证时序问题,子进程先退出,父进程后退出。
3.为避免子进程进入僵尸状态,父进程需要等子进程结束后回收子进程的资源。

(2)wait函数

我们在父进程中使用wait函数来进行进程的等待。下面我们将介绍两个函数来实现进程的等待。
在这里插入图片描述

返回值与参数

等待成功,wait将返回子进程的id,如果等待失败,wait会返回-1。
wait的参数status和waitpid的status表示一个意思,在下面会西索。

举例

父进程可以通过进程等待来达到回收僵尸进程的目的:

pid_t id=fork();    
if(id==0)
{
  int cnt=5;
  while(cnt)
  {
    printf("child:%d is running,cnt is %d\n",getpid(),cnt);
    cnt--;
    sleep(1);
  }
  exit(0);
}
else 
{
  sleep(10);
  printf("father wait begin\n");
  pid_t ret=wait(NULL);
  if(ret>0)
  {
    printf("father wait %d\n",ret);
  }
  else
  {
    printf("wait fail");
  }                                                                       
  printf("wait success\n");
  sleep(10);
}

这段代码的目的是:先让子进程运行5s,父进程休眠10s,这就导致了子进程在前5s是运行态,在后5s是僵尸状态(父进程在休眠,无法进行资源的回收)。当父进程进行进程等待的时候,回收了子进程的资源,此时子进程死亡。父进程在10s再退出。
当我们不进行进程等待的时候,父进程执行结束,子进程会变成孤儿进程被操作系统领养。
如果进行进程等待,子进程就不会被领养,资源被父进程回收后再死亡。
通过简单的命令行脚本我们可以进行观察。
在这里插入图片描述
wait其实有两种作用,1是等待子进程结束后回收,2是通过status获得子进程的信息。

(3)waitpid函数

返回值与参数

wait的返回值也是一个pid_t类型:

1.正常情况下,返回子进程的pid。
2.如果设置了选项WRONGHANG,而调用waitpid发现没有子进程可以被回收,则返回0。
3.如果嗲破蛹出错则返回-1。
waitpid有三个参数:
在这里插入图片描述

pid

一个进程可以有多个子进程,wait函数等待的是所有子进程执行结束,而waitpid可以通过参数pid来指定要结束的进程。
当pid为-1时,代表等待所有的进程结束,与wait相同。

  pid_t ret=waitpid(id,NULL,0);

只需要将上面进程等待的代码改成这样,由于父进程中的fork返回值是子进程的id,赋值给了变量id,所以这里表示的是等待子进程执行结束。

status

status的值

status是一个指针类型,是一个输出型参数。输出型参数的意义在于在waitpid中修改了status的值是通过指针修改的,对函数外界是有影响的。我们其实就是通过status来获得进程退出的结果的。
因此我们需要自己定义一个int类型的数据,并向status传入它的地址,然后通过该数据来获得进程退出的结果。
我们可以将子进程的退出码改为20,然后打印一下status的值来进行观察:

pid_t id=fork();    
if(id==0)    
{    
  int cnt=5;    
  while(cnt)    
  {    
    printf("child:%d is running,cnt is %d\n",getpid(),cnt);    
    cnt--;    
    sleep(1);    
  }    
  exit(10);
}
else 
{
//  sleep(10);
  int status=0;
  printf("father wait begin\n");
  pid_t ret=waitpid(id,&status,0);
  if(ret>0)
  {
    printf("father wait %d\n",ret);
    printf("status is %d\n",status);
  }
  else
  {                                                                                                                                                      
    printf("wait fail");
  }
  printf("wait success\n");
//  sleep(10);
}

在这里插入图片描述
此时我们发现status的值是2560,那么这个数字代表什么呢?
首先我们要明确,status是反映子进程退出时的状态的,而进程退出有三种情况:正常退出,结果正确;正常退出,结果错误;程序崩溃退出。所以status其实是反映这三种情况的。

status的构成

status指向的内容有32个比特位,但是只有低的16位有意义,高的16位没有意义。
程序崩溃即代码异常终止,本质上是收到了一个终止的信号,waitpid会首先判断是否是接收到终止信号导致进程结束,如果是则直接对status指向的内容进行修改。
在这里插入图片描述
其中终止信号占7位,coredump占一位。
如果不是程序异常终止,则status进行如下赋值:
在这里插入图片描述
因此我们就可以知道2560的由来了,退出码是10,status就是0000 1010 0000 0000的值刚好是2560。

通过status获得退出码

知道了status的构成,我们就可以通过移位操作,通过status来获得程序的退出码和退出信号。

(status>>8)&0xFF//获得退出码
status&(0x7F)//获得退出信号

了解了这一原理,我们还会得出这样的结论:bash就是通过wait方法获得的各个退出码,因此我们可以才能通过echo $?来获得各个进程的退出码。
我们发现如果要获得退出码还需要进行移位操作,非常的不方便,因此大佬们又引入了两个宏,来帮助我们获取退出码:

WIFEXITED(status):若为正常退出,则返回非0。
WEXITSTATUS(status):如果WIFEXITED为非0,则提取子进程的退出码。

  if(WIFEXITED(status))    
  {    
    printf("father wait %d\n",ret);    
    printf("statusexit is %d\n",WEXITSTATUS(status));                                                                                                    
  }    

此时可以打印到退出码10:
在这里插入图片描述

options

waitpid的第三个选项options有两种取值:一种是0,一种是WNOHANG。
其中0表示的是阻塞等待,而WNOHANG表示的是非阻塞等待。
阻塞等待指的是父进程在等待子进程结束的期间内,不进行任何操作。而非阻塞等待指的是父进程在等待子进程结束的期间内不断调用waitpid的过程。(注意返回值条件,当设置WNOHANG且子进程没有结束时,返回值为0,否则返回子进程的pid)。
当取值为0,即为阻塞等待时,本质上其实就是将父进程由运行队列转移到等待队列。等子进程执行结束之后再将父进程由等待队列转移到运行队列。之前的例子都是阻塞赋值的例子。
我们也可以验证一下非阻塞赋值:

 while(1)
 {
   pid_t ret=waitpid(id,&status,WNOHANG);
   if(ret==0)
   {
     printf("do my things\n");
   }
   else if(ret>0)
   {
     printf("wait success\n");
     break;
   }
   else 
   {
     printf("wait failed\n");
   }
   sleep(1);    
}   

我们可以通过循环来使用非阻塞等待,帮助父进程完成其他内容。

3.总结

1.进程结束有三种原因。使进程结束有两种主要的方法。
2.进程等待有两个主要的函数,并且等待分为阻塞等待和非阻塞等待。
3…三连叭球球了。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。