【云原生】Linux进程控制(进程程序替换)

✨个人主页: Yohifo
🎉所属专栏: Linux学习之旅
🎊每篇一句: 图片来源
🎃操作环境: CentOS 7.6 阿里云远程服务器

  • Good judgment comes from experience, and a lot of that comes from bad judgment.
    • 好的判断力来自经验,其中很多来自糟糕的判断力。

上帝的指纹


文章目录

  • 🌇前言
  • 🏙️正文
    • 1、为何要进行程序替换?
    • 2、七大替换函数
      • 2.1、函数1 execl
      • 2.2、函数2 execv
      • 2.3、函数3 execlp
      • 2.4、函数4 execvp
      • 2.5、函数5 execle
      • 2.6、函数6 execve
      • 2.7、函数7 execvpe
    • 3、补充
      • 3.1、函数名记忆
      • 3.2、替换现象
  • 🌆总结


🌇前言

子进程 在被创建后,共享的是 父进程 的代码,如果想实现自己的逻辑就需要再额外编写代码,为了能让 子进程 执行其他任务,可以把当前 子进程 的程序替换为目标程序,此时需要用到 Linux 进程程序替换相关知识

子进程 替换为其他程序后,无法再执行原有程序,但 进程 始终为同一个

火爆全网的 ChatGTP 能否替换 “人类” ?

机器人


🏙️正文

1、为何要进行程序替换?

在学习相关函数前,先要弄清楚为何要进行程序替换?

  • 将运行中的程序看作一个 任务处理平台
  • 由我们发出指令,交给 任务处理平台 去完成
  • 因为每次发出的指令都可能不相同,所以 任务处理平台 中的代码不能固化
  • 为了解决这个问题,任务处理平台 可以通过创建子进程,让子进程完成对应指令
  • 子进程实现对应指令依赖于程序替换

总结: 程序替换的目的是让子进程帮我们执行特定任务

就像汽车拥有各种各样的轮胎,如越野时需要换上路面兼容性更好、更耐造的越野胎;日常家用时,舒适性更好、胎噪更小的轮胎显然就更合适了,针对不同的使用场景替换不同的轮胎,程序替换时也是这么个意思,执行特定任务

轮胎分类
shell 外壳中的 bash 就是一个任务处理平台,当我们发出指令,如 lspwdtouch 等指令时后,bash 会创建子进程,将其替换为对应的指令程序并执行任务,就能实现各种指令

bash

进程程序替换图解

  • Linux 中的指令都是用 C语言 写的可执行程序,所以可以进行替换
  • bash 运行后,输入 指令 本质上就是在进行程序替换

进程替换

关于简易版 bash 的实现方法,将在下篇文章中揭晓


2、七大替换函数

进程程序替换函数共有七个,其中六个都是在调用函数6,因此函数6 execve 才是真正的系统级接口

函数介绍

各种替换函数间的关系

函数调用关系
这些函数都属于 exec 替换家族,所以它们的返回值都一样

注意: 这七个函数只有在程序替换失败后才会有返回值,返回 -1,程序替换成功后不返回

程序都已经替换成功,后续代码也都将被替换,所以成功后的返回值也就没意义了

2.1、函数1 execl

首先是最简单的替换函数 execl

#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序的路径,如 /usr/bin/ls
  • 参数2:待替换程序的名称,如 ls
  • 参数3~N:待替换程序的选项,如 -a -l等,最后一个参数为 NULL,表示选项传递结束
  • ... 表示可变参数列表,可以传递多个参数

图解

注意: 参数选项传递结束或不传递参数,都要在最后加上 NULL,类似于字符串的 '\0'

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

int main()
{
  //execl 函数
  printf("程序替换前,you can see me\n");
  int ret = execl("/usr/bin/ls", "ls", "-a", "-l", NULL);

  //程序替换多发生于子进程,也可以通过子进程的退出码来判断是否替换成功
  if(ret == -1)
    printf("程序替换失败!\n");

  printf("程序替换后,you can see me again?\n");
  return 0;
}

结果
可以看出,函数 execl 中的 命令+选项+NULL 是以 链式 的方式进行传递的

链式传递

2.2、函数2 execv

替换函数 execv 是以顺序表 vector 的方式传递 参数2~N

#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序的路径,如 /usr/bin/ls
  • 参数2:待替换程序名及其命名构成的 指针数组,相当于一张表

图解
注意: 虽然 execv 只需传递两个参数,但在创建 argv 表时,最后一个元素仍然要为 NULL

#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  //execv 函数
  pid_t id = fork();
  if(id == 0)
  {
    printf("子进程创建成功 PID:%d   PPID:%d\n", getpid(), getppid());
    char* const argv[] = 
    {
      "ls",
      "-a",
      "-l",
      NULL
    };	//argv 表,实际为指针数组

    execv("/usr/bin/ls", argv);

    printf("程序替换失败\n");
    exit(-1); //如果子进程有此退出码,说明替换失败
  }

  int status = 0;
  waitpid(id, &status, 0); //父进程阻塞等待
  if(WEXITSTATUS(status) != 255)
  {
    printf("子进程替换成功,程序正常运行 exit_code:%d\n", WEXITSTATUS(status));
  }
  else
  {
    printf("子进程替换失败,异常终止 exit_code:%d\n", WEXITSTATUS(status));
  }

  return 0;
}

正常运行的情况

正常运行
错误运行的情况,改变 path

execv("/usr/bin", argv); //故意提供错误路径

错误运行
execl 函数不同,execv 是以表的形式进行参数传递的

顺序表传递

2.3、函数3 execlp

可能有的人觉得写 path 路径很麻烦,还有可能会写错,那么能否换成 自动挡 替换呢?

答案是可以的,execlp 函数在进行程序替换时,可以不用写 path 路径

#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序名,如 lspwdclear
  • 参数2~N:可变参数列表,为命令的选项

execlp 就像是 execl 的升级版,可以自动到 PATH 变量中查找程序

注意: 只能在环境变量表中的 PATH 变量中搜索,如果待程序路径没有在 PATH 变量中,是无法进行替换的

#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  //execlp 函数
  pid_t id = fork();
  if(id == 0)
  {
    printf("you can see me\n");

    execlp("ls", "ls", "-a", "-l", NULL); //程序替换

    printf("you can see me again?");
    exit(-1);
  }

  int status = 0;
  waitpid(id, &status, 0);  //等待阻塞
  if(WEXITSTATUS(status) != 255)
    printf("子进程替换成功 exit_code:%d\n", WEXITSTATUS(status));
  else
    printf("子进程替换失败 exit_code:%d\n", WEXITSTATUS(status));

  return 0;
}

结果
使用 execlp 替换程序更加方便,只要待替换程序路径位于 PATH 中,就不会替换失败

2.4、函数4 execvp

execv 加个 p 也能实现自动查询替换,即 execvp

#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序名,需要位于 PATH
  • 参数2:待替换程序名及其命名构成的 指针数组
#include <stdio.h>
#include <stdlib.h> //exit 函数头文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
  //execvp 函数
  pid_t id = fork();
  if(id == 0)
  {
    printf("子进程创建成功 PID:%d   PPID:%d\n", getpid(), getppid());
    char* const argv[] = 
    {
      "ls",
      "-a",
      "-l",
      NULL
    };

    execvp("ls", argv);

    printf("程序替换失败\n");
    exit(-1); //如果子进程有此退出码,说明替换失败
  }

  int status = 0;
  waitpid(id, &status, 0); //父进程阻塞等待
  if(WEXITSTATUS(status) != 255)
  {
    printf("子进程替换成功,程序正常运行 exit_code:%d\n", WEXITSTATUS(status));
  }
  else
  {
    printf("子进程替换失败,异常终止 exit_code:%d\n", WEXITSTATUS(status));
  }

  return 0;
}

结果
假若参数1 file 的路径不在 PATH 中,程序会替换错误

execvp("a.out", argv);

错误结果
如果想替换自己写的程序,那么只需要将路径添加至 PATH 中即可

2.5、函数5 execle

e 表示 env 环境变量表,可以将自定义或当前程序中的环境变量表传给待替换程序

#include <unistd.h>

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

函数解读

  • 最后一个参数:替换成功后,待替换程序的环境变量表,可以自定义
char* const myenv[] = {"myval=100", NULL};  //自定义环境变量表

execle("./other/CPP", NULL, myenv); //程序替换

替换为自己写的程序 CPP

//当前源文件为 test.cc 即 C++源文件
// .xx 后缀也可以表示 C++源文件
#include <iostream>

using namespace std;

extern char** environ;	//声明环境变量表

int main()
{
  int pos = 0;
  //只打印5条
  while(environ[pos] && pos < 5)
  {
    cout << environ[pos++] << endl;
  }
  return 0;
}

按照预期替换程序并传入自定义环境变量表后

结果
可以看到,程序 CPP 中的环境变量表变成了自定义环境变量,即只有一个环境变量 myval=100

改变 execle 最后一个参数,传入默认环境变量表

 extern char** environ;

 execle("./other/CPP", NULL, environ); //继承环境变量表

继承
结论: 如果主动传入环境变量后,待替换程序中的原环境变量表将被覆盖

现在可以理解为什么在 bash 中创建程序并运行,程序能继承 bash 中的环境变量表了

  • bash 下执行程序,等价于在 bash 下替换子进程为指定程序,并将 bash 中的环境变量表 environ 传递给指定程序使用
  • 其他没有带 e 的替换函数,默认传递当前程序中的环境变量表

图解

2.6、函数6 execve

execve 是系统真正提供的程序替换函数,其他替换函数都是在调用 execve

比如

  • execl 相当于将链式信息转化为 argv 表,供 execve 参数2使用
  • execlp 相当于在 PATH 中找到目标路径信息后,传给 execve 参数1使用
  • execleenvp 最终也是传给 execve 中的参数3
#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序的路径
  • 参数2:待替换程序名及其参数组成的 argv
  • 参数3:传递给待替换程序的环境变量表

替换 ls -a -l 程序

extern char** environ;

execve("/usr/bin/ls", argv, environ);

结果

替换为自定义程序 CPP

extern char** environ;

execve("./other/CPP", argv, environ);

结果
替换函数除了能替换为 C++ 编写的程序外,还能替换为其他语言编写的程序,如 JavaPythonPHP等等,虽然它们在语法上各不相同,但在 OS 看来都属于 可执行程序,数据位于 代码段数据段,直接替换即可

系统级接口是不分语言的,因为不论什么语言最终都需要调用系统级接口,比如文件流操作中的 openclosewrite 等函数,无论什么语言的文件流操作函数都需要调用它们


2.7、函数7 execvpe

execvp 的再一层封装,使用方法与 execvp 一致,不过最后一个参数可以传递环境变量表

#include <unistd.h>

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

函数解读

  • 返回值:替换失败返回 -1
  • 参数1:待替换程序名,需要位于 PATH
  • 参数2:待替换程序名及其命名构成的 指针数组
  • 参数3:传递给待替换程序的环境变量表
extern char** environ;

execvpe("ls", argv, environ);

结果


3、补充

最后再补充一些关于程序替换的知识

3.1、函数名记忆

七大替换函数按 程序名+选项 传递方式可以分为两组

  • 列表:execlexeclpexecle
  • 顺序:execvexecvpexecveexecvpe

可以看出,列表传递中必有 l,顺序传递则必有 v,函数名中字符的含义如下

  • exec 该函数隶属于程序替换家族
  • llist,列表传递
  • vvector,顺序传递
  • p 表示 PATH,根据程序名自动在 PATH 中查找
  • e 则是 environ,是否手动传递环境变量表

3.2、替换现象

子进程程序替换后,并不会创建新进程,而是对原有程序中的 数据代码 进行修改,可以通过替换以下程序观察

#include <iostream>
#include <unistd.h>

using namespace std;

int main()
{
  while(1)
  {
    cout << "程序替换成功";
    cout << " PID:" << getpid() << "   PPID:" << getppid() << endl;
    sleep(1);
  }

  return 0;
}

子进程
可以看到在进行程序替换后,子进程和待替换程序为同一个进程

  • 这就表明程序替换并不是进程替换
  • 因为是同一个进程,所以对父进程没有任何影响,体现了进程间的独立性

在子进程执行程序替换前,子进程和父进程共享一份只读区域的数据,但因为发生了程序替换,触发 写时拷贝 机制,令子进程读取另一块区域的数据

  • 写时拷贝 在只读数据区也能触发,因为不能影响到父进程

替换


🌆总结

以上就是本篇关于 Linux 进程程序替换的相关内容了,在本文中,我们知道了进行程序替换的目的,学习使用了程序替换相关的七大函数,最后还观察了程序替换后的神奇现象,在学完这些知识后,我们就可以实现一个简单的 bash,体验一下在自己程序中输入指令操控 Linux 的奇妙体验

如果你觉得本文写的还不错的话,期待留下一个小小的赞👍,你的支持是我分享的最大动力!

如果本文有不足或错误的地方,随时欢迎指出,我会在第一时间改正


星辰大海

相关文章推荐

Linux进程控制【创建、终止、等待】

===============

Linux进程学习【进程地址】

Linux进程学习【环境变量】

Linux进程学习【进程状态】

Linux进程学习【基本认知】

感谢支持

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

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

相关文章

keepalived配置使用

keepalived安装tar -zxvf ***yum install -y gcc gcc-c wget popt-devel openssl openssl-devel yum install -y libnl libnl-devel libnl3 libnl3-devel yum install -y libnfnetlink-devel ./configure --sysconf/etc make make installmaster1 编辑keepalived的配置文件keep…

【C++】科普:C++中的浮点数怎么在计算机中表示?

这里我们以8.25这个数为例说明计算机时如何存取float类型的数据的&#xff1a; float a 8.25;引言 1. 所占位数 首先&#xff0c;明确一个概念&#xff0c;float类型的数据在常规计算机中通常占4个字节&#xff0c;也就是32位。其内存分布如图&#xff1a; 位字段说明所占位…

【pytorch源码剖析系列】优化器

写在前言&#xff1a;pyotrch优化器从源码的角度带你理解优化器的由来&#xff0c;实现&#xff0c;作用。pytorch的优化器&#xff1a;管理并更新模型中可学习参数的值&#xff0c;使得模型输出更接近真是标签。导数&#xff1a;函数在指定坐标轴上的变化率方向导数&#xff1…

ChatGPT来了你慌了吗?

文章目录一、ChatGPT是什么&#xff1f;一、ChatGPT到底多强大&#xff1f;三、各平台集成了ChatGPT插件&#xff1a;四、ChatGPT能否取代程序员&#xff1f;一、ChatGPT是什么&#xff1f; ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&…

快速上手vue elementUI好看的登录界面

这是一个非常非常适合新手的vue登录界面&#xff0c;总体来说美观大气&#xff0c;axios那部分没有发&#xff0c;有需要的大家可以自己进行二次开发&#xff0c;继续编写。 用到了技术栈有 vue/cli 5.07 element-ui 2.15.9 适合入门级新手&#xff0c;展示下页面 emmm验证码…

【Spring Cloud Alibaba】2.服务注册与发现(Nacos安装)

文章目录环境要求简介安装Nacos源码安装Docker安装数据库配置访问服务我们要搭建一个Spring Cloud Alibaba项目就绕不开Nacos&#xff0c;阿里巴巴提供的Nacos组件&#xff0c;可以提供服务注册与发现和分布式配置服务&#xff0c;拥有着淘宝双十一十几年的流量经验&#xff0c…

关于docker mysql 请求速度慢的问题

一&#xff0c;问题描述&#xff1a; 请求mysql 数据库&#xff0c;请求速度很慢&#xff0c;需要六七秒&#xff0c;数据量也不大。使用的是docker 容器启动的mysql 二&#xff0c;问题原因&#xff1a; 网上说的是因为MySQL的dns导致&#xff0c;我实测也是有效果的&#xf…

Linux:主机USB设备驱动简析

文章目录1. 前言2. 分析背景3. USB 总线硬件拓扑4. USB 协议栈概览4.1 Linux USB 子系统概览4.2 USB外设(如U盘)固件基础5. Linux USB 子系统初始化6. Linux USB 主机控制器(HCD) 驱动6.1 USB 主机控制器驱动初始化6.2 USB 主机控制器设备对象注册和驱动加载7. Linux USB 设备驱…

【亲测搭建成功】模拟无网络情况下安装K8S集群和相关组件

目录标题 前言准备工作:k8s集群:先构思网络拓扑图划分网络资源服务器开始搭建服务器操作系统初始化1.关闭防火墙2. 关闭selinux3. 修改网卡配置5.系统模块配置nacos 高可用mysql双主+双从rockemq 集群nginx高可用(双主)Redis 双主、双从minio分布式文件存储前言 最近项目上…

vulnhub Noob渗透笔记

靶机下载地址:https://www.vulnhub.com/entry/noob-1,746/ kali ip 信息收集 依旧我们先使用nmap扫描确定一下靶机ip nmap -sP 192.168.20.0/24发现靶机ip 扫描开放端口 nmap -A -p 1-65535 192.168.20.129 开放21 80 55077端口 先尝试使用匿名账号登录ftp,账户anonym…

linux系统运维面试题大全(137道题)

linux系统运维面试题大全 1、 如何看当前Linux系统有几颗物理CPU和每颗CPU的核数&#xff1f; 查看物理cup&#xff1a; cat /proc/cpuinfo|grep -c ‘physical id’ 查看每颗cup核数 cat /proc/cpuinfo|grep -c ‘processor’ 2、查看系统负载有两个常用的命令&#xff0c;…

STM32 ADC+定时器+DMA+FFT

本次实现的功能为单片机DAC输出一个正弦波&#xff0c;然后ADC定时采样用DMA输出&#xff0c;最后对DAC输出的波形进行FFT。单片机STM32F103ZET6内部时钟一、配置ADCADC端口为PA1&#xff0c;采用DMA输出&#xff0c;定时器3触发定时器时钟64M&#xff0c;分频后为102.4KHzADC采…

Scrapy的callback进入不了回调方法

一、前言 有的时候&#xff0c;Scrapy的callback方法直接被略过了&#xff0c;不去执行其中的回调方法&#xff0c;可能排查好久都排查不出来&#xff0c;我来教大家集中解决方法。 yield Request(urlurl, callbackself.parse_detail, cb_kwargs{item: item})二、解决方法 1…

基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建

基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建 文章目录基于QEMU-aarch64学习UEFI(EDK2)-1环境搭建一、环境搭建1、虚拟机Ubuntu系统安装2、docker镜像导入3、下载EDK2源码4、容器创建和代码编译4.1 容器创建4.2 代码编译5、运行QEMU_EFI.fd6、VSCODE配置7、日常工作8、不同项目的…

数据结构——二叉树与堆

作者&#xff1a;几冬雪来 时间&#xff1a; 内容&#xff1a;二叉树与堆内容讲解 目录 前言&#xff1a; 1.完全二叉树的存储&#xff1a; 2.堆的实现&#xff1a; 1.创建文件&#xff1a; 2.定义结构体&#xff1a; 3.初始化结构体&#xff1a; 4.扩容空间与扩容…

学习黑客十余年,如何成为一名高级的安全工程师?

1. 前言 说实话&#xff0c;一直到现在&#xff0c;我都认为绝大多数看我这篇文章的读者最后终究会放弃&#xff0c;原因很简单&#xff0c;自学终究是一种适合于极少数人的学习方法&#xff0c;而且非常非常慢&#xff0c;在这个过程中的变数过大&#xff0c;稍有不慎&#…

win32api之文件系统管理(七)

什么是文件系统 文件系统是一种用于管理计算机存储设备上文件和目录的机制。文件系统为文件和目录分配磁盘空间&#xff0c;管理文件和目录的存储和检索&#xff0c;以及提供对它们的访问和共享&#xff0c;以下是常见的两种文件系统&#xff1a; NTFSFAT32磁盘分区容量2T32G…

C/C++之while(do-while)详细讲解

目录 while循环有两个重要组成部分&#xff1a; while 是一个预测试循环 无限循环 do-while 循环 while循环有两个重要组成部分&#xff1a; 进行 true 值或 false 值判断的表达式&#xff1b;只要表达式为 true 就重复执行的语句或块&#xff1b;图 1 显示了 while 循环的…

GIS开源库GEOS库学习教程(一):编译及示例代码

1、介绍 GEOS库是一个集合形状的拓扑关系操作实用库&#xff0c;简单得说&#xff0c;就是判断两个几何形状之间关系和对两个几何形状进行操作以形成新的几何形状的库。GEOS是仿照JTS库做的&#xff0c;是JTS的C实现。下面是JTS Topology Suite (JTS) 拓扑运算函数库的介绍&…

Android 自定义View 之 Mac地址输入框

Mac地址输入框前言正文一、什么是View?二、什么是自定义View三、自定义View① 构造方法② XML样式③ 测量④ 绘制1. 绘制方框2. 绘制文字⑤ 输入1. 键盘布局2. 键盘接口3. 键盘弹窗4. 显示键盘5. 处理输入四、使用自定义View五、源码前言 在日常工作开发中&#xff0c;我们时长…