0


【Linux】进程控制

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️小林爱敲代码
      🛰️博客专栏:✈️Linux之路
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

文章目录

💖进程的退出场景

进程的退出的三种场景。

  • 代码运行完毕,结果正确。
  • 代码运行完毕,结果错误。
  • 代码异常终止。

想必我们在写C语言的时候,在main函数后面都会写上 return 0 ,那么为什么偏偏是return 0呢?而不是return 其他数字呢? 因为main函数的return值是进程的退出码!

而退出码分两者情况:

0 : 正常退出。

!0 : 结果不正确。

那么为什么 0是正常的,非0是不正确的? 打个比方,假如你小时候有次考试,考了100分。你回家后,你爸爸会问你为什么考了100分吗?那肯定是因为你认真努力对不对?这是正确的结果,所以你父亲不会问你为什么考100分。但如果此时你考了20分回家,你爸会问你为什么考了20分?这时候就有很多种可能性,可能是昨晚没睡好,也可能是你发烧了… 然后你父亲会根据这些可能性对症下药。所以C进程也有很多的退出码,而strerror函数可以通过退出码来获取对应的字符串。让我们遍历一下这些退出码,来看看这些退出码对应的错误可能。

#include<stdio.h>#include<string.h>intmain(){for(int i =0; i <140; i++)printf("%d:%s\n",i,strerror(i));return0;}

在这里插入图片描述

在这里插入图片描述

我们可以看到退出码的数量,有135个之多。而每个退出码都对应着一种错误。

exict

而还有一种退出方式,就是exict()强制终止。而这个函数要传的参数也是一个退出码。我们可以用 echo $?查看程序的退出码。我们写一个这样的程序。

#include<stdio.h>
#include<string.h>
   
void fun()
{
   exit(100);
}
   
int main()
{
    fun();                                                                                     return 0;                                                                             }   

我们执行这个程序,然后查看退出码。

在这里插入图片描述

所以,exit是一个强制终止的函数,传进去的参数则是这个进程的退出码

_exit

我们先来看看两段代码:

#include<stdio.h>#include<stdlib.h>intmain(){printf("hello");return0;}
#include<stdio.h>#include<stdlib.h>intmain(){printf("hello");exit(0);}

我们会发现这两段代码都会输出hello

在这里插入图片描述

当这个进程的时候,hello还在缓冲区当中,为什么可以输出hello?这是因为main函数return和exit都会在进程结束之前刷新缓冲区。所以我们可以看到hello。那么我们换成_exit试试呢?

#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){printf("hello");_exit(12);}

程序运行后,我们可以发现退出码是12,但程序并没有打印hello,这是因为**_exit终止进程不会刷新缓冲区!!**

在这里插入图片描述

exit,return和_exit的区别

return:在main函数中代表程序退出,返回值为退出码,退出前会刷新缓冲区。在非main函数中是一个函数调用。

exit:在任何地方使用都代表进程退出,并会刷新缓冲区,参数就是退出码。

_exit:在任何地方使用都代表进程退出,但不会进行程序后序的收尾工作,比如刷新缓冲区… 参数也是退出码。

进程的退出

进程退出的时候,操作系统做了什么?在操作系统层面,就是少了一个进程:free PCB , free mm_struct 地址空间,free 页表和各种映射关系以及代码和数据。

💖进程wait

我们从三个问题来剖析进程wait,进程wait是什么?为什么要有进程wait?怎么解决僵尸问题?

进程wait是什么?

我们都知道fork()可以创建一个子进程。那么创建这个子进程是为了什么?当然是为了帮助父进程完成某种任务。但有没有这样一种情况,那就是父进程比子进程先挂掉?所以为了避免这个问题,我们就需要进程wait,也就父进程等待子进程。

我们先来看下面一段代码:

#include<stdio.h>#include<stdlib.h>#include<unistd.h>intmain(){int sum =0;int pid =fork();if(pid ==0){//childfor(int i =1; i <5;i++){
            sum += i;sleep(1);}exit(0);//子进程结束退出。}printf("i am father\n");return0;}

如上这个代码,父进程比子进出先挂掉了,那么为了不让父进程挂掉,我们需要用wait来等待子进程。而wait函数在一个 sys/wait.h的头文件中。

#include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int sum =0;int pid =fork();if(pid ==0){//childfor(int i =1; i <5;i++){
            sum += i;printf("i am child,sum is %d\n",sum);sleep(1);}exit(0);//子进程结束退出。}wait(NULL);//等待子进程printf("i am father\n");return0;}

现在,父进程就会等子进程结束,才会继续执行。

在这里插入图片描述

wait() 的返回值

wait()也有返回值,如果wait等待失败,那么wait会返回-1。

为什么要让父进程等待?

1.通过获取子进程退出的信息,能够得知子进程执行结果。
2.可以保证:时序问题,子进程先退出,父进程后退出。
3.进程退出的时候会进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放子进程占用的资源。

另外,进程一旦变成僵尸状态,那么它将刀枪不入,即使是"杀人不眨眼的" kill -9命令,也无法奈何它,因为谁也没有办法杀死一个已经死去的进程。

如何让父进程进行等待?

我们先来看下面这一段代码。

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int pid =fork();if(pid ==0){int cmt =5;while(cmt--){printf("i am child\n");sleep(1);}exit(0);//子进程结束}sleep(10);printf("father wait begin\n");//父亲开始等待wait(NULL);sleep(10);return0;}

然后我们来测试一下。

刚开始的时候,我们的子进程处于S状态,也就是休眠状态。

在这里插入图片描述

当子进程结束,但父进程还在休眠时,子进程变成了僵尸状态

在这里插入图片描述

当父进程结束休眠,开始wait时。我们会发现子进程消失了。

在这里插入图片描述

所以我们可以发现:父进程等待,是可以回收掉僵尸进程的。

waitpid

waitpid介绍

  • pid_ t waitpid(pid_t pid, int *status, int options);
  • 返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
  • 参数: pid: Pid=-1,等待任一个子进程。与wait等效。 Pid>0.等待其进程ID与pid相等的子进程。
  • status:
  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:
  • WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进 程的ID。
  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退 出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

waitpid(pid_t pid, int status, int options) 的第一个参数,可以是进程的Pid,这样是指定等待某个进程。也可以是-1,*-1是等待任意控制的一个进程**。而第二个参数 *status 是一个整形的指针,我们需要传一个整形的地址给它,它代表着子进程的退出情况。所以最终一定要通过status来获取子进程的退出结果。

获取子进程的status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16比特 位):

status是一个整形,可以用来反馈程序退出的三种状,程序退出结果正确程序退出结果不正确程序异常崩溃。 而它的三种状态都在它的低16位上进行表示。而高16位我们暂且无视。0 - 7 位用来表示终止信号,8-15位用来表示退出码。当程序正常退出时,我们只看8-15位。当程序异常退出时,我们只看0-8位。

在这里插入图片描述

异常信号

那代码异常终止的本质是什么呢?是因为进程收到了某种信号。如果进程没有收到信号,那么进程是正常的跑完的,此时再回有退出码。如果进程收到信号,那么此时就不关心退出码。

在这里插入图片描述

如何获取子进程的退出码和信号呢?

还是这张图,它的前8位是退出码,后七位是收到信号。当然,如果正常退出,则收到的信号为0。如果收到信号,那么前8位就无法使用。

在这里插入图片描述

所以我们要求出退出码很简单,直接

(status>>8)& 1111 1111

。也就获取前8位,获取退出信号就是后七位,我们直接

status& 0111 1111

即可。那么我们写一段代码来测试一下。

子进程正常退出的情况

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main()
{
  int pid = fork();
  if(pid == 0)
  {
    sleep(1);
    exit(100);//退出码
  }
  
  int status = 0;
  waitpid(pid,&status,0);
  printf("child pid : %d, status code : %d, status signal:%d\n",pid,status>>8&0xFF,status&0x7F); //打印退出码及信号

  return 0;
}

进程正常结束的话,不会收到信号,所以信号值为0。

在这里插入图片描述

子进程异常终止的情况

那么我们试试异常退出的情况呢?我们在子进程退出前加上一句代码

int a = 5 /0;

,除于0,这个异常够大吧!

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int pid =fork();if(pid ==0){sleep(1);int a =5/0;//故意制造异常exit(100);//退出码}int status =0;waitpid(pid,&status,0);printf("child pid : %d, status code : %d, status signal:%d\n",pid,status>>8&0xFF,status&0x7F);//打印退出码及信号return0;}

我们执行后发现退出码变成0了,可是我们的退出码之前设置的是100。这就意味着这个程序根本就没有退出码!因为退出码是程序正常结束的时候才有。而异常终止则不会有退出码,而我们可以看到,我们的进程收到了一个为8的信号。

在这里插入图片描述

然后我们用 kill -l 来查看信号。

image-20230210211408246

当然,这样子位运算计算退出码或者状态码是不是太麻烦了呢?我们可以直接用对应的宏即可。

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

代码:

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int pid =fork();if(pid ==0){sleep(1);exit(100);}int status =0;waitpid(pid,&status,0);// printf("child pid : %d, status code : %d, status signal:%d\n",pid,status>>8&0xFF,status&0x7F);if(WIFEXITED(status)){//如果进来说明正常退出printf("exit code:%d\n",WEXITSTATUS(status));}else{printf("get a signal!");}return0;}

在这里插入图片描述

阻塞等待与非阻塞等待

我们的waitpid 还有第三个参数,而这第三个参数如果为0则是阻塞等待,如果为 WNOHANG(宏) ,那么则是非阻塞等待。

什么是阻塞等待和非阻塞等待?

举个例子,假如你有事情要请张三帮忙,你呢给他打电话。这时候张三说:我现在有事,你等我一下啊。然后就让你等他,那么你怎么等呢?如果是阻塞等待,那么就是你让他电话别挂电话,要观测他的一举一动。如果是非阻塞等待,那么就是挂掉电话,过一会再来问。还没好就再挂掉电话,过一会再过来问…

简单来说,阻塞等待就是父进程一直在那干等着,什么也不做。而非阻塞等待就是父进程隔一段时间来监测一下子进程的状态,子进程还没结束就再隔一段时间来看一下子进程。直到子进程结束。

阻塞等待的代码演示:

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int pid =fork();if(pid ==0){int cmt =5;while(cmt--){printf("i am child,time  = %d\n",cmt);sleep(1);}exit(0);}//parentint status =0;waitpid(pid,&status,0);//阻塞等待printf("father wait end\n");return0;}

非阻塞等待的代码演示:

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/wait.h>intmain(){int pid =fork();if(pid ==0){int cmt =5;while(cmt--){printf("i am child,time  = %d\n",cmt);sleep(1);}exit(0);}//parentint status =0;int ret =0;while(ret==0){
  ret =waitpid(pid,&status,WNOHANG);//返回值为0则正常退出printf("i am father\n");sleep(1);//每1秒监测一下子进程}return0;}
阻塞的本质,起始就是进程的PCB被放入了等待队列,并讲进程设置为S状态。返回的本质就是把进程的PCB从等待队列拿到运行队列。从而被CPU调度。
标签: linux c++ c语言

本文转载自: https://blog.csdn.net/Lin5200000/article/details/130630244
版权归原作者 林 子 所有, 如有侵权,请联系我们删除。

“【Linux】进程控制”的评论:

还没有评论