Linux之进程控制(上)

目录

进程创建

进程终止 

进程退出码

进程终止的方式

进程等待 

进程等待的方式

 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的参数。

本期内容到此结束^_^

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

7 个不容忽视的开源安全工具

专业人士选择的第一个工具通常是开源选项,因为它们得到了广泛社区的保证和支持。此代码是支持安全可靠的互联网的基础的一部分。 最近,XZ Utils 等丑闻让用户犹豫不决。开放性是否是攻击的危险载体?还有其他问题在等着他们吗? 辩护者指出,虽然开放性可以让某些攻击变得更…

刷代码随想录有感(120):贪心算法——买卖股票的最佳时机

题干&#xff1a; 代码&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {int low INT_MAX;int res INT_MIN;for(int i 0; i < prices.size(); i){low min(low, prices[i]);res max(res, prices[i] - low);}return res;} }; 贪心…

在本地和Linux之间传输文件

1.打开本地的cmd窗口 2. 然后按这个链接的说法在cmd中远程连接Linux&#xff08;技术|如何在 Linux 中使用 sFTP 上传或下载文件与文件夹&#xff09; 3. 看这个链接里面的sftp命令进行本地和Linux之间的文件互传 &#xff08;https://www.cnblogs.com/niuben/p/13324099.htm…

动态规划基础练习

我们需要先从数组较大的开始进行处理&#xff0c;每次考察上下左右的&#xff0c;比较当前存储的最大值和转移来的值&#xff0c;哪一个大一点 #define _CRT_SECURE_NO_WARNINGS #include<bits/stdc.h> using namespace std;int n, m; int a[105][105]; int addx[] { 0,…

远程过程调用协议gRPC及在go环境下的使用

1. 远程过程调用协议 1.1 定义 远程过程调用(Remote Procedure Call&#xff0c;PRC是一种进程间通信技术&#xff0c;它使得程序可以像调用本地函数一样调用远程服务器上的函数。RPC 屏蔽了底层的通信细节&#xff0c;让开发者能够更专注于业务逻辑&#xff0c;而无需关心网络…

C语言力扣刷题4——删除链表的倒数第 N 个结点[双指针],只遍历一遍

力扣刷题4——删除链表的倒数第 N 个结点[双指针] 一、博客声明二、题目描述三、解题思路1、思路说明 四、解题代码&#xff08;附注释&#xff09; 一、博客声明 找工作逃不过刷题&#xff0c;为了更好的督促自己学习以及理解力扣大佬们的解题思路&#xff0c;开辟这个系列来记…

“用友审批,兴业付款”,YonSuite让企业资金调拨更高效

随着企业规模的扩大和经营的不断深入&#xff0c;企业的分支机构分布各地&#xff0c;在多银行多账户的资金管理上面临着诸多挑战。传统的资金管理方式往往效率低下、风险较高&#xff0c;难以满足企业快速发展的需求。 为了解决这些痛点&#xff0c;用友网络科技股份有限公司…

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

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

Redis为什么设计多个数据库

​关于Redis的知识前面已经介绍过很多了,但有个点没有讲,那就是一个Redis的实例并不是只有一个数据库,一般情况下,默认是Databases 0。 一 内部结构 设计如下: Redis 的源码中定义了 redisDb 结构体来表示单个数据库。这个结构有若干重要字段,比如: dict:该字段存储了…

人生最有力,最棒的十句话!

人生最有力&#xff0c;最棒的十句话 1、允许一切事发生&#xff0c;所有一切发生的事不是你能阻挡了的&#xff0c;你接受&#xff0c;他也发生&#xff0c;你不接受&#xff0c;他也发生&#xff0c;你还不如坦然面对接受现实。 2、你焦虑的时候千万不要躺着啥也不干&#xf…

机器人控制系列教程之关节空间运动控制器搭建(1)

机器人位置控制类型 机器人位置控制分为两种类型&#xff1a; 关节空间运动控制—在这种情况下&#xff0c;机器人的位置输入被指定为一组关节角度或位置的向量&#xff0c;这被称为机器人的关节配置&#xff0c;记作q。控制器跟踪一个参考配置&#xff0c;记作 q r e f q_{re…

2SK241 LTSpice模型及仿真

2SK241是东芝生产的一款NMOS&#xff0c;早已停产&#xff0c;但是在收音机圈子里还是有很多死忠粉&#xff0c;所以在淘宝上也是一堆打磨改标的假货。 言归正传&#xff0c;在矿坛上找到了2SK241的模型&#xff1a; .model M2SK241bottom NMOS(Level1 Rd1 Rs10 Rg50 Kp8mV…

《数据结构与算法基础 by王卓老师》学习笔记——类C语言有关操作补充

1.元素类型说明 2.数组定义 3.C语言的内存动态分配 4..C中的参数传递 5.传值方式 6.传地址方式 例子

CICD持续集成(Jenkins+Git+Gogs)

1.Jenkins Jenkins 是一个开源的、用于构建和自动化软件开发流程的持续集成和交付工具。它提供了一个可扩展的平台&#xff0c;用于构建、测试和部署软件项目。通过使用 Jenkins&#xff0c;开发团队可以实现持续集成和交付&#xff0c;自动化构建和测试过程&#xff0c;提高软…

C++Primer Plus 第十四章代码重用:14.4.7 成员模板

CPrimer Plus 第十四章代码重用 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;CPrimer Plus 第十四章代码重用&#xff1a;14.4.7 成员模板 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如…

【算法训练记录——Day37】

Day37——贪心Ⅴ 1.leetcode_56合并区间 1.leetcode_56合并区间 思路&#xff1a;排序&#xff0c;如果重叠&#xff0c;更新right 为max(right, curVal), 不重叠就加入res,需要单独考虑最后一次&#xff0c;因为每次都是在下一次遍历开始时判断是否加入res&#xff0c;因此 当…

基于Java的广场舞团管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术&#xff0c;B/S架构 工具&#xff1a;MyEclipse&#xff0c;MySQL 系统展示 首页 社团活…

AI是如何与快充技术结合的?

针对AI技术在快充领域的运用&#xff0c;我们可以进一步深入探讨AI如何与快充技术结合&#xff0c;提升充电效率和用户体验。以下是一些具体的AI技术在快充领域的应用场景&#xff1a; 一、智能充电算法 学习充电模式&#xff1a;AI算法可以学习用户的充电习惯&#xff0c;比…

微服务中的Feign远程调用

Feign的个人理解 Feign在英文中是“装”的意思&#xff0c;但在微服务中他是远程调用的一种方式&#xff0c;我的理解是&#xff1a;他替代了RestTemplateNacos中的URL编码的方式&#xff0c;显得很高大上&#xff0c;所以很装&#xff1a;&#xff08;声明式事务&#xff0c;只…

端口扫描攻击检测及防御方案

端口扫描数据一旦落入坏人之手&#xff0c;可能会成为更大规模恶意活动的一部分。因此&#xff0c;了解如何检测和防御端口扫描攻击至关重要。 端口扫描用于确定网络上的端口是否开放以接收来自其他设备的数据包&#xff0c;这有助于网络安全团队加强防御。但恶意行为者也可以…