小工具知识网

当前位置: 首页 > 行业知识 > 【Linux】进程控制

行业知识

【Linux】进程控制

2026-10-31 07:54:48 来源:互联网转载

        ���迎来到小林的博客!!

      ️博客主页:✈️小林爱敲代码

      ️博客专栏:✈️Linux之路

      ️社区 :✈️ 进步学堂

      ️欢迎关注:点赞收藏✍️留言

文章目录

  • 进程的退出场景
    • 进程的退出的三种场景。
    • exict
    • _exit
    • 进程的退出
    • 进程wait
      • 进程wait是什么?
      • 为什么要让父进程等待?
      • 如何让父进程进行等待?
      • waitpid
        • waitpid介绍
        • 获取子进程的status
        • 异常信号
        • 阻塞等待与非阻塞等待

          进程的退出场景

          进程的退出的三种场景。

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

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

            而退出码分两者情况:

            0 : 正常退出。

            !0 : 结果不正确。

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

            #include#includeint main(){    for(int i = 0 ; i    

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

            exict

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

            #include#include   void fun(){   exit(100);}   int main(){    fun();                                  return 0;                              }   

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

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

            _exit

            我们先来看看两段代码:

            #include#includeint main(){printf("hello");return 0;}
            #include#includeint main(){printf("hello");exit(0);}

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

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

            #include#include#includeint main(){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#include#includeint main(){    int sum = 0;    int pid = fork();    if(pid == 0)    {        //child        for(int i = 1 ; i  

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

            #include#include#include#includeint main(){    int sum = 0;    int pid = fork();    if(pid == 0)    {        //child        for(int i = 1 ; i  

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

            wait() 的返回值

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

            为什么要让父进程等待?

            1.通过获取子进程退出的信息,能够得知子进程执行结果。

            2.可以保证:时序问题,子进程先退出,父进程后退出。

            3.进程退出的时候会进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,释放子进程占用的资源。

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

            如何让父进程进行等待?

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

            #include#include#include#include#includeint main(){  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);  return 0;}

            然后我们来测试一下。

            刚开始的时候,我们的子进程处于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#include#include#include#includeint 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#include#include#include#includeint main(){  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); //打印退出码及信号  return 0;}

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

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

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

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

                  代码:

                  #include#include#include#include#includeint 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);  if(WIFEXITED(status))  {    //如果进来说明正常退出    printf("exit code:%d\n",WEXITSTATUS(status));  }   else   {       printf("get a signal!");   }  return 0;}

                  阻塞等待与非阻塞等待

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

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

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

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

                  阻塞等待的代码演示:

                  #include#include#include#include#includeint main(){  int pid = fork();  if(pid == 0)  {    int cmt = 5;    while(cmt--)    {      printf("i am child,time  = %d\n",cmt);      sleep(1);    }    exit(0);  }  //parent  int status = 0;  waitpid(pid,&status,0);//阻塞等待  printf("father wait end\n");  return 0;}

                  非阻塞等待的代码演示:

                  #include#include#include#include#includeint main(){  int pid = fork();  if(pid == 0)  {    int cmt = 5;    while(cmt--)    {      printf("i am child,time  = %d\n",cmt);      sleep(1);    }    exit(0);  }  //parent  int status = 0;  int ret = 0;  while(ret==0)  {  ret =  waitpid(pid,&status,WNOHANG); //返回值为0则正常退出  printf("i am father\n");  sleep(1); //每1秒监测一下子进程  }  return 0;}

                  阻塞的本质,起始就是进程的PCB被放入了等待队列,并讲进程设置为S状态。返回的本质就是把进程的PCB从等待队列拿到运行队列。从而被CPU调度。

上一篇:大溪河服务器怎么样?

下一篇:织梦CMS单页制作秘籍,轻松创建吸引人的网页标题!