Linux-进程控制

news2024/9/21 2:38:01

进程控制

    • 进程创建
      • fork函数
      • 写时拷贝
      • fork常规用法
      • fork调用失败的原因
    • 进程终止
    • 进程等待
    • 进程程序替换
      • 程序替换的原理
      • 如何程序替换

进程创建

fork函数

fork之前父进程独立运行,fork之后,父子两个执行流分别执行

进程具有独立性,代码和数据必须独立的,代码只能读取,数据通过写时拷贝实现独立。

fork之后,是否只有fork之后的代码是父子进程共享的?

fork之后,父子共享所有的代码

子进程执行的后续代码!=共享的所有代码,只不过子进程只能从这里开始执行

fork之后,会给子进程分配内存块和内核数据结构,将父进程的部分数据结构内容拷贝到子进程的数据结构中,在父进程的pcb中的上下文数据中存在一个程序计数器eip/pc指针),eip存储正在运行指令的下一条指令,因为子进程的pcb是用父进程的pcb初始化的,所以现在子进程pcb中的eip中存储的内容和父进程相同,那么子进程只能从这里开始执行。

**补充:**程序进行循环,跳转,都是修改eip来实现

写时拷贝

写时拷贝本身就是由os的内存管理模块完成的

页表中除了映射关系还有读写属性

当刚创建子进程时,子进程的映射关系继承自父进程,会把父进程页表,子进程页表的映射关系都设置为只读的,当发生修改时,再将修改的数据拷贝到另一个空间,修改子进程的映射关系,把这个数据的读写属性改为可写。

image-20221007094809716

为什么要写时拷贝?

创建子进程的时候,就把数据分开,不行吗?

  1. 父进程的数据,子进程并不一定要修改,不修改,就没有必要再拷贝一份,如果全部拷贝一份,会导致浪费空间。

  2. 如果fork的时候,就无脑拷贝数据的子进程,会增加fork的成本(内存和时间)

  3. 写时拷贝是延迟拷贝

    因为,拷贝数据的最小拷贝成本就是只拷贝修改的数据

    既然拷贝成本依旧存在,我们不再一开始拷贝,而是等到需要时,再拷贝。

    那么在需要这个空间之前,把这个空间先给别人使用,变相提高了内存的使用率

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段(比如:通过fork的返回值判断父子进程执行不同的代码)

    int main()
    {
        pid_t id=fork();
        if(id==0)
        {
            printf("我是子进程\n");
        }
        else
        {
            printf("我是父进程\n");
        }
        return 0;
    }
    
  • 一个进程要执行一个不同的程序(比如:子进程从fork返回后,调用exec函数(程序替换))

    int main()
    {
        pid_t id=fork();
        if(id==0)
        {
            printf("我是子进程\n");
        }
        else
        {
            printf("我是父进程\n");
            execlp("ls","ls",NULL);
        }
        return 0;
    }
    

fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程超出了限制

进程终止

在C/C++中,我们写main函数,都会在结尾return 0;

  1. 这个0是return给谁的?
  2. 为什么是0,其他值可不可以?
  1. return + 数字,这个数字叫做进程退出码,当进程退出时,会把进程退出码放到该进程的pcb中,父进程从子进程的pcb中获取进程退出码,所以这个进程退出码是给父进程的。

  2. 进程退出分为三种

    1. 进程跑完,结果正确

    2. 进程跑完,结果错误

    3. 进程异常退出

      0:表示进程跑完

      非0:表示进程异常退出,异常退出的原因不同,数字不同

echo $?		//在bash中,最近一次执行完毕时,对应的进程退出码

失败的非零值可以自定义

关于终止的常见做法

  1. 在main函数中return,为什么其他函数不行

    非main函数 return叫做函数调用返回值

    main函数return叫做进程退出

  2. 在任何一个地方调用exit(参数进程退出码),叫做进程退出

    exit()终止进程,会刷新缓冲区

    _exit()终止进程,不会刷新缓冲区

关于终止,内核做了什么?

进程终止后,进程就会进入僵尸进程,父进程进程等待子进程,获取子进程的退出信息,子进程进入死亡状态,这时,这个进程才是真正的退出。

进程 = 内核数据结构 + 进程代码 和 数据

退出就要释放这个进程内核的数据结构 + 代码 和 数据

但是操作系统可能不会释放该进程的内核数据结构,创建对象分为两步:1、开辟空间 2、初始化

操作系统会维护一个废弃数据结构链表,进程退出后,会将这个进程放到这个链表中,等到创建一个新进程的内核数据结构时,就将它们从这个链表拿出来,直接初始化,就节省了开辟空间的工作。

这个废弃数据结构链表也叫内核的数据结构缓冲池,slab分派器

进程等待

为什么要进程等待?

  • 子进程退出,父进程需要知道子进程的任务做的如何,否则,会导致子进程一直处于僵尸状态,进而造成内存泄漏
  • 如果进程进入僵尸状态,就无法杀死(kill -9)
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

如何等待?

wait

wait(NULL); //等待任意一个退出的子进程

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>		//wait所需头文件
#include<sys/wait.h>		//wait所需头文件

int main()
{ 
    pid_t id=fork();
    if(id==0)
    {
        int cnt=10;
      	while(cnt--)
      	{
        	printf("我是子进程,pid:%d\n",getpid());
        	sleep(1);
      	}
    }
    else
    {
      	printf("我是父进程,pid:%d\n",getpid());
      	sleep(20);
      	int ret = wait(NULL);
      	printf("ret:%d\n",ret);                                                               	                                               
      	int cnt=3;
      	while(cnt--)
      	{
        	sleep(1);
      	}
    }
    return 0;    
}

waitpid

pid_t waitpid(pid_t pid,int *status,int options); //等待一个指定的退出子进程

pid

pid>0:是几,就代表哪一个子进程,指定等待进程

pid=-1,等待任一进程,等价于wait

status: 输出型参数,从该子进程的pcb中获取

通过调用该函数,从函数内部拿出来特定的数据(从子进程的pcb中拿出来子进程退出码)(子进程退出时,会把子进程的退出信息写入到进程的pcb中,代码可以释放)

status是一个32位整数,我们只取低16位整数,其中,次低8位为进程退出码,低7位为进程终止信号,低第8位为core dump标志

image-20221010204510716

options: options = 0为阻塞等待,options = WNOHANG为非阻塞等待

**阻塞等待:**当父进程等待子进程时,子进程还没有退出,父进程阻塞,等到子进程退出,唤醒父进程,等待子进程

阻塞等待返回值:

返回值>0等待成功,返回子进程pid

返回值<0等待失败

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

int main()
{
    pid_t id=fork();
  	if(id==0)
  	{
    	int cnt=5;
    	while(cnt)
    	{   
      		printf("i am a child progress, pid : %d , %d s\n ",getpid(),cnt--);
      		sleep(1);
    	}
    	printf("子进程退出\n");
    	exit(10); 
  	}
  	else
  	{
    	int status=0;
    	int ret=waitpid(id,&status,0);
    	printf("退出码: %d , 退出信号: %d , coredump标志位: %d\n",(status>>8)&0xFF,status&0x7F,status&0x80);
  	}
  	return 0;
}

**非阻塞等待:**当父进程等待子进程时,子进程还没有退出,父进程先去做自己的事

//非阻塞等待一般多次调用非阻塞接口,轮询检测(循环)

非阻塞等待返回值:

>0,等待成功,返回自己子进程**pid**,
=0,等待成功,子进程还没有退出,父进程做自己的事
<0,等待失败

kill -l所有信号,没有零号

如果异常退出,只关心退出信号,退出码没有意义

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

typedef void(*handler_t)();

std::vector<handler_t> handlers;

void fun1()
{
  printf("方法一\n");
}

void fun2()
{
  printf("方法二\n");
}

void Load()
{
  handlers.push_back(fun1);
  handlers.push_back(fun2);
}

int main()
{
  pid_t id=fork();
  if(id==0)
  {
    int cnt=5;
    	while(cnt)
    	{   
      		printf("i am a child progress, pid : %d , %d s\n ",getpid(),cnt--);
      		sleep(1);
    	}
    	printf("子进程退出\n");
    	exit(10); 
  }
  else
  {
    int status=0;
    while(1)
    {
      int ret=waitpid(id,&status,WNOHANG);
      if(ret>0)
      {
        //等待成功
        printf("退出信号: %d , 退出码: %d , coredump标志位: %d\n",(status>>8)&0xFF,status&0x7F,status&0x80);
        break;
      }
      else if(ret==0)
      {
        //等待成功,子进程还没退出,父进程做自己的事
        if(handlers.empty())
          Load();
        for(auto f : handlers)
        {
          f();
          sleep(1);
        }
      }
      else 
      {
        //等待失败
      }
    }
  }
  return 0;
}

补充:

WITEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出)

WEXISTATUS(status):若WITEXITED非零,提取子进程退出码。(查看进程的退出码)

进程程序替换

之前都是子进程执行父进程的代码片段

如果我们想让创建出来的子进程,执行权限的程序呢?

进程程序替换

子进程往往需要干两种事

  1. 执行父进程的代码片段
  2. 执行磁盘中一个全新程序

程序替换的原理

  1. 将磁盘中的程序加载到内存中

  2. 为页表重新建立映射关系,映射到这个全新的程序代码

    让父进程和子进程彻底分离,并让子进程执行一个全新程序

如何程序替换

通过系统调用完成

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],

记忆方法

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

execle函数

自己维护的环境变量,是覆盖式的

int main()
{
  pid_t id=fork();
  if(id==0)
  {
    printf("我是子进程,pid:%d,ppid:%d,将要程序替换\n",getpid(),getppid());
    char* const env_[]={(char*)"MYPATH=byld",NULL};
   	execle("./mycmd","mycmd",NULL,env_);
  }
  
  else
  {
    int status=0;
    int ret = waitpid(id,&status,0);
    if(ret==id)
    {
      sleep(2);
      printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
    }
  }
  return 0;
}

image-20221029215612585

如何使用系统提供的环境变量?

extern char** environ;

execle("./mycmd","mycmd",NULL,environ);

如何追加环境变量?

在bash中export MYPATH=1234

image-20221029221225222

进程替换实例:

myproc的子进程进程替换成mycmd

myproc.cpp

int main()
{
  pid_t id=fork();
  if(id==0)
  {
    	printf("我是子进程,pid:%d,ppid:%d,将要程序替换\n",getpid(),getppid());
      	execl("./mycmd","mycmd",NULL);
  }
  else
  {
    int status=0;
    int ret = waitpid(id,&status,0);
    if(ret==id)
    {
      sleep(2);
      printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
    }
  }
  return 0;
}

mycmd.c

int main()
{
    printf("PATH:%s\n",getenv("PATH"));    
  	int n=10;
  	int sum=0;
  	for(int i=1;i<=n;i++)
  	{
    	sum+=i;
  	}
  	printf("%d\n",sum);
  	return 0;
}

image-20221029214503596

注意:不带e的程序替换函数,替换后的子进程也有环境变量,同样继承自父进程(从这个实例可知)

为什么会有这么多接口?execve为什么是单独的?

execve才是真正的系统接口,其他的是对这个系统接口的封装,都会转化成对execve这个系统接口的调用,为了适配应用场景

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

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

相关文章

机器学习HMM模型

目录1 马尔科夫链1.1 简介1.2 经典举例1.3 小结2 HMM简介2.1 简单案例2.2 案例进阶2.2.1 问题阐述2.2.2 问题解决3 HMM模型基础3.1 什么样的问题需要HMM模型3.2 HMM模型的定义3.3 一个HMM模型实例3.4 HMM观测序列的生成3.5 HMM模型的三个基本问题4 前向后向算法评估观察序列概率…

计算机毕业设计-SSM高校社团招新系统-JavaWeb大学生社团管理系统-源码+文档+讲解

注意&#xff1a;该项目只展示部分功能&#xff0c;如需了解&#xff0c;评论区咨询即可。 本文目录1.开发环境2.系统的设计背景3 前后台功能设计3.1 前台功能3.2 后台功能4 系统页面展示4.1 学生功能模块展示4.2 干部功能模块展示4.3 管理员功能模块展示5 更多推荐6 部分功能代…

如何用IDEA提高你的开发效率

前言 ​ 作为一名java开发工程师&#xff0c;IDEA无疑是我日常接触最多的工具。因此&#xff0c;能否高效使用IDEA软件&#xff0c;一定程度上决定了我们的开发效率。本文将主要介绍IDEA中的四个便于提高开发效率的功能&#xff0c;常用快捷键、实时模版、后缀补全、文件和代码…

《本地计算机DNS缓存文件》

C:\Windows\System32\drivers\etc 36.152.44.95 www.baidu.com 正常访问www.baidu.com可以DNS抓包&#xff0c;将百度的IP及域名加入文件位置的hosts文件中即该IP和域名将不再请求网络上的DNS服务器&#xff0c;即加快域名解析&#xff1b; 具体作用&#xff1a; 1.加快域名解…

什么是RPC框架?

什么是RPC&#xff1f; In distributed computing, a remote procedure call (RPC) is when a computer program causes a procedure (subroutine) to execute in a different address space (commonly on another computer on a shared network), which is coded as if it wer…

创新能力 | 产品经理实践中常犯的七大错误

做产品是一个既感性又理性的过程&#xff0c;纵然有很多前辈同行的经验传承和技巧指导&#xff0c;但在落到实处是时&#xff0c;总难免犯一些错误。有些是经验不足导致&#xff0c;有些则是产品经理对于人性的浅见寡闻。本文作为产品经理实践指南专题的中级篇&#xff0c;阐述…

用 AWTK 和 AWPLC 快速开发嵌入式应用程序 (2)-走马灯

AWPLC 目前还处于开发阶段的早期&#xff0c;写这个系列文章的目的&#xff0c;除了用来验证目前所做的工作外&#xff0c;还希望得到大家的指点和反馈。如果您有任何疑问和建议&#xff0c;请在评论区留言。 1. 背景 AWTK 全称 Toolkit AnyWhere&#xff0c;是 ZLG 开发的开源…

全球名校AI课程库(35)| 辛辛那提大学 · 微积分Ⅱ课程『MATH101 Calculus II』

&#x1f3c6; 课程学习中心 | &#x1f6a7; CS数学基础课程合辑 | &#x1f30d; 课程主页 | &#x1f4fa; 中英字幕视频 | &#x1f680; 项目代码解析 课程介绍 Trefor Bazett 教授在 Cincinnati 大学任教时&#xff0c;制作了两套完整的的数学课程&#xff08;微积分、离…

Eclipse创建Servlet项目-7

目录 1、创建动态 Web 项目 2、使用 Eclipse 创建 Servlet 3、配置 web.xml 4、部署项目并启动服务器 通过前面的学习&#xff0c;我们了解了如何在 Tomcat 目录下手动部署 Servlet&#xff0c;这种方式不但效率低下&#xff0c;而且容易出错。因此&#xff0c;在实际开发中…

变量常量,基本数据类型及数据类型转换

⭐️ 变量常量与基本数据类型及数据类型转换 &#x1f4cd; 来自&#xff1a;中南林业科技大学软件协会学术部&#xff1a;谢添 ⏲ 时间&#xff1a;2022 - 10 - 29 至 2022 - 10 - 30 &#x1f3e0; 官网&#xff1a;https://www.csuftsap.cn/ ✏️ 本章所有提供代码均已测…

四旋翼无人机学习第4节--STM32、MPU9250等器件的绘制

0 前言 当画stm32、mpu9250这种多引脚的芯片&#xff0c;就需要参考芯片手册啦。 这里给大家推荐一个芯片手册查询网站。 半导小芯-芯片查询工具 进入网站&#xff0c;输入芯片的具体名称&#xff0c;点击查询即可。 最后点击下载即可。 1 stm32芯片手册引脚查询 选择引脚…

学习在Git项目中使用子模块(图文教程)

一般认为 父项目 是当前正在做的主要工作&#xff0c;但需要依赖 子模块 中提供的算法或者工具。父项目 与 子模块 不是同一批人维护的&#xff0c;或者是需要分开维护的。 此情此景&#xff0c;需要学习该教程了&#xff01;&#xff01;&#xff01; 文章目录1 如何在父项目…

基于Java的一个可自由拖拽的BI可视化系统(附源码)

介绍 这是一个可自由拖拽的BI可视化系统支持主流的关系数据&#xff1a;MySQL&#xff0c;Oracle&#xff0c;PostgreSQL等同时支持Apache Doris&#xff0c;这个一开始初衷就是为了 Doris 数据可视化分析做的后端框架使用了若依 功能 按项目管理数据看板看板具备分享功能可以…

每天五分钟机器学习:超平面分离定理和凸优化

凸集和凸函数 在点集拓扑学与欧几里得空间中,凸集是一个点集,其中每两点之间的直线上的点都落在该点集中。如下所示: 函数任意两点(x,f(x))和(y,f(y))连线上的值大于(x,y)区间内任意一点m的值f(m),那么这个函数就是一个凸函数: 超平面分离定理 空间中存在两类样本,…

【CV】第 3 章:使用 OpenCV 和 CNN 进行面部检测

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

JavaScript语法知识笔记(一)——书写方式,输入出语句,变量,字面量,标识符,数据类型。

01.JS的三种书写方式 <!-- 2.内嵌式的js --><script>// alert(sajmo);</script><!-- 3.外部js script 双标签 --><script src"script.js"></script> <!-- JS代码需要写到script标签中 --><script type"text/jav…

软考高级-系统架构师-案例分析-架构设计真题考点汇总

2010年-2021年(不包括2019年和2020年)涉及到架构设计考点的有: 2010年题1,4; 2011年题1,4; 2012年题1; 2013年题1,4; 2014年题1,4; 2015年题1; 2016年题1; 2017年-题1; 2018年题1,5; 2021年题1 1.软件架构风格 软件架构风格是描述特定软件系统组织方式和惯用模式。组织方式描述…

2022第二届中国高校大数据竞赛A题(更新完毕)

文章目录题目任务做题解析第一问第三问第四问第一个预测第二个预测第五问关键技术摘要代码文件下载题目 制造业是国民经济的主体&#xff0c;近十年来&#xff0c;嫦娥探月、祝融探火、北斗组网&#xff0c;一大批重大标志性创新成果引领中国制造业不断攀上新高度。作为制造业…

公众号网课查题搭建-查题校园题库系统

公众号网课查题搭建-查题校园题库系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台…

Vue项目中Pinia状态管理工具的使用

目录Pinia与Vuex的区别使用Pinia直接修改数据的两种方式使用actions修改数据重置state中数据Pinia持久化存储Pinia模块化实现Pinia中store之间互相调用Pinia官网介绍说&#xff1a;Pinia 是 Vue 的存储库&#xff0c;它允许您跨组件/页面共享状态。Vuex同样可以作为状态管理工具…