如何理解:进程控制

news2024/11/14 16:37:30

文章目录

  • 前言:
  • 进程创建:
  • 进程终止:
  • 如何终止进程?
  • 进程等待
    • 非阻塞等待:
  • 总结:

前言:

​ 对于前面的地址空间的学习,我们现在了解到原来所谓变量的地址其实是虚拟地址,该虚拟地址是通过页表的映射关系从而找到内存中真实的物理地址!下面我们进入到关于进程的控制

进程创建:

​ 现在我们对进程就有了新的定义:进程 = 内核的相关管理数据数据结构(task_struct + mm_struct + 页表) + 代码和数据。 其中代码是父子进程共享的,数据是判断是否发生写实拷贝的。

  • fork函数的返回值

    我们之前也有过介绍,fork函数是用来创建子进程的,创建子进程成功则返回0,对于父进程的返回值则是子进程的pid,这一点虽然我上次没有细讲,但是在截图时就会发现。可以看看我之前博客——>进程理解

    • 子进程返回0
    • 父进程返回子进程的pid
  • 为什么父进程返回的是子进程的pid,给子进程返回的却是0呢?

    要记住,我们在讲解进程状态的时候,对于僵尸进程我们有过介绍,父进程是会管理子进程的,谈到管理永远是6个字——>”先描述,再组织“

    所以当然是为了让父进程方便对子进程进行标识,进而进行管理!

  • fork函数的常规用法

    1. 一个父进程希望复制自己,使得父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
    2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
  • fork调用失败的原因

    1. 系统中有太多的进程。
    2. 实际用户创建数超过限制。

进程终止:

  • 进程终止是在做什么呢?

    首先我们应该清楚一件事情——>当加载进程是,应当是先创建PCB、页表和地址空间等,再是加载代码和数据

    所以进程终止是在:

    1. 释放曾经的代码和数据所占据的空间。
    2. 释放内核数据结构(若task_struct受僵尸状态,则演示释放)
  • 进程终止的3种情况

    • 提出问题:为什么main函数最后要返回0呢?为什么不是1或者100呢?
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    
    int main()
    {
    	printf("I am a process! pid: %d, ppid: %d\n", getpid(), getppid());
    	sleep(1);
    	return 100;
    }
    

    [!TIP]

    我们可以使用指令:echo $?

    该指令代表父进程bash获取到子进程的退出码,0表示成功,非0表示失败。退出码的意义就是告诉关心方(父进程),我把任务处理的怎么了。

    image-20240814221753931

    但我要是再次执行echo $?指令,表示的就是刚刚子进程的退出码,毕竟该指令是获取最近进程的退出码。

    image-20240814221932725

    那这个退出码究竟有什么用,你想返回100或者0不是都可以吗。诶,这个时候操作系统会给我们提供个新的系统调用函数:strerror函数用来获取系统错误信息或打印用户程序错误信息,下面我们来用用这个系统调用

    [!TIP]

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <string.h>
    
    int main()
    {
     int errorcode = 0;
     for(errorcode = 0; errorcode <= 255; ++errorcode)
     {
         printf("%d -> %s\n", errorcode, strerror(errorcode));
     }
     return 100;
    }
    

    image-20240814223501038

    image-20240814224450118

    我们会打印特别多的错误码,每个错误码都对应了一个描述。所以为什么我们最后都会返回0,就是因为如果全部代码都执行完毕后,那肯定是不会存在问题的,那么就返回0。如果其中有一处有错误,系统就会直接进行返回相应的错误码。

    image-20240814224639250

    这里是我随便ls一个文件夹,因为在当前目录未找到该文件夹,所以执行该指令的进程就会向bash返回相对应的错误码2。

    所以我们在输入指令的时候,本质也是OS创建子进程然后子进程在执行。这点我们通过指令echo $?可以很好的证明。

    这也能很好地说明bash为什么要得到子进程的退出码,为了知道子进程退出的情况(是否成功,失败又是什么原因),当然bash只是提供信息,不会自动解决,这只是一种为用户负责的体现

    • 我们也可以实现自定义退出码

      #include <stdio.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <string.h>
      
      enum
      {
          ERROR_DIV = -1,
          NOMARL_DIV
      };
      
      int exit_code = NOMARL_DIV;
      
      int Div(int a, int b)
      {
          if(b == 0)
          {
              exit_code = ERROR_DIV;
              //printf("This is error!\n");
              return exit_code;
          }
          exit_code = NOMARL_DIV;
          return a/b;
      }
      
      const char* ErrorMode(int mode)
      {
          switch(mode)
          {
              case ERROR_DIV:
                  return "Div zero";
              case NOMARL_DIV:
                  return "Div nomarl";
              default:
                  return "Unknow error!";
          }
      }
      
      int main()
      {
      	int ans = Div(4, 0);
      	printf("ans is-> %d[%s]\n", ans, ErrorMode(exit_code));
      	ans = Div(4, 2);
      	printf("ans is-> %d[%s]\n", ans, ErrorMode(exit_code));
          return 0;
      }
      

      通过判断分母是否为0,从而实现判断结果是否是正确的!

      • 退出信号

        int main()
        {
            int* p = NULL;
            *p = 3;
            printf("%d\n", *p);
            return 0;
        }
        

        对于上述代码,我们执行起来百分之百会出现报错,如果我们在VS上也写过这样的bug,那么该错误就是一个很典型的——段错误
        image-20240815120245588

        这个时候代码不会跑完,因为在执行的过程中出现了异常,就提前退出了。就像在VS写代码时,代码崩溃了,此时OS发现你的进程做了不该做的事情,OS就会杀掉进程,一旦出现了异常,退出码就没意义了。

        比如此时我使用指令echo $? 打印退出码,是会发现退出码为139,但是退出码在133后就是未知了:
        image-20240815121710938

        • 为什么会出现异常呢?

          本质上是因为进程收到了OS发给进程的信号

          我们可以使用指令:kill -l
          image-20240815122015374

          段错误的出现就是对应的11号信号

          所以衡量一个进程的退出,我们(父进程bash)只需要两个数字:退出码 + 退出信号

          第一步是先确认是否异常
          若不是异常就一定是代码跑完了,看退出码就好了。

[!IMPORTANT]

所以进程终止的3种情况:

  1. 代码跑完,结果正确
  2. 代码跑完,结果不正确
  3. 代码执行时,出现了异常,提前退出了

如何终止进程?

  1. main函数的return,表示进程终止(非main函数的return,代表函数结束)
  2. 代码调用exit函数(在代码任意位置调用exit函数都表示进程退出)
  3. 通过系统调用函数_exit( )
  • exit( ) VS _exit( )

    1. exit( )是库函数,_exit( )是系统调用

    2. exit函数会在进程退出的时候,重置缓冲区,而 _exit( )不会,因此我们可以猜测缓冲区是在exit那一层,也可以说明使用exit( )库函数的本质是在调用系统调用函数 _exit( ).

      #include <stdio.h>
      #include <stdlib.h>
      
      int main()
      { 
          printf("Hello, this is a test code!");
          exit(0);
      }
      
      

      image-20240815123149853

      #include <stdio.h>
      #include <stdlib.h>
      
      int main()
      { 
          printf("Hello, this is a test code!");
          _exit(0);
      }
      
      

      image-20240815123237707

进程等待

​ 任何进程,在退出的情况系,一般必须要被父进程进行等待!

  • 为什么父进程要等待?

    1. 进程在退出的时候,如果父进程不管不顾,退出进程会出现僵尸状态从而到时内存泄漏。所以父进程通过等待,解决子进程出现的僵尸问题,为了回收系统资源。(这是一定要考虑的)
    2. 获取子进程的退出信息,知道子进程是因为什么退出的。(可选的功能)
  • 该怎么进行等待?

    我们可以使用系统调用函数:wait( ) 和 waitpid( ) 函数

    #include <sys/types.h>
    #include <sys/wait.h>

    pid_t wait(int* status);
    pid_t waitpid(pid_t pid, int* status, int options);

    • wait( )

      • 父进程阻塞等待任意一个子进程,子进程不退则父进程不退。
      • 该函数能够回收子进程资源,以及获取子进程的 pid。

      返回值:成功返回被等待进程的pid,失败返回-1.
      参数:输出型参数,获取子进程退出状态,不关心则可以设置成NULL

      #include <stdio.h>
      #include <unistd.h>
      #include <sys/types.h>
      #include <stdlib.h>
      #include <sys/wait.h>
      
      void ChildRun()
      {
          int cnt = 1;
          while(cnt <= 5)
          {
              printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());
              sleep(1);
              cnt++;
          }
      }
      
      int main()
      {
          printf("I am process!\n");
      
          pid_t id = fork();
          if(id == 0)
          {
              // child
              ChildRun();
              printf("Child quit...\n");
              exit(123);
          }
          // father
          pid_t rid = wait(NULL);
          if(rid > 0)
          {
              printf("wait success, rid: %d\n", rid);
          }
          else
          {
              printf("wait failed!\n");
          }
          sleep(2);
          return 0;
      }
      

      image-20240815134408685

      最后是等待成功并且返回了子进程的pid

    • waitpid( )

      回收子进程资源,解决僵尸问题的同时,还能够获取子进程退出信息。

      返回值:

      • pid_t > 0:等待成功,子进程退出,并且父进程回收成功
      • pid_t < 0:等待失败
      • pid_t == 0:检测是成功的,只不过子进程还没退出,需要你下一次重复等待。

      参数:

      1. pid_t pid

        • pid == -1,等待任意一个子进程,与wait一样。
        • pid > 0,等待其进程的ID与pid相等的子进程。
      2. int* status

        • 输出型参数,用来保存退出信息,保存退出码 + 退出信号,让我们知道“退出”的情况如何
      3. int options

        • 指定父进程的等待方式,为 0 则让父进程进行 阻塞 等待,非 0 则进行 非阻塞 等待。

          #include <stdio.h>
          #include <unistd.h>
          #include <sys/types.h>
          #include <stdlib.h>
          #include <sys/wait.h>
          void ChildRun()
          {
              int cnt = 1;
              while(cnt <= 5)
              {
                  printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());
                  sleep(1);
                  cnt++;
              }
          }
          
          int main()
          {
              printf("I am process!\n");
          
              pid_t id = fork();
              if(id == 0)
              {
                  // child
                  ChildRun();
                  printf("Child quit...\n");
                  exit(123);
              }
              // father
              int status = 0;
              pid_t rid = waitpid(-1, &status, 0);
              if(rid > 0)
              {
                  printf("wait success, rid: %d\n", rid);
              }
              else
              {
                  printf("wait failed!\n");
              }
              sleep(2);
              printf("father process quit, status: %d\n", status);
              return 0;
          }
          

          image-20240815142915066

          最后的运行结果status是31488,这么奇怪的数字。

          • 分析status

            我们介绍过,status是输出型参数,记录了该进程的退出码 + 退出信号,但是我们应该如何利用一个值记录退出码和退出信号呢?

            [!NOTE]

            我们都知道,一个int* 变量的大小是4字节,那么也就是32个比特位,但status不能简单的当做int 来看待,可以当做位图来看,我们只研究低16比特位*

            分为两种情况:

            1. 正常终止时:高8位为退出码信息,低8位默认全部为0
            2. 被信号杀掉时(出现异常):高8位不再使用,低7位存储终止信号,中间还有一位存core dump标志位

            image-20240815144220123

            红色各段就代表着 退出码 + 退出信号,退出码拥有8个比特位,而终止信号拥有7个比特位

            所以我们上述的31488其实是两个数据的整合,但是我们也可以打印出来看看。

            我们需要将退出码先挪动到低8位,再转换为10进制,所以我们可以用位运算操作符:
            打印退出码:(status >> 8) & 0xFF
            打印退出信号:status & 0x7F
            image-20240815145027841

            printf("exit_code: %d, exit_signal: %d\n", (status >> 8)&0xFF, (status & 0x7F));
            

            最后的输出结果:
            image-20240815145620114

            因为我的子进程最后exit了123,又因为这是正常终止,所以返回退出码接收到了123.
            但如果我加一个段错误,再运行就会如下图所示:

            image-20240815150154379

            image-20240815145913895

            此时是异常退出,status就会收到退出信号非退出码

            我们也可以利用两个宏,来查出退出码:

            1. WIFEXITED(status):若位正常终止子进程返回的状态,则为真。(主要是查看子进程是否正常退出,本质上是查看signal位是否满足。
            2. WEXITSTATUS(status):若WIFEXITED非空,提取子进程的退出码。
            if(WIFEXITED(status))
            {
            	printf("child process quit success, chid exit code: %d\n", WEXITSTATUS(status));
            }
            else
            {
            	printf("child process quit unnormal!\n");                                     
            }
            

非阻塞等待:

​ 我们上面讲的等待过程是属于阻塞等待,还记得我们在进程状态部分讲解过阻塞态吗,还记得阻塞态是会进入等待队列的吗?如果你有疑问的话可以去看看我之前写的博客:进程的祖册、挂起和运行状态。
​ 那我们刚刚介绍waitpid( )系统调用时,也介绍了参数option是控制关于非阻塞等待的,如果参数option为0,那就是阻塞等待,父进程会等待子进程结束,再进行父进程的操作,这是阻塞等待。
非阻塞等待是父进程在等待子进程的过程中,父进程同时也在做某些操作,这就是非阻塞等待。

[!NOTE]

我们可以使用宏:WNOHANG 来表示父进程进入非阻塞等待。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/wait.h>

void ChildRun()
{
 int cnt = 1;
 while(cnt <= 5)
 {
     printf("%d-> I am child process, pid: %d, ppid: %d\n", cnt, getpid(), getppid());
     sleep(1);
     cnt++;
 }
}

void DoOtherThing()
{
 printf("   I am doing my father things while child process is running!\n");
}

int main()
{
 printf("I am a process, pid: %d, ppid: %d\n", getpid(), getppid());
 pid_t id = fork();
 if(id == 0)
 {
     //child
     ChildRun();
     printf("child process quit...\n");
     sleep(1);
     exit(0);
 }
 //father
 int status = 0;
 while(1)
 {
     pid_t rid = waitpid(0, &status, WNOHANG);
     if(rid == 0)
     {
         printf("   Just checking child process...\n");
         DoOtherThing();
         sleep(1);
     }
     else if(rid > 0)
     {
         printf("wait suceess!\n");
         break;
     }
     else
     {
         printf("wait failed!\n");
         break;
     }
 }
 if(WIFEXITED(status))
 {
     sleep(1);
     printf("father process quit success!\n");
     printf("exit_code: %d, exit_signal: %d\n", WEXITSTATUS(status), status&0x7F);
 }
 else
 {
     printf("quit unnormal!\n");
 }
 return 0;
}

通过以上代码,我们就能实现子进程运行的同时,父进程也在运行自己的任务:
image-20240815174618876

总结:

​ 现在我们已经学会了有关进程创建的话题,接下来我们将要讨论进程替换的话题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2042308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python管理mysql(实现读写分离)及如何用Mycat读写分离

Day24 编写python代码实现读写分离 1、安装pymysql&#xff0c;它是python管理mysql的驱动&#xff0c;或者称为连接器 [rootpyhton ~]#pip3 config set global.index-url Simple Index [rootpython ~]# pip3 install pymysql #安装pymysql [rootpyhton ~]# python3 …

【C++】什么是内存管理?

如果有不懂的地方&#xff0c;可以看我以往文章哦&#xff01; 个人主页&#xff1a;CSDN_小八哥向前冲 所属专栏&#xff1a;C入门 目录 C/C内存分布 C内存管理方式 new/delete操作内置类型 new/delete操作自定义类型 operator new与operator delete函数 new和delete实现…

【秋招笔试】24-08-10-OPPO-秋招笔试题(第二套)

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

2024巴黎奥运会VIS视觉设计分享学习

2024年巴黎奥运会的视觉识别系统&#xff08;VIS&#xff09;以其独特的设计和创意&#xff0c;展现了法国的优雅与活力。该设计融合了现代感与传统元素&#xff0c;以巴黎标志性的建筑和文化为灵感&#xff0c;创造出一系列令人印象深刻的视觉符号。 色彩上&#xff0c;采用了…

SpringCloud网关

1.网关的作用 2.网关入门 2.1引入依赖 <dependencies><dependency><groupId>com.heima</groupId><artifactId>hm-common</artifactId><version>1.0.0</version></dependency><!--网关--><dependency><g…

【qt】基于tcp的客户端搭建

ui界面设计 我们给客户端提供服务器的ip地址&#xff0c;以及服务器的端口号 1.界面设计 2.修改对象名称 代码实现 断开按键的槽函数处理 转到槽&#xff0c;然后实现槽函数&#xff0c;直接关闭该窗口&#xff0c;就可以了 连接槽函数编写&#xff0c;首先要支持网络通信…

C# 解析html

C#解析html库 网上查找有如下几个库 SGMLReader (好久不更新了)html-agility-pack(活跃)AngleSharp(活跃)CsQuery(好久不更新) 选择AngleSharp 原因:能向写js一样获取元素 安装AngleSharp 1.2.0-beta.431 using AngleSharp.Html.Parser;namespace HtmlParse01;class Progr…

示波法电子血压原理

血压是指血液在血管内流动时作用于单位面积血管壁的侧压力&#xff0c;它是推动血液在血管内流动的动力。在不同血管内被分别称为动脉血压、毛细血管压和静脉血压&#xff0c;通常所说的血压是指体循环的动脉血压。 血压正常范围值&#xff1a;收缩压130mmHg&#xff0c;舒张压…

【Python学习-UI界面】PyQt5 小部件9-QMenuBar,QMenuQAction 菜单栏

样式如下: 在pyqt5中直接添加就行&#xff0c;子项只能输入英文&#xff0c;输入中文的方式待研究 一个水平的 QMenuBar 位于QMainWindow对象的标题栏下方&#xff0c;用于显示QMenu对象。 QMenu 类提供了一个可以添加到菜单栏的小部件。它还用于创建上下文菜单和弹出菜单。…

基于YOLOv10深度学习的草莓成熟度检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、人工智能

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

SpringCloud完整教程

一下内容为本人在听黑马程序员的课程时整理的 微服务技术栈 ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ ⎛⎝≥⏝⏝≤⎛⎝ 1、微服务框架 1.1、认识微服务 1.1.1、服务架构演变 **单体架构&#xff1a;**将业务的所有功能集中在一个项目中开发&#xff0c;打包成…

TypeScript 编译选项

编译TS 编译 .ts 文件&#xff1a; tsc app.ts执行命令tsc app.ts 可以把 单个文件 app.ts编译成 app.js。 这个命令需要每次编译时手动执行。 自动编译文件 tsc app.ts -w // 或者 tsc app.ts --watch通常 -w 或 --watch 指令用于启动 TypeScript 编译器的监视模式。 编译…

梅丽尔·斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越”

梅丽尔斯特里普表演艺术家中心对外开放并恢复线下活动 体现了她的“卓越” 2024-08-14 20:38 发布于&#xff1a;河北省 该中心将为美国演员工会和美国电视广播艺人协会的艺术家提供资源和机会&#xff0c;而且全部免费 同时命名的还有汤姆汉克斯和丽塔威尔逊放映室、妮可…

工业WiFi网关在工业领域的具体应用-天拓四方

工业WIFI网关在工业领域的应用已经变得极为广泛&#xff0c;它不仅是连接工业设备、传感器与云平台之间的桥梁&#xff0c;更是推动工业智能化和自动化的重要动力。下面将进一步详细介绍工业WiFi网关在工业领域的一些具体应用。 工业自动化生产&#xff1a; 在工业自动化生产…

算法力扣刷题记录 八十一【343. 整数拆分】

前言 动态规划第8篇。记录 八十一【343. 整数拆分】 一、题目阅读 给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。返回 最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 …

货代企业转型海外仓,会面临哪些难点?

根据数据统计&#xff0c;跨境电商出口B2C物流市场规模已经达到3825亿元&#xff0c;其中海外仓物流以2102亿的规模以及55%的市场份额成为主流。从细分领域看&#xff0c;其中FBA物流、第三方仓、自建仓的市场规模分别达到1156亿、736亿、210亿。 在跨境物流行业中&#xff0c…

uniapp粘贴板地址识别 address-parse插件的使用

1&#xff1a; 插件安装 主要是依靠 address-parse 这个插件&#xff1a; 官网 收货地址自动识别 支持pc、h5、微信小程序 - DCloud 插件市场 // 首先需要引入插件 npm install address-parse --save 2&#xff1a;html部分 <view class""><view class&quo…

QT、C++简单界面设计

#include "mywidget.h"MyWidget::MyWidget(QWidget *parent): QWidget(parent) {---------------------窗口设置----------------------this->setWindowTitle("南城贤子摄影工作室");//设置窗口标题this->setWindowIcon(QIcon("d:\\Pictures\\C…

并发编程 | 线程池的手动创建

手动创建线程的弊端 在前面我们讲了手动创建线程的使用方式&#xff1a;当一个任务过来&#xff0c;创建一个线程&#xff0c;线程执行完了任务马上又销毁&#xff0c;然后下一次任务过来又重新创建线程&#xff0c;执行完任务再销毁&#xff0c;周而复始。 那么会导致这样一…

【数据结构】-----红黑树

目录 前言 一、what is it&#xff1f; 二、How achieve it&#xff1f; Ⅰ、结点类 Ⅱ、实现 插入 情况一&#xff1a;叔叔存在且为红色 情况二&#xff1a;叔叔不存在或者叔叔为黑色 旋转 验证 ①验证中序遍历 ②验证是否满足红黑树的性质 Ⅲ、完整实现代码 三、A…