进程控制(Linux)

news2024/11/24 13:46:11

进程控制

fork

在Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值:
在子进程中返回0,父进程中返回子进程的PID,子进程创建失败返回-1。

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程。
  • 将父进程部分数据结构内容拷贝至子进程。
  • 添加子进程到系统进程列表当中。
  • fork返回,开始调度器调度。
    在这里插入图片描述

进程终止

进程退出场景

在我们写的程序中,代码运行会有三种情况:

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

main函数中的return的返回值为进程的退出码!

在这里插入图片描述

进过测试总结——return返回的值为main函数的退出码!

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gk5jqfJM-1685794022709)(G:\Mynote\image\image-20230526182653011.png)]
return的返回值含义(还有很多但想表达的是只有0为成功其他为错误原因):

在这里插入图片描述

代码异常终止

通俗的讲:代码跑了一半报错终止运行了——程崩溃!
崩溃后的程序它的返回值是没有意义的!例子:

在这里插入图片描述

进程退出的方式

  • main函数return,代表进程退出
  • 非main函数返回——函数返回
  • exit在任地方调用都是代表终止进程,参数都是退出码!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REiidq7G-1685794022710)(G:\Mynote\image\image-20230526193200792.png)]

  • exit与return是一样的可以退出进程
    在这里插入图片描述在这里插入图片描述

  • _exit终止进程,强制终止进程,不要进行进程的收尾工作,比如不是刷新缓冲区!exit()会刷新缓冲区(用户缓冲区)
    在这里插入图片描述

进程退出OS层面做了什么?

  • 系统层面上,少了一个进程:free PCB,free mm_struct,free页表和各种映射关系,代码+数据申请的空间也要给free了
    进程异常退出

情况一:向进程发生信号导致进程异常退出。

例如,在进程运行过程中向进程发生kill -9信号使得进程异常退出,或是使用Ctrl+C使得进程异常退出等。

情况二:代码错误导致进程运行时异常退出。

例如,代码当中存在野指针问题使得进程运行时异常退出,或是出现除0的情况使得进程运行时异常退出等。

进程等待

进程wait是什么?

fork() ;
子进程:帮助父进程完成某些任务
父进程:而父进程要想知道知道子进程帮我完成了什么任务就要用wait/waitpid等待子进程退出

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

注意:一但进程变成僵尸进程那他就流弊了无敌了kill-9也杀不死他,应为没有办法杀一个死去的进程!

如何解决僵尸进程?

使用wait来回收子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);//子进程在这里结束
  }

  sleep(10);
  printf("fahter wait begin\n");
  pid_t ret = wait(NULL);
  if(ret > 0)
  {
  
  printf("fahter wait :%d, success\n", ret);
  }
  else{
    printf("father wait failed!\n");
  }
  sleep(10);
  return 0;
}

效果如下:

在这里插入图片描述
总结:wait可以回收僵尸进程

在这里插入图片描述

status可以帮助我们收到子进程结束的三种反馈情况!

我们程序代码跑完结果对or结果不对是靠进程退出码来判定的,但是如何证明我们的代码是跑完了的呢,而不是“先帝创业未半而中道崩殂”的呢?——如果一个程序应为代码异常而终止的问题导致程序收到的其他信号。所以我们可以判断一个进程是否有收到信号来判断进程是否是异常终止的!下面的表应该怎么看呢?
在这里插入图片描述
我们通过一系列位操作,就可以根据status得到进程的退出码和退出信号。
解释:次底8位为退出状态——退出码,而底7位为终止信号!所以我们以后只需要判断退出码它的底7位为0它就没有收到信号

exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F; //退出信号

系统当中提供了两个宏来获取退出码和退出信号。

  1. WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  2. WEXITSTATUS(status):用于获取进程的退出码。

exitNormal = WIFEXITED(status); //是否正常退出
exitCode = WEXITSTATUS(status); //获取退出码

需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了。
进程等待的方法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  sleep(10);
  printf("fahter wait begin\n");
 // pid_t ret = wait(NULL);
 // pid_t ret = waitpid(id,NULL,0);等待指定一个进程
 // pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程
  int status = 0;
  pid_t ret = waitpid(-1,&status,0);
  if(ret > 0)
  { 
  printf("fahter wait :%d, success, status exit code:%d ,status exit signal%d\n", ret,
  (status>>8)&0xFF, status & 0x7F);//看信号位与退出码
  }
  else{
    printf("father wait failed!\n");
  }
  sleep(10);
  return 0;

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  printf("i am a child process!,pid :%d ,ppid: %d \n",getpid(),getppid());
  exit(10);
}

结果:

i am a child process!,pid :20681 ,ppid: 19703

在这里插入图片描述

我们发现进程/a.out 的父进程居然是-bash——bash是所有启动进程的父进程!bash是如何得到进程的的退出结果的呢?一定是通过wait来获得进程的退出结果的! 所以我们可以使用echo可以查看子进程的退出码!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 5;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  sleep(5);
  printf("fahter wait begin\n");
 // pid_t ret = wait(NULL);
 // pid_t ret = waitpid(id,NULL,0);等待指定一个进程
 // pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程
  int status = 0;
  pid_t ret = waitpid(id,&status,0);
  if(ret>0)
  {
    if(WIFEXITED(status))//没有收到任何的退出信号的
    {
    //正常结束的,获取对应的退出码!
      printf("exit code: %d\n",WEXITSTATUS(status));
    }
    else{
      printf("error, get s signal!\n");
    }
  }
}

waitpit

作用:waitpid会暂时停止进程的执行,直到有信号来到或子进程结束。
函数说明:

如果在调用 waitpid()时子进程已经结束,则 waitpid()会立即返回子进程结束状态值。
子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回。
如果不在意结束状态值,则参数 status 可以设成 NULL。

参数:
参数 pid 为欲等待的子进程识别码,其他数值意义如下:

  1. pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
  2. pid=-1 等待任何子进程,相当于 wait()。
  3. pid=0 等待进程组识别码与进程相同的任何子进程。
  4. pid>0 等待任何子进程识别码为 pid 的子进程。

参数 status:输出型参数,获取子进程的退出状态,不关心可设置为NULL。
参数 options:当设置为WNOHANG时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待。若正常结束,则返回该子进程的pid。

理解waitpit

在这里插入图片描述

阻塞

阻塞与非阻塞

*阻塞通俗的讲就是你和你女朋友出去玩但是她要化妆你要等她一直等

非阻塞为你和你女朋友出去玩但是她要化妆你要等她,但过五分钟打电话问一下直到她下来。——这种方式为基于非阻塞的轮询方案!*

注:阻塞与非阻塞都是等待的一种方法

阻塞的本质:其实是进程的PCB被放入等待队列,将进程的状态改为S状态
返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度。

非阻塞等待

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
  pid_t id = fork();
  if(id == 0)
  {
    //child
    int cnt = 3;
    while(cnt)
    {
      printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);
      cnt--;
      sleep(1);
    }
    exit(0);
  }

  int status = 0;
  while(1)
  {
     pid_t ret = waitpid(id,&status,WNOHANG);                                                                                                                                                                  
      if(ret == 0)                            
      {                                                                   
        // 子进程没有退出,但是waitpid等待是成功的,需要父进程重新进行等待
        printf("父进程运行");
      }                                                                   
      else if (ret > 0)                                                   
      {                                                                   
        // 子进程退出,waitpid也成功l,获取到对应的结果了
        printf("获得子进程的退出码%d,子进程的退出信号%d",(status>>8)&0xFF,status&0x7F);
        break;
      }                                                     
      else                                               
      {                                                  
        // 等待进程失败
        perror("waitpid");
        break;
      }
  }
  sleep(1);
}

进程程序替换

用fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),若想让子进程执行另一个程序,往往需要调用一种exec函数。

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(借尸还魂),并从新程序的启动例程开始执行。

在这里插入图片描述

看上图为子进程的他的地址空间(虚拟内存)和页表没有变化只有物理内存改变了通过修改物理内存的数据数据和代码从而改变子进程运行全新的内容(借尸还魂),但要注意的是原本子进程与父进程是公用一个空间(代码与数据)但子进程的数据被改变后会写实拷贝从此子进程与父进程的的数据不在有关联**(父亲是老师儿子开公司)**

有的人要问了如何替换呢??(小朋友你是否有很多问号?)

替换函数(exec系列函数)

替换函数有六种以exec开头的函数,它们统称为exec函数:

execl

int execl(const char *path, const char *arg, ...);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

也可以

execl("/usr/bin/ls", "ls", "-ali", NULL);

execlp

int execlp(const char *file, const char *arg, ...);

第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execlp("ls", "ls", "-a", "-i", "-l", NULL);

execle

int execle(const char *path, const char *arg, ..., char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myenvp[] = { "MYVAL=qwe", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

execv

int execv(const char *path, char *const argv[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);
12

execvp

int execvp(const char *file, char *const argv[]);

第一个参数是要执行程序的名字,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };

execve

int execve(const char *path, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=qwe", NULL };
execve("./mycmd", myargv, myenvp);

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

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

相关文章

Spring - BeanFactory与ApplicationContext介绍

文章目录 Spring Bean一、BeanFactory 快速入门1.1 BeanFactory 开发步骤1.2 DI依赖注入 二、ApplicationContext快速入门2.1 入门2.2 BeanFactory 与 ApplicationContext关系2.3 BeanFactory 继承体系2.4 ApplicationContext 继承体系 Spring Bean 之前也了解过Spring Bean&a…

高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression)

文章目录 效果一览文章概述研究内容程序设计参考资料效果一览 文章概述 高斯过程回归 | Matlab实现高斯过程回归多输入单输出预测(Gaussian Process Regression) 研究内容 高斯过程回归(Gaussian Process Regression,GPR)是一种基于概率模型的非参数回归方法,可以用于

mybatisplus数据权限插件学习初探 动态表名更换插件

文章目录 学习链接 mybatisplus数据权限插件学习初探前言案例建表用户表订单表 环境准备UserUserMapperUserMapper.xmlOrdersOrdersMapperOrdersMapper.xml 配置UserTypeEnumUserContextHolderCustomizeDataPermissionHandlerMybatisPlusConfig 测试测试类bossdeptManagerclerk…

Zinx框架学习 - 消息封装

Zinx - V0.5 消息封装 之前我们使用Request来保存服务器的数据&#xff0c;很显然使用[]byte来接收数据&#xff0c;没有长度也没有消息类型&#xff0c;接下来就要针对这个消息进行封装 创建消息类型 定义一个基本的message包&#xff0c;会包含消息ID、数据、数据长度三个…

路径规划算法:基于探路者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于探路者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

涉及float和double

文章目录 涉及float和double的问题&#xff1a;它们的存储方式&#xff1a;有效位&#xff1f; 链式结构 涉及float和double的问题&#xff1a; 它们的存储方式&#xff1a; 它们会分成小数部分和指数部分分别存储。小数部分的有效位数越多&#xff0c;精度就越高&#xff0c;…

NLP超详细新手快速入门上手篇(1)常用函数

前言 自然语言处理(NLP)是机器学习的应用之一&#xff0c;用于分析、理解和生成自然语言&#xff0c;以便人类与计算机&#xff0c;人类与人类更好的交流。自然语言处理按照任务类型可以分为分类、匹配、翻译、结构化预测、与序贯决策过程这五类。 本篇参考自TensorFlow官方文…

MyBatis 查询数据库

✏️作者&#xff1a;银河罐头 &#x1f4cb;系列专栏&#xff1a;JavaEE &#x1f332;“种一棵树最好的时间是十年前&#xff0c;其次是现在” 目录 MyBatis 是什么&#xff1f;第⼀个MyBatis查询创建数据库和表添加MyBatis框架支持设置 MyBatis 配置信息添加业务代码 查询操…

【VBA】实现批量生成二维码

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

Nginx - ​一个高性能、灵活可靠的开源Web服务器

Nginx是什么&#xff1f; Nginx是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的&#xff0c;第一个公开版本0.1…

idea使用native-image打包springboot项目

native-image简介 native-image 是一个用于将 Java 程序编译为本地可执行文件的工具。它是 GraalVM 的一部分&#xff0c;GraalVM 是一个高性能的通用虚拟机&#xff0c;支持多种语言。 使用步骤 下载GraalVM 安装 GraalVM&#xff1a;首先&#xff0c;你需要安装 GraalVM。…

20230603-周六随笔

周六闲来无事&#xff0c;给新电脑装下开发环境&#xff0c;记录一下遇到的问题 git下载代码报错 报错1&#xff1a;schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)解决方法&#xff1a;执行git config --system http.sslbackend openssl命令 报错2&#xff1a;SSL certifica…

【Java 8 新特性】获取对象列表中的某个属性组成的列表

文章目录 获取对象列表中的某个属性组成的列表1、用法示例2、详细案例 附录&#xff1a;Java 8 Stream 基本用法1、map2、filter3、forEach4、limit5、sorted6、并行&#xff08;parallel&#xff09;程序7、Collectors8、统计 获取对象列表中的某个属性组成的列表 1、用法示例…

高完整性系统工程(十一):Fault Tolerant Design

目录 1. INTRODUCTION TO FAULT TOLERANCE 1.2 Definitions 1.3 Two Kinds of Faults 1.4 Hardware vs Software Faults 1.4.1 Failure Curve for Hardware 1.4.2 Hardware and Software Failures 1.5 Causes of Failures 1.6 3 Ways to Class Failures 1.6.1 Tempora…

【LLM】大模型值得探索的十个研究方向

note 基础理论&#xff1a;大模型的基础理论是什么&#xff1f; 网络架构&#xff1a;Transformer是终极框架吗&#xff1f; 高效计算&#xff1a;如何使大模型更加高效&#xff1f; 高效适配&#xff1a;大模型如何适配到下游任务&#xff1f; 可控生成&#xff1a;如何实…

ChatGPT有关的模块知多少?

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 ChatGPT有关的模块知多少&#xff1f; &#x1f9ca;摘要&#x1f9ca;ChatGPT 开发库清单 &#x1f9ca;摘要 本文介绍了基于OpenAI ChatGPT 的API 开发的python 模块库。【原创&am…

STM32cubemx定时外部模式测量10M以上频率

STM32cubemx定时外部模式测量10M以上频率 本文讲解利用定时器的外部时钟功能&#xff0c;巧妙测量高频外部信号频率。范围可以到高达30M以上。 所需工具&#xff1a; 开发板:STM32F103RCT6STM32CubeMXIDE: Keil-MDK 文章目录 STM32cubemx定时外部模式测量10M以上频率原理讲解…

【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(Optional篇)

Guava相关的介绍 Guava工程包含了许多被Google的Java项目广泛依赖的核心库。我们希望通过本文档为Guava中最流行和最强大的功能提供更具可读性和解释性的说明。 本教程是中级教程&#xff0c;适合 Guava 中级开发者的进阶学习。 学习Guava前的准备工作 学习目标和计划&#xf…

Python学习笔记 - 探索集合Set

尊敬的各位&#xff0c;我是Mr.数据杨&#xff0c;非常高兴和你们一起走进Python的世界。今天&#xff0c;让我们一起探讨Python中神奇的“集合”。你们是否读过《三国演义》&#xff1f;你们应该记得那场赫赫有名的“桃园三结义”吧&#xff1f;那让我们以此为例&#xff0c;来…

Windows动态链接库的生成和使用

工程需要&#xff0c;最近在编一组Windows上的动态链接库给Python调用。之前做过Linux下C动态库的编译&#xff0c;并提供给Python调用&#xff0c;Windows下的编译跟Linux还是有些差距&#xff0c;因此花了一点时间跑通&#xff0c;在这里记录一下。 为了完整对比&#xff0c…