Linux之进程控制(上)

news2024/11/22 15:41:02

目录

进程创建

进程终止 

进程退出码

进程终止的方式

进程等待 

进程等待的方式

 status概述

总结


上期我们学习了Linux中进程地址空间的概念,至此进程的所有基本概念已经全部学习完成,今天我们将开始学习进程相关的操作。

进程创建

进程创建其实之前我们已经讨论过,进程创建有两种方式。

1.程序编译之后形成可执行程序,运行可执行程序,可创建进程。

 

2.使用fork函数创建子进程。

#include<stdio.h>
#include<unistd.h>
int a = 100;
int main()
{
  if(fork()==0)
  {
    printf("#############我是子进程更改前a的值等于%d ###########\n",a);
    a = 200;
    printf("#############我是子进程更改后a的值等于%d 地址为%p \n",a,&a);
  }
  else{
    printf("##############我是父进程,a的值等于%d 地址为%p \n",a,&a);
    sleep(2);
  }
  
  return 0;
}

 创建子进程的目的就是让子进程去做跟父进程不一样的事情(任务)。在fork函数执行之后,会去执行fork函数之后的代码,因为子进程会继承父类的pcb,pcb中有pc指针,用于记录进程的上下文数据,正是因为如此,子进程拥有和父类一样的pc指针,而且子进程和父进程是共享代码的,所以子进程会去执行fork函数之后的代码。

进程终止 

什么是进程终止呢?进程终止其实就是进程退出,进程退出总共有三种情况。

1.代码运行完毕,结果正确。

2.代码运行完毕,结果不正确。

3.代码异常终止。 

那么既然进程退出总共有三种情况,那么我们怎样去分辨这三种情况呢?其实这就涉及到了下一个概念------进程退出码

进程退出码

直接给出进程退出码的概念。

进程退出码:一个整型变量,在进程退出时返回,返回0为代码运行完毕,结果正确;返回非0(非0有很多种可能,每一个可能为一种错误的原因)为代码运行完毕,结果不正确;当代码异常终止时,进程退出码是没有任何意义的。

通过一个场景为大家熟悉一下进程退出码。比如学校考试,进程退出码可以看做考试成绩,满分只有一种,但是非满分有很多种,用满分对应退出码为0,非满分对应退出码我为非0,当考场作弊时,分数已经没有了任何意义,对应为代码异常终止,进程退出码无意义。

进程终止的方式

进程终止有三种方式。

1.使用main函数作为返回。

#include<stdio.h>

int main()
{
  printf("hello world!\n");
  return 0;
}

 上述代码大家并不陌生,大家可能经常会无脑写return 0,但是有多少人想过为什么要写return 0呢。这个return 0其实就是进程退出的标志,返回0证明代码运行结束结果正确,进程退出。可以使用echo $?查看当前进程的退出码。

当然,除过return 0 之外我们可以自己返回任意退出码。我们将return 0改为了return 1.

 2.使用exit函数进行进程退出,函数的参数为进程退出码。

#include<stdio.h>
#include<unistd.h>

int main()
{
  printf("hello world!\n");
  //return 1;
  exit(0);
}

 

通过运行结果可知,exit可以进行进程的退出,退出码也与我们设置的一样。

3.使用_exit函数进行进程退出,函数的参数为进程退出码。 

#include<stdio.h>
#include<unistd.h>

int main()
{
  printf("hello world!\n");
  _exit(1);
}

 

 通过运行结果可知,exit可以进行进程的退出,退出码也与我们设置的一样。

那么问题来了,exit函数和_exit函数都可以进行进程退出,那么它们的区别是什么呢?我们以下述代码为大家解释。

 1.exit函数。

#include<stdio.h>
#include<unistd.h>

int main()
{
 
  printf("hello world!");
  exit(0);
 
}

2._exit函数。

#include<stdio.h>
#include<unistd.h>

int main()
{
 
  printf("hello world!");
  _exit(0);
 
}

 

上述代码,除了函数不同之外,其它部分全部相同,为什么运行结果不一样呢,exit函数打印出了对应的结果,而_exit函数没有打印对应的结果。这究竟是为什么呢?

其实,这也就是exit函数和_exit函数的区别,因为上述打印代码都没有带‘\n’,所以在打印的时候时不会刷新缓冲区,所以要最终在显示器上打印出来,就必须刷新缓冲区。两个函数只有exit会在函数运行结束时刷新缓冲区,而_exit不会刷新缓冲区,所以就有了第一份代码打印对应的结果,而第二份代码不去打印。

进程等待 

我们知道任何进程在退出时都会先转为Z状态(僵尸态),因为僵尸态的进程是已经死亡的进程,操作系统无法杀死已经死亡的进程,所以当系统中存在大量的僵尸进程时,就会占用大量的系统资源,造成资源的浪费。那么究竟如何避免系统产生大量的僵尸进程呢?进程等待就是最好的一个解决方式。

我们知道僵尸进程的产生原因就是子进程退出时,产生大量的退出信息而父进程不对这些信息进行处理所造成的,所以要避免产生僵尸进程,就要使用进程等待的方式,让父进程去处理子进程产生的退出信息,从而避免僵尸进程的产生。

进程等待的方式

1.wait函数。

wait函数由两个返回值,返回值>0,意味着等待成功,有子进程退出,返回值为子进程的pid。返回值<0,意味着等待失败。 

参考下述代码。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.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);
      sleep(1);
      cnt--;
    }
  }

  else{
    //parent
    sleep(10);
    //父进程开始进行等待
    int ret=wait(NULL);
    if(ret>0)
    {
      printf("父进程等待成功!\n");
    }
    else{
      printf("父进程等待失败!\n");
    }
    sleep(10);
    
  }
   

整个执行逻辑是,让父进程先休眠10s,在父进程休眠期间,子进程进行打印,打印完成之后,子进程退出,但是由于父进程在休眠,所以父进程不对子进程的退出信息进行处理,所以子进程变成了僵尸进程,但是当父进程睡眠10s被唤醒之后,就开始进行等待,将子进程的退出信息进行了处理,此时子进程就从僵尸态变成了死亡状态,从Z变X状态。然后只剩父进程,父进程继续休眠10s,父进程退出,然后后进程数量变成了0。运行结果如下。

运行结果符合预期。

子进程状态由S变成了Z状态,符合预期。

然后由Z变成了X状态,符合预期。

  

最终系统进程变成了0,符合预期。

2.waitpid函数。

waitpid有三个返回值,返回值>0,意味着父进程等待成功,返回值为子进程的pid,返回值=0,意味着等待成功,但是没有子进程退出,返回值<0,意味着等待失败。 

 waitpid函数的功能与wait函数的功能类似。二者的区别仅仅是参数的区别。

wait中的status和waitpid中的status是同一个参数,意义相同,所以我们重点介绍waitpid的三个参数。

第一个参数pid:当pid为-1时,意味着父进程等待任意一个子进程,即与wait函数功能一致。当pid不为-1时,则表明等待pid为当前值的进程。

第二个参数status: 一个输出型参数。

我们知道父进程等待其实就是获取子进程的退出信息,子进程有什么退出信息呢。进程退出我们知道有三种情况,前两种都是代码执行完毕,进程正常退出,所以我们用退出码可以识别进程的退出状态。最后一种是异常退出,异常退出我们用是否存在信号来识别。综上子进程退出的信息无非就有两种进程的退出码和信号。所以父进程通过这两种信息来判断子进程的退出状态。

 status概述

status可以看做是一个整型,所以总共有32个比特位,但是我们往往不关心高16位,我们只关心低16位。图示如下:

低16位中的次低8位用于存放子进程的退出码,低8位的0-6位存放信号信息,第7位是core dump标志,这个我们后期在为大家讲解。

所以父进程判断子进程是否是正常退出时,先判断信号为是否为0,如果为0意味着正常退出,则去获取退出码,判断运行结果是否正确。 

那么具体的这些退出码信息和信号信息是怎样写入status中的呢?

其实在子进程退出时,它的退出信息(退出码和信号)全部保存在了其pcb中,父进程从子进程的pcb中获取到了退出码信息和信号信息,最终让status和退出码信息和信号信息进行与操作和移位操作,使得退出码信息和信号信息存储在了status中,最终可以通过status获得子进程的退出码和信号信息。但是移位和与操作比较麻烦,操作系统给了两个函数用于获取这两个信息。WIFEXITED(status)用于判断进程是否正常退出,为真则正常退出,否则为异常退出,WEXITSTATUS(status)用于获取子进程的退出码。

 第三个参数options:即用于指定父进程是阻塞等待还是非阻塞等待。为0则为阻塞等待,为WNOHANG则为非阻塞等待。

所谓阻塞等待和非阻塞等待其实就是,在等待的过程中父进程是否可以干自己的事情。若为阻塞状态,则意味着父进程的pcb被加载到了等待队列中,父进程不能被cpu运行,所以父进程也就不能干其它的事;若为非阻塞状态,意味着父进程的pcb仍运行队列中,可以被cpu调度,所以可以执行其它事情。

代码如下。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.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);
      sleep(1);
      cnt--;
    }
  }

  else{
    //parent
    sleep(10);
    //父进程开始进行等待
   // int ret=wait(NULL);
   
    int status = 0;
    int ret=waitpid(-1,&status,0);
    if(ret== 0)
    {
      //等待成功,但是没有子进程退出
      printf("父进程等待成功,没有子进程退出!\n");
    }
    else if(ret > 0){
      //等待成功,有子进程退出
      printf("等待成功,子进程pid为:%d\n",ret);
    }
    else
    {
      //等待失败
      printf("父进程等待失败!\n");
    }
    sleep(10);
 
  }
  
}

 运行结果如下:

运行结果符合预期。

 

子进程有由S状态转为Z状态。符合预期。

子进程由Z状态转为X状态,只剩一个父进程,符合预期。 

父进程休眠结束,进程退出,进程数量变为0,符合预期。

总结

进程相关的操作已经基本完成,只剩下进程替换,进程替换我们下期单独来讲,因为进程替换相对比较重要。本期的主要内容为进程创建,进程退出,进程等待的操作,大家要掌握相关的概念以及实现这些操作要使用的函数接口以及每个参数的具体含义,特别是进程等待的第二个方法waitpid的参数。

本期内容到此结束^_^

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

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

相关文章

上市公司环境研究汇总数据集(2008-2022年)

数据简介&#xff1a;上市公司环境研究是指对上市公司在环境保护和可持续发展方面的表现和做法进行评估和研究。这些评估可以包括上市公司的环境风险、环境管理制度和政策、环境负债和环境绩效等方面。 上市公司环境研究可以帮助上市公司更好地了解自身的环境状况和风险&#…

聊天伴侣-豆包

前言 2024年5月14日凌晨&#xff0c;OpenAI发布最新多模态大模型 GPT-4o。GPT-4o的“o”代表“omni”&#xff0c;意为全能&#xff0c;与现有模型相比&#xff0c;它在视觉和音频理解方面尤其出色。GPT-4o可以在音频、视觉和文本中进行实时推理&#xff0c;接受文本、音频和图…

1/7精确到100位,1000位,100000位怎么算?

双精度 Console.WriteLine("1/7的值是" (double)1 / 7);结果:0.14285714285714285 即使使用双精度浮点数&#xff0c;精确的位数也是有限的&#xff0c;如果想精确到小数点后100位&#xff0c;1000位&#xff0c;甚至更高哪&#xff1f; 朴素的除法 除数 余数 商…

【C++】初识C++(一)

一.什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度 的抽象和建模时&#xff0c;C语言则不合适。为了解决软件危机&#xff0c; 20世纪80年代&#xff0c; 计算机界提出了OOP(object o…

第三阶段Spark

Spark和PySpark的介绍 PySpark的相关设置 安装PySpark库 pip install pyspark pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyspark 构建PySpark执行环境入口对象 # 导包 from pyspark import SparkConf, SparkContext# 创建SparkConf类对象 conf SparkConf()…

【shell脚本实战案例】数据磁盘初始化

文章目录 一、案例应用场景二、案例需求三、案例算法四、代码实现五、实现验证 &#x1f308;你好呀&#xff01;我是 山顶风景独好 &#x1f388;欢迎踏入我的博客世界&#xff0c;能与您在此邂逅&#xff0c;真是缘分使然&#xff01;&#x1f60a; &#x1f338;愿您在此停留…

Geotools系列说明之JTS空间几何介绍

JTS介绍 The JTS Topology Suite is a Java API that implements a core set of spatial data operations using an explicit precision model and robust geometric algorithms. It provides a complete model for specifying 2-D linear Geometry. Many common operations i…

Linux部署Java项目至云服务器

文章目录 1.服务器环境2.发布部署过程2.1 执行SQL脚本2.2 修改代码中数据源的配置2.3 修改配置中的日志级别与日志文件路径2.4 打包Java程序2.5 上传到服务器2.6 后台运行2.7 服务器开放对应的端口2.8 访问验证 1.服务器环境 要将我们的项目部署到云服务器上我们就需要先有一个…

Sentinel解决雪崩问题

我们或多或少都对雪崩问题有点了解&#xff0c;在微服务系统中&#xff0c;各个微服务互相调用&#xff0c;关系错综复杂&#xff0c;如果其中一个微服务挂了或者处理消息的速度大幅下降&#xff0c;需要被处理的消息越积越多&#xff0c;那么影响的不仅仅是本微服务的功能&…

MathType2024最新破解版在哪里可以下载?

在当今科技日益发展的时代&#xff0c;我们每个人都可能遇到需要在电子文档、网页或其他平台中输入复杂数学公式的情况。这时&#xff0c;一个强大且易用的数学公式编辑器就成了我们迫切需要的工具。而MathType&#xff0c;作为一款专业、精准的数学公式编辑器&#xff0c;无疑…

大模型和数据库最新结合进展

写在前面 本文主要内容是上次接受 infoQ 访谈&#xff0c;百度智能云朱洁老师介绍了大模型和 AI 结合相关话题&#xff0c;这次整体再刷新下&#xff0c;给到对这个领域感兴趣的同学。 当前&#xff0c;百度智能云云数据库特惠专场开始&#xff01;热销规格新用户免费使用&am…

IDEA中导入Maven项目

IDEA中导入Maven项目 方式1&#xff1a;使用Maven面板&#xff0c;快速导入项目 打开IDEA&#xff0c;选择右侧Maven面板&#xff0c;点击 号&#xff0c;选中对应项目的pom.xml文件&#xff0c;双击即可 说明&#xff1a;如果没有Maven面板&#xff0c;选择 View > Appe…

<电力行业> - 《第8课:输电(一)》

1 输电环节的意义 电能的传输&#xff0c;是电力系统整体功能的重要组成环节。发电厂与电力负荷中心通常都位于不同地区。在水力、煤炭等一次能源资源条件适宜的地点建立发电厂&#xff0c;通过输电可以将电能输送到远离发电厂的负荷中心&#xff0c;使电能的开发和利用超越地…

firewalld(2)安装、配置文件、规则查询

安装firewalld 我使用的操作系统是debian 12,并没有安装firewalld。 通过apt install firewalld安装firewalld firewalld 本身是一个服务(firewalld.service),可以通过 systemctl 进行启动、停止和重启,而iptables 本身并不是一个服务,而是一个用户空间工具,被用来配置底…

什么是预主密钥(pre-master secret)?

什么是预主密钥&#xff08;Pre-Master Secret&#xff09;&#xff1f; 在SSL/TLS协议中&#xff0c;预主密钥&#xff08;Pre-Master Secret&#xff09;是建立安全连接的关键要素之一。它在客户端和服务器之间生成共享密钥的过程中扮演重要角色。本文将详细介绍预主密钥的生…

J018_冒泡排序

一、排序过程 如果要对一个数组进行升序排序&#xff1a; 每个轮次两两数字进行比较&#xff0c;如果前面的数字大于后面的数字&#xff0c;则交换两个数字的位置&#xff1b;如果前面的数字小于或等于后面的数字&#xff0c;则这两个数字位置不变。直到把数组中所有数字比较…

打靶记录——靶机medium_socnet

靶机下载地址 https://www.vulnhub.com/entry/boredhackerblog-social-network,454/ 打靶过程 由于靶机和我的Kali都处于同一个网段&#xff0c;所以使用arpscan二次发现技术来识别目标主机的IP地址 arpscan -l除了192.168.174.133&#xff0c;其他IP都是我VMware虚拟机正…

amis源码 更新组件数据域的几种方法

更新组件数据域的几种方法&#xff1a; 默认都是合并数据&#xff0c;非覆盖(指定replace为true的才是覆盖)&#xff1a; const comp amisScoped.getComponentById(id);//或者getComponentByName(name) 1.comp.setData(values, replace); //更新多个值values&#xff0c; r…

wget之Win11中安装及使用

wget之Win11中安装及使用 文章目录 wget之Win11中安装及使用1. 下载2. 安装3. 配置环境变量4. 查看及使用1. 查看版本2. 帮助命令3. 基本使用 1. 下载 下载地址&#xff1a;https://eternallybored.org/misc/wget 选择对应的版本进行下载即可 2. 安装 将下载后的wget-1.21.4-w…

Rpc服务的提供方(Rpcprovider)的调用流程

首先&#xff0c;服务的提供方&#xff0c;会通过rpcprovider向rpc服务方注册rpc服务对象和服务方法&#xff0c; 那么&#xff0c;我们通过protobuf提供的抽象层的service和method&#xff0c;将服务对象和它所对应的服务方法记录在map表中&#xff0c; 当它启动以后&#xff…