Linux系统进程退出与等待

目录

1.进程退出

(1)进程退出的本质

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

(3)退出码

退出码的概念

查看退出码

查看所有退出码

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

return

exit()与_exit()

2.进程等待

(1)进程等待的原因

(2)wait函数

返回值与参数

举例

(3)waitpid函数

pid

status

status的值

status的构成

通过status获得退出码

options


1.进程退出

(1)进程退出的本质

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

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

进程的退出有三种情况:

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

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

(3)退出码

退出码的概念

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

查看退出码

在命令行使用:

echo $?

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

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

执行后查看退出码:

可以观察到退出码是0,此时查看的是刚刚结束的进程test的退出码。
如果我们在命令行继续输入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()
{
    for(int i=0;i<140;i++)
    {
        printf("%d:%s\n",i,strerror(i));
    }
    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表示一个意思。

举例

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

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

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

wait其实有两种作用,1是等待子进程结束后回收,2是通过status获得子进程的信息。

(3)waitpid函数

返回值与参数

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

1.正常情况下,返回子进程的pid

2.如果设置了选项WRONGHANG,而调用waitpid发现没有子进程可以被回收,则返回0.

pid

一个进程可以有多个子进程,wait函数等待的是所有子进程执行结束,而waitpid可以通过参数pid来指定要结束的进程。

当pid为-1时,代表等待所有的进程结束,与wait相同。

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

只需要将上面进程等待的代码改成这样,这里的id是子进程id。

status
status的值

status是一个指针类型,是一个输出型参数。输出型参数的意义在于在waitpid中修改了status的值是通过指针修改的,对函数外界是有影响的。我们其实就是通过status来获得进程退出的结果的。

因此我们需要自己定义一个int类型的数据,并向status传入它的地址,然后通过该数据来获得进程退出的结果。
我们可以将子进程的退出码改为10,然后打印一下status的值来进行观察:

pid_t id=fork();
    if(id==0)
    {
        int cnt=5;
        while(cnt)
        {
            printf("child:%d id running,cnt is %d\n",getpid(),cnt);
            cnt--;
            sleep(1);
        }
        exit(10);
    }
    else
    {
        sleep(10);
        int status=0;
        printf("father begin wait\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\n");
        }
        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);    
}   

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值