Linux 进程替换深剖

news2024/12/20 10:56:50

目录

    • 传统艺能😎
    • 概念🤔
    • 细则🤔
    • 原理🤔
    • exec 函数🤔
      • execl😋
      • execlp😋
      • execle😋
      • execv😋
      • execvp😋
      • execve😋
    • 实现简易 shell🤔

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

概念🤔

你是否遇到过这样一个场景,**在一个多人项目中,有人用 Java 实现部分功能,有人用 C++ 实现部分功能,有人用 PHP,go,Python……**那该如何将这些不同的逻辑语言统一进来呢?

当我们 fork() 生成子进程后,子进程的代码与数据可以来自其他可执行程序。把磁盘上其他程序的数据以覆盖的形式给子进程。这样子进程就可以执行全新的程序了,这种现象称为 程序替换 \color{red} {程序替换} 程序替换

细则🤔

首先需要知道进程替换是有原则的:

  1. 进程替换不会创建新进程,因为他只是将该进程数据替换为指定的可执行程序。而进程 PCB 没有改变,所以不是新的进程,进程替换后不会改变 pid

  2. 替换成功后,替换函数后的代码不会执行,因为进程替换是覆盖式的,替换成功后进程原来的代码就消失了,同理替换失败会执行替换函数后的代码

  3. 进程替换函数在进程替换成功后不返回,函数的返回值只会表示替换失败;进程替换成功后,退出码为替换后进程的退出码

原理🤔

替换是用的是替换函数: e x e c 函数 \color{red} {exec 函数} exec函数

该函数类型使用头文件:<unistd.h>
函数原型:int execl(const char *path,const char *arg,…)

path:可执行程序的路径
arg:如何执行可执行程序
… :可变参数,是给执行程序携带的参数,在参数末尾加 NULL 表示参数结束

返回值:替换失败返回-1,替换成功不返回

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,并从新程序的启动例程开始执行,本质上和虚拟内存和写时拷贝机制有点类似,通过在页表上进行映射操作进行替换:

在这里插入图片描述

那么子进程程序替换后,会对父进程产生印象吗?

毫无疑问,子进程被创建时是与父进程共享代码和数据,但当程序替换时,也就意味着需要进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此进行程序替换后不会影响父进程的代码和数据!

exec 函数🤔

进程替换有六种替换函数,他们是以 exec 开头的函数,统称为 exec 函数:
在这里插入图片描述

execl😋

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

第一个参数是要执行程序的路径,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾

以 ls 命令为例:

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

execlp😋

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

第一个参数是要执行程序的名字,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾

同样以 ls 命令为例:

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

execle😋

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

第一个参数是要执行程序的路径,第二个参数是可变参数列表,代表我们需要以 list 的形式处理各种操作,内容是各个指令选项,并以NULL结尾,第三个参数是你自己设置的环境变量,比如我们设置了自己的 MYVAL 环境变量,在 mypro 程序内就可以使用该环境变量:

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

execv😋

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

第一个参数是要执行程序的路径,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组当中的内容是各个指令选项,数组以 NULL 结尾

还是以 ls 命令为例:

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

execvp😋

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

第一个参数是要执行程序的名字,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组当中的内容是各个指令选项,数组以 NULL 结尾

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

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

execve😋

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

第一个参数是要执行程序的路径,第二个参数是一个指针数组,代表我们需要以 vector 的形式处理各种操作,数组以NULL结尾,第三个参数是你自己设置的环境变量,比如我们设置了自己的 MYVAL 环境变量,在 mypro 程序内就可以使用该环境变量:

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

为了方便记忆,这里根据各个函数的后缀进行了归纳:

l:表示参数采用 list 列表的形式
v:表示参数采用 vector 数组的形式
p:表示能自动搜索环境变量 PATH 进行程序查找,即不需要列举程序路径
e:表示可以传入自己设置的环境(env)变量

但是事实上, 只有 e x e c v e 才是真正的系统调用 \color{red} {只有 execve 才是真正的系统调用} 只有execve才是真正的系统调用,其它 5 个函数都是调用的execve,也就是说其他 5 个函数实际上是对 execve 的系统调用进行了封装,以满足不同用户的不同调用场景,下图就是各成员间的关系:
在这里插入图片描述

实现简易 shell🤔

shell 也就是之前说的命令行解释器,运行原理就是:当有命令需要执行时,shell 创建子进程,让子进程执行命令,而 shell 只需等待子进程退出即可

在这里插入图片描述
我们将 shell 的运行逻辑分为下面的五步:

  1. 获取命令行
  2. 解析命令行
  3. 创建子进程
  4. 替换子进程
  5. 等待子进程退出

我们之前学习了 fork 函数可以进行进程创建,exec系列函数可以进行子进程替换,wait 或者 waitpid 函数可以等待子进程,那么基础框架就勾勒出来了:

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后最大个数
#define SEP " "

char commend_line[NUM];
char* commend_args[SIZE];
char env_buffer[128];
char pwd_buffer[128];
 //实现改变当前工作目录的chdir
int ChangeDir(const char* new_path)
{
  chdir(new_path);
  return 0; //调用成功
}
 //增添自义定环境变量
void PutEnvInMyShell(char* new_env)
{
  putenv(new_env);
}
 
int main()
{
  //shell 本质上就是一个死循环
  while(1)
  {
    //显示提示符
    getcwd(pwd_buffer,128);
    printf("yuanlai45@Centos %s# ",pwd_buffer);
    fflush(stdout);
    
    //获取用户输入
    memset(commend_line,'\0',sizeof(commend_line)*sizeof(char)); //初始化为 \0
    fgets(commend_line,NUM,stdin);//获取到的是c风格的字符串 '\0'结尾
    commend_line[strlen(commend_line)-1]='\0';//清除\n 
    
    //字符串分割,比如:"ls -a -l" ->  "ls" "-a" "-l"
    commend_args[0]=strtok(commend_line,SEP);
    int index=1;
    
    //给指令添加颜色
    if(strcmp(commend_args[0],"ls")==0)
    {
      commend_args[index++]=(char*)"--color=auto";
    }
    while(commend_args[index++]=strtok(NULL,SEP));
    
    //内建命令,因为是想改变父进程所处的工作目录,所以并不需要去创建子进程
    if(strcmp(commend_args[0],"cd")==0 && commend_args[1]!=NULL)
    {
      ChangeDir(commend_args[1]);
      continue;
    }
    
    //同理,我们想给父进程添加环境变量,以继承的方式去给子进程
    if(strcmp(commend_args[0],"export")==0 && commend_args[1]!=NULL)
    {
      //目前我们环境变量的信息在commmand_line里面,每次会被清空
      //此处需要自己保存一下环境变量的内容
      //binPutEnvInMyShell(commend_args[1]);
      strcpy(env_buffer,commend_args[1]);
      PutEnvInMyShell(env_buffer);
      continue;
    }
    //创建进程,执行
    pid_t id = fork();
    if(id==0)//子进程
    {
      //程序替换
      execvp(commend_args[0],commend_args);
      exit(-1);//执行到这里说明子进程替换失败
    }
    int status = 0;
    pid_t ret = waitpid(id,&status,0);//阻塞等待
    if(ret>0)
    {
    //打印进程终止信号和退出码
      printf("等待子进程成功,sig:%d,code:%d\n",status&0x7F,status&0xFF);
    }
  }         
  return 0;
}

我们自己实现的 shell 在子进程退出后都会打印子进程的退出码,我们可以根据这一点来区分当前使用的是 Linux 的 shell 还是我们自己实现的 shell

aqa 芭蕾 eqe 亏内,代表着开心代表着快乐,ok 了家人们

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

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

相关文章

【python】之常用类型(包括进制)之间的转换

个人主页&#xff1a;天寒雨落的博客_CSDN博客-C,CSDN竞赛,python领域博主 特别标注&#xff1a;仅为自己的学习记录笔记&#xff0c;方便复习和加深记忆&#xff0c;仅供借鉴参考&#xff01; 目录 一、字符和整数之间的转换 1.整数转字符 chr(x) 2.字符转整数 ord(x) 二、…

Git操作复习笔记

Git操作复习笔记一、git基础1.1 安装1.2 简单的命令1.2.1 基本工作流程1.2.3 git使用前配置1.2.4 git提交步骤1.2.5 恢复记录1.2.6 撤销二、git分支操作2.1 分支细分2.2 分支命令2.3 暂时保存更改三、github操作3.1 多人协作开发的流程3.2 创建远程仓库3.3 远程仓库克隆到本地仓…

【K8S系列】Kubernetes的网络模型

目录 一、k8s的三种网络 二、service网络 2.1 netfilter 2.2 iptables 2.3 clustip 一、k8s的三种网络 Node Network: 与外部网络接口 Service Network&#xff1a; ipvs规则当中的网络、路由提供调度 Pod Network&#xff1a; 节点当中pod的内部网络无法与外界通信 其中&…

【python初学者日记】selenium初体验——“秒杀商品”、“清空购物车”技能养成记(一)

【python初学者日记】selenium初体验——“秒杀商品”、“清空购物车”技能养成记&#xff08;一&#xff09;用python解决“清空购物车”、“秒杀商品”问题合集&#xff1a;1、Mac版在pycharm和终端上使用pip显示&#xff1a;pip: command not found Mac2、Mac版在pycharm中*…

毛球修剪器方案开发的工作原理和构成

本文介绍了毛球修剪器方案开发的工作原理&#xff1b;不管是羊毛衫、兔子衫还是普通纤维衫&#xff0c;时间一长都不可避免地会有很多毛球。它看起来脏又乱&#xff0c;穿起来特别不雅观。用除毛器剃毛球可以轻松去除毛衣的原始绒毛&#xff0c;而毛衣将失去其原有的保暖性。 原…

HTML登录页面

第一步:构建HTML框架 简介&#xff1a;本文用最通俗的语言&#xff0c;一步步教会大家CSS构建登录页面。 首先构建HTML框架&#xff0c;包含用户名&#xff0c;密码&#xff0c;记住密码&#xff0c;注册这几个功能。 如果大家HTML不牢固&#xff0c;请看我的这篇博客:https:/…

【数据结构】线性表之顺序表详解

&#x1f9d1;‍&#x1f4bb;作者&#xff1a; 情话0.0 &#x1f4dd;专栏&#xff1a;《数据结构》 &#x1f466;个人简介&#xff1a;一名双非研究生的编程菜鸟&#xff0c;在这里分享自己的编程学习笔记&#xff0c;欢迎大家的指正与点赞&#xff0c;谢谢&#xff01; 顺…

32、Java——迷你图书管理器(对象+JDBC)

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;乐趣国学的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;Java案例分…

SpringBoot交友APP项目实战(详细介绍+案例源码) - 10.网关配置

系列文章目录 1. 项目介绍及环境配置 2. 短信验证码登录 3. 用户信息 4. MongoDB 5. 推荐好友列表/MongoDB集群/动态发布与查看 6. 圈子动态/圈子互动 7. 即时通讯&#xff08;基于第三方API&#xff09; 8. 附近的人(百度地图APi) 9. 小视频 10.网关配置 文章目录系列文章目录…

【数据结构练习题——查找】

题目&#xff1a;给定如下关键字序列 &#xff08;36,23,51,6&#xff0c;58,48,39,8&#xff0c;88,76,63,17&#xff09; &#xff08;1&#xff09;按表中顺序建立一棵初始为空的二叉排序树&#xff0c;画出该二叉排序树。 &#xff08;2&#xff09;求上述二叉排序树中等…

ikun网站成名录: HTML 中的常用标签用法,从0到1创建一个ikun简介

常见标签(以下均省略了< >) 1.标题标签 h1~h6 2.段落&#xff0c;换行标签 由于html的特性&#xff0c;我们在语句中添加换行&#xff0c;多个空格都是没办法对我们的文本分段落的。如图 所以改用这个标签便可分段了&#xff1a; 用于我们文本可能需要手动换行&#x…

Hadoop高手之路2—Hadoop集群的基础设置

文章目录Hadoop集群的基础设置一、虚拟机软件的安装二、创建虚拟机&#xff0c;安装CentOS1.下载CentOS2.创建虚拟机3.编辑虚拟机设置4.安装centos7.9mini版本5.启动centos&#xff0c;并进行登录6. 退出root登录&#xff0c;用user1登录三、CentOS网络配置1. 查看本地windows主…

图像格式RGB-HSV-YUV

文章目录一、RGB色彩空间二、HSV 色彩空间三、YUV 色彩空间四、色彩空间的转换待更新中FPGA实现RGB与HSV的转换 一、RGB色彩空间 RGB 是最常用于显示器的色彩空间&#xff0c;R(red)是红色通道&#xff0c;G(green)是绿色&#xff0c;B(blue)是蓝色通道。这三种颜色以不同的量…

(附源码)计算机毕业设计SSM教师教学质量评价系统

&#xff08;附源码&#xff09;计算机毕业设计SSM教师教学质量评价系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术…

【数据结构与算法】手写HashMap的模拟实现

✨哈喽&#xff0c;进来的小伙伴们&#xff0c;你们好耶&#xff01;✨ &#x1f6f0;️&#x1f6f0;️系列专栏:【数据结构与算法】 ✈️✈️本篇内容:手写HashMap的模拟实现&#xff01; &#x1f680;&#x1f680;代码存放仓库gitee&#xff1a;Java数据结构代码存放&…

目标检测 YOLOv5 - Rockchip rknn模型的测试 包括精度,召回率,mAP等详细信息

目标检测 YOLOv5 - Rockchip rknn模型的测试 包括精度&#xff0c;召回率&#xff0c;mAP等详细信息 flyfish 该测试是使用了自定义128张图片的测试结果&#xff0c;如果采用官网的coco128图片数据会比下列数值更好看。 以下是对比结果&#xff0c;pt模型的测试结果和rknn模型…

进程互斥的实现方法

文章目录进程互斥的软件实现方法单标志法双标志先检查法双标志后检查法Peterson算法img硬件实现进程互斥中断屏蔽方法TestAndSet指令Swap指令进程互斥的软件实现方法 软件实现方法的思想&#xff1a;在进入区设置并检查一些标志 来标明是否有进程在临界区中,若已有进程在临界区…

MyBatis

最近新开了个项目&#xff0c;记录第一次新开项目做得一些步骤&#xff0c;整合mybatis就是重要的一步&#xff0c;这里我演示了依赖的添加&#xff0c;逆向文件的生成。 1.整合mybatis 1.1基础配置 先添加依赖&#xff0c;再增加配置文件 dependencies <!--Spring Boot …

Java 开发工具 Eclipse

目录 一、Eclipse 概述 二、Eclipse 安装与汉化 三、创建 Java 项目 四、创建 Java 类 五、运行 Java 程序 六、Eclipse 调试程序 &#xff08;方法一&#xff09; 七、Eclipse 调试程序 &#xff08;方法二&#xff09; 工欲善其事&#xff0c;必先利…

Linux文件查找find

目录 前言 查找命令 命令演示 1.which&#xff1a;命令查找 2.locate命令 3.find命令&#xff08;主要使用这个命令进行查找文件&#xff09; 1&#xff09;语法 2&#xff09;find的用法介绍 按文件名查找 手动写入指定大小数据到文件内&#xff0c;介绍一下dd命令。…