【Linux进程】进程控制(下) {进程程序替换:程序替换的工作原理,程序替换函数exec*,简单的命令行解释器}

四、进程程序替换

在这里插入图片描述

  • 之前用fork创建子进程后,父子进程执行同一个程序的不同代码段。

  • 如何使子进程执行另一个不同的程序呢?子进程需要进行程序替换!

  • 程序替换,就是通过特定的接口,将磁盘上一个全新的程序(包括代码和数据)加载到调用进程的地址空间中。


4.1 程序替换的原理

在这里插入图片描述

在进行程序替换时,操作系统会将新程序的代码和数据加载到调用进程的地址空间中。这个过程通常包括以下几个步骤:

  1. 加载数据:操作系统会将新程序的代码和数据从磁盘或其他存储介质中读取到内存中。这些代码和数据会被加载到物理内存中的合适位置。

  2. 调整进程地址空间(重定位):由于新的程序可能与原有程序的地址空间不同,因此需要进行地址重定位。操作系统会根据新程序的要求,将程序中的地址引用进行调整,使其指向正确的物理内存位置。

  3. 更新页表:操作系统会更新进程的页表,以映射新程序的代码和数据所在的物理内存页。这样,进程就可以通过虚拟内存地址访问到正确的物理内存。

  4. 清理旧程序:操作系统会释放原有程序占用的物理内存页,以便为新程序腾出空间。这些旧的物理内存页会被标记为可重用,以供其他进程使用。

总的来说,程序替换是通过加载新程序的代码和数据到物理内存中,并进行地址重定位和页表更新来完成的。这样,进程就可以执行新的程序了。


4.2 程序替换函数

  • 子进程往往要调用exec*函数进行程序替换以执行另一个程序,exec*函数是加载器的底层调用接口。
  • 当进程调用exec*函数时,该进程用户空间的代码和数据完全被新程序替换,从新程序的启动例程开始执行。
  • 调用exec并不创建新进程,所以调用exec前后该进程的pid并未改变。

下面是exec*系统调用:

在这里插入图片描述


4.2.1 execl函数

参数:

  1. path:待替换程序的所在路径+文件名
  2. arg, …:命令行参数,以字符串的形式一个个传入,最后以NULL结尾(标识参数传递完毕)。

返回值:

  1. exec函数只有发生错误失败时才会返回,返回值是-1。

  2. exec函数一旦调用成功,后续的所有代码都不会执行,也根本不需要有返回值。

提示:

  1. execl拆解速记:exec(execute) - l(list)
  2. 命令行参数表和环境变量表都是以NULL结尾,用于表示结束。

测试代码:

#include <iostream>    
#include <unistd.h>    
using namespace std;    int main(){    cout << "当前进程的开始代码!" << endl;    execl("/usr/bin/ls", "ls", "-a", "-l", "--color=auto", NULL);                                                                  cout << "当前进程的结束代码!" << endl; //程序被替换,所以不会被打印   
}    

测试结果:

在这里插入图片描述


4.2.2 execv函数

与execl比较,只是第二个参数argv不同。

argv:命令行参数表(字符指针数组),数组元素同样要以NULL结尾。

其他特性和execl一模一样

execv拆解速记:exec(execute) - v(vector)

测试代码:

#include <iostream>          
#include <unistd.h>    
#include <sys/wait.h>    
using namespace std;    int main(){    pid_t id = fork();                                           if(id == 0)                                                        {                                                              //子进程执行流        cout << "I'm child process! child_pid:" << getpid() << endl;    char *const argv[] = {(char*)"ls", (char*)"-a", (char*)"-l", (char*)"--color=auto", NULL};    execv("/usr/bin/ls", argv);                                                                   }                                else if(id > 0)         {                                //父进程执行流                                                  int status = 0;                                                                               pid_t ret = waitpid(id, &status, 0);    if(ret > 0)                             {              if(WIFEXITED(status))                    {                                                              cout << "子进程正常退出!child_pid:" << ret;    cout << " exit_code:" << WEXITSTATUS(status) << endl;    }else{cout << "子进程崩溃!child_pid:" << ret;cout << " exit_signal:" << (status & 0x7F) << endl;}                                                                                                                          }else if(ret == -1){cout << "等待子进程失败!" << endl;}}else{perror("子进程创建失败!");return 1;}
}

测试结果:

在这里插入图片描述


4.2.3 execlp函数

与execl比较,只是第一个参数file不同。

file:待替换的程序名,该程序的所在路径必须在PATH环境变量中。

其他特性和execl一模一样

execlp拆解速记:exec(execute) - l(list) - p(PATH)

测试代码:

//......  if(id == 0)    {    //子进程执行流    cout << "I'm child process! child_pid:" << getpid() << endl;    execlp("ls", "ls", "-a", "-l", NULL);    
//...... 

运行结果:

在这里插入图片描述


当然也可以执行我们自己编写的代码

可以使用绝对路径或相对路径:

  1. 绝对路径:execv("/home/zty/code/Linux/20230721/test", argv);
  2. 相对路径:execv("./test", argv);

测试代码:这里测试的是之前写过的一个接收命令行选项参数的简单程序

测试结果:

在这里插入图片描述

补充内容:makefile一次性构建多个可执行程序

.PHONY:all    
all:myproc test    myproc:myproc.cc    
g++ $^ -o $@ -std=c++11    
test:test.cc    
g++ $^ -o $@ -std=c++11    .PHONY:clean    
clean:    
rm -f myproc      

甚至还可以执行其他语言编写的程序

shell脚本

#! /usr/bin/bash echo "hello shell!"

运行命令:bash test.sh

将进程替换为shell程序:execlp("bash", "bash", "test.sh", NULL);

python脚本

#! /usr/bin/python3.6print("hello Python/n")

运行命令:python test.py

将进程替换为Python程序:execlp("python", "python", "test.py", NULL);

如果python脚本文件具有可执行权限:execlp("./test.py", "test.py", NULL);

注意:

  1. shell,Python,Java等编程语言拥有自己的解释器,通过解释器可以直接运行所编写的程序,无需编译生成可执行程序。
  2. 可以给脚本文件加上可执行权限,然后直接./test.py运行程序。实际仍然是通过解释器执行程序的。

4.2.4 execle函数

与execl相比,新增了第三个参数envp

envp:环境变量表(字符指针数组),数组元素同样要以NULL结尾。

其他特性和execl一模一样

execle拆解速记:exec(execute) - l(list) - e(environment)

测试代码:

//......  if(id == 0)    {    //子进程执行流    cout << "I'm child process! child_pid:" << getpid() << endl;    char *const _env[] = {"MYENV=122", NULL}; //设置MYENV环境变量execle("./test", "test", "-e", "NULL", _env);//test -e选项打印MYENV环境变量
//...... 

测试结果:

在这里插入图片描述

提示:环境变量具有全局属性,是因为可以通过类似于execle的系统调用,将父进程环境变量表传递给子进程。


4.2.5 execve函数

在这里插入图片描述

execve是一个系统调用函数,用于在Linux系统中执行一个新的程序。它的参数解释如下:

  1. const char *filename:要执行的程序的路径。可以是绝对路径,也可以是相对路径。

  2. char *const argv[]:一个字符串数组,用于传递给新程序的命令行参数。数组的最后一个元素必须为NULL,表示参数列表的结束。

  3. char *const envp[]:一个字符串数组,用于传递给新程序的环境变量。数组的最后一个元素必须为NULL,表示环境变量列表的结束。如果envp为NULL,则新程序将继承当前进程的环境变量。

execve函数的返回值是一个整数,如果执行成功,它不会返回,而是直接在当前进程中加载并执行新程序。如果发生错误,返回值为-1,并设置errno来指示具体的错误类型。

注意:

  1. execve函数会替换当前进程的代码和数据,将其替换为新程序的代码和数据。因此,execve函数之后的代码将不会被执行。
  2. 如果希在执行新程序后继续执行其他操作,可以使用fork函数创建一个子进程,在子进程中调用execve函数,而在父进程中继续执行其他操作。

execve是真正的系统调用,而上面的6个接口实际上是系统提供的基本封装。 他们会将接收到的参数进行合并处理,最后底层还是会调用execve:

在这里插入图片描述

在这里插入图片描述
总结exec*函数的命名方式:

  1. +l/v:命令行参数以可变参数列表或者指针数组传入
  2. +p:是否在环境变量PATH中查找程序路径
  3. +e:是否自己维护环境变量表

4.3 简单的命令行解释器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>char cmd_line[1024]; //用于接收存储整条命令
char* cmd_param[32]; //将整条命令拆解成一个个参数
char env_buffer[64]; //环境变量缓冲区//shell运行原理:父进程接收并解析命令,创建子进程执行命令,父进程等待。
int main(){//0.命令行解释器是常驻内存进程,不退出while(1){//1.打印提示信息:[root@localhost myshell]#printf("[root@localhost myshell]# ");fflush(stdout); //[1]//sleep(1);//2.获取用户输入(包括指令和选项):"ls -a -l -i"memset(cmd_line, '\0', sizeof(cmd_line));    if(fgets(cmd_line, sizeof(cmd_line), stdin) == NULL) continue; //[2]if(strcmp(cmd_line, "\n") == 0) continue; //[2]cmd_line[strlen(cmd_line)-1] = '\0'; //[3]//printf("%s\n", cmd_line);//3.命令行字符串解析:"ls -a -l" --> "ls" "-a" "-l"cmd_param[0] = strtok(cmd_line, " "); //[4]int i = 1;while(cmd_param[i++] = strtok(NULL, " "));//for(int i = 0; cmd_param[i]; ++i)//{//  printf("%s ",cmd_param[i]);//}//printf("\n");//4.内置命令:让父进程(shell)自己执行的指令,又叫内建命令//内建命令本身就是shell中的一个函数调用if(strcmp(cmd_param[0], "cd") == 0) //cd指令切换父进程(shell)工作目录{if(cmd_param[1] != NULL)chdir(cmd_param[1]); //[5]continue;}if(strcmp(cmd_param[0], "export") == 0) //export指令导出环境变量,使其可被子进程继承{    if(cmd_param[1] != NULL)    {    strcpy(env_buffer, cmd_param[1]); //[6]putenv(env_buffer);  //[7] }    continue;    }    //5.创建子进程执行命令:int id = fork();if(id == 0){printf("I'm child process! pid:%d ppid:%d\n", getpid(), getppid());execvp(cmd_param[0], cmd_param);exit(1);}//6.父进程(shell)等待子进程,获取退出状态,回收资源int status = 0;int ret = waitpid(-1, &status, 0); //阻塞等待if(ret > 0){if(WIFEXITED(status)){//正常退出返回退出码printf("normal exit! child_pid:%d exit_code:%d\n", ret, WEXITSTATUS(status));}else{//异常退出返回退出信号printf("abnormal exit! child_pid:%d exit_signal:%d\n", ret, status&0x7F);}}else if(ret < 0){printf("Waiting failed!\n");}}
}

设计流程:

  1. 命令行解释器是常驻内存进程,不退出
  2. 打印提示信息:[root@localhost myshell]#
  3. 获取用户输入(包括指令和选项):“ls -a -l -i”
  4. 命令行字符串解析:“ls -a -l” --> “ls” “-a” “-l”
  5. 内置命令:让父进程(shell)自己执行的指令,又叫内建命令,内建命令本身就是shell中的一个函数调用
  6. 创建子进程执行命令
  7. 父进程(shell)等待子进程,获取退出状态,回收资源

解释:

  • [1] 由于打印的提示信息不带’\n’,所以缓冲区中的数据不会自动刷新到显示器,需要手动fflush(stdout)强制刷新,使其立马显示在屏幕上。
  • [2] 如果没有获取到任何字符或者获取失败直接continue;需要注意的是’\n’也会被读取,如果只读取到’\n’也要continue;
  • [3] 需要将获取到的最后一个换行符替换为’\0’,否则会被当做命令的一部分处理。
  • [4] C库函数strtok的用法:C 库函数 – strtok() | 菜鸟教程 (runoob.com)
  • [5] strtok工作原理:将指定的分隔符替换为’\0’,将原字符串分割。依次返回子串的首字符地址。
  • [6] Linux系统调用chdir:
    • 功能:用于改变当前进程的工作目录;这意味着后续的文件操作(如打开文件、读写文件)将在新的当前目录下进行。
    • 头文件:<unistd.h>
    • 参数:切换的路径
    • 返回值:成功返回0,失败返回-1
  • [7] cmd_param保存的是cmd_line中各命令行参数的首字符地址。memset会将cmd_line清空,导致环境变量丢失。因此需要将环境变量拷贝到缓冲区。
  • [8] Linux系统调用putenv:
    • 功能:用于设置环境变量
    • 头文件:<stdlib.h>
    • 参数:环境变量键值对字符串
    • 返回值:设置成功返回0,失败返回非0值

提示:

  1. shell执行的命令通常有两种:
    • 外部命令:第三方提供的在磁盘中有具体二进制文件的可执行程序(由子进程执行),如:ls,ps,pwd,我们编写的程序
    • 内置命令:shell内部自己实现的方法,由父进程(shell)自己执行,这些命令就是要影响shell本身,如:cd,export
  2. 进程的环境变量会被子进程继承,并且子进程进行程序替换并不会替换环境变量相关的内容
  3. shell的环境变量是从哪里来的?
    • 最初的环境变量是保存在配置文件(shell脚本)中的。shell启动的时候,通过读取配置文件,获得起始环境变量。

为什么要程序替换?

和应用场景有关,有时候我们必须让子进程执行新的程序。

为什么要创建子进程执行程序?

为了不影响父进程,如上面的shell程序,我们想让父进程聚焦在读取命令,解析命令,指派进程执行程序的功能上!

如果不创建子进程,我们就只能替换父进程程序了

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

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

相关文章

❤ Win7 电脑的使用

❤ Win7 电脑的使用 日常使用 1、 电脑显示文件的后缀名 开发使用 1、win7启动Vue项目 新版本的node.js已经不支持win7操作系统&#xff0c;其最多只能用node.js的v13.14.0。所以安装vue/cli时不能安装最新版&#xff0c;得装指定版本 下载安装node.js 因为Win7系统最多…

Python Web开发技巧VII

目录 装饰器inject_serializer 装饰器atomic rebase git 清理add的数据 查看git的当前工作目录 makemigrations文件名称 action(detailTrue, methods["GET"]) 如何只取序列化器的一个字段进行返回 Response和JsonResponse有什么区别 序列化器填表和单字段如…

数学建模-蒙特卡洛模拟

%% 蒙特卡罗用于模拟三门问题 clear;clc %% &#xff08;1&#xff09;预备知识 % randi([a,b],m,n)函数可在指定区间[a,b]内随机取出大小为m*n的整数矩阵 randi([1,5],5,8) %在区间[1,5]内随机取出大小为5*8的整数矩阵 % 2 5 4 5 3 1 4 2 %…

(无人机方向)ros小白之键盘控制无人机(终端方式)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一&#xff1a;配置pycharm的ros开发环境二&#xff1a;核心代码讲解三 效果演示XTDrone 四 完整代码 前言 ubuntu 18.04 pycharm ros melodic 做一个在终端中…

微信小程序——同一控件的点击与长按事件共存的解决方案

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

植物一区HR | 植物生理组+转录组:揭示豆科植物响应干旱胁迫机制

PlantArray 植物高通量生理学表型监测系统 是一套以植物生理学为基础的高精度&#xff0c;高通量&#xff0c;自动化表型监测系统&#xff0c;集合实验设置、数据分析、决策工具于一身&#xff0c;能够高通量实时动态监测并进行全天候生理及环境参数采集&#xff0c;是进行植物…

网络设备中的配置文件管理

建立强大网络的第一步是为灾难和网络中断做好准备&#xff0c;许多企业在中断期间遭受损失&#xff0c;因为他们缺乏备份计划并且配置管理不达标&#xff0c;使用配置文件管理工具进行适当的配置文件管理不仅有助于处理网络中断&#xff0c;还有助于优化网络性能。 使用配置文…

海尔设计借助亚马逊云科技生成式AI,实现端到端的云上工业设计解决方案

海尔创新设计中心&#xff08;以下简称海尔设计&#xff09;成立于1994年&#xff0c;目前拥有400多名设计师&#xff0c;为海尔智家旗下七大品牌全球的所有产品提供设计创新和模式探索。亚马逊云科技为海尔设计提供了四个完整的云上解决方案&#xff0c;全面替代自有机房&…

Vue3 word如何转成pdf代码实现

&#x1f642;博主&#xff1a;锅盖哒 &#x1f642;文章核心&#xff1a;word如何转换pdf 目录 1.前端部分 2.后端部分 在Vue 3中&#xff0c;前端无法直接将Word文档转换为PDF&#xff0c;因为Word文档的解析和PDF的生成通常需要在后端进行。但是&#xff0c;你可以通过Vu…

HCIA 第二课总结

配置网络设备的明文密钥实验组网 实验拓扑 将一个路由器使用配置口进行连接 sys #进入系统视图模式 sysname RTA #给设备命名 user-interface console 0 #进入用户接口配置界面 authentication-mode password #配置认证模式为密钥认证 set authentication password ciphe…

百题千解计划【CSDN每日一练】订班服(附解析+多种实现方法:Python、Java、C、C++、C#、Go、JavaScript)

如果决意去做一件事了,就不要再问自己和别人值不值得,心甘情愿才能理所当然,理所当然才会义无反顾。 🎯作者主页: 追光者♂🔥 🌸个人简介: 💖[1] 计算机专业硕士研究生💖 🌟[2] 2022年度博客之星人工智能领域TOP4🌟 🏅[3] 阿里云社区特邀专…

LeetCode刷题笔记-287题寻找重复数

LeetCode 287 寻找重复数 难度&#xff1a;中等 题目&#xff1a; 给定一个包含 n 1 个整数的数组 nums &#xff0c;其数字都在 [1, n] 范围内&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。 假设 nums 只有 一个重复的整数 &#xff0c;返回…

软件外包开发测试管理工具

测试是软件工程中非常重要的一个环节&#xff0c;在上线前必须需要经过严格的测试才能确保上线后软件系统长时间运行。有大量的软件开发和测试管理工具&#xff0c;每一个工具都有自己的特点&#xff0c;今天和大家分享一些常见的工具&#xff0c;希望对大家有所帮助。北京木奇…

【Spring Boot丨序列化、反序列化】

序列化、反序列化 概述Jackson 序列化和反序列化简介自定义序列化器注册外部序列化程序&#xff1a; 指定类的 Json 序列化、反序列化 主页传送门&#xff1a;&#x1f4c0; 传送 概述 序列化是将对象转换为字节序列的过程&#xff0c;而反序列化则是将字节序列恢复为对象的过…

监听镜像版本变化触发 GitOps工作流

文章目录 前言工作流总览安装和配置 ArgoCD Image Updater创建 Image Pull Secret&#xff08;可选&#xff09;创建 Helm Chart 仓库创建 ArgoCD Application删除旧应用&#xff08;可选&#xff09;配置仓库访问权限创建 ArgoCD 应用 体验 GitOps 工作流总结 前言 在【GitOps…

基于Citespace、vosviewer、R语言的文献计量学可视化分析技术及全流程文献可视化SCI论文高效写作方法

文献计量学是指用数学和统计学的方法&#xff0c;定量地分析一切知识载体的交叉科学。它是集数学、统计学、文献学为一体&#xff0c;注重量化的综合性知识体系。特别是&#xff0c;信息可视化技术手段和方法的运用&#xff0c;可直观的展示主题的研究发展历程、研究现状、研究…

【小波尺度谱】从分段离散小波变换计算小波尺度谱研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

用于系统监控及进程管理python库之psutil

前言 对于一个job级别应用再进行测试的过程中&#xff0c;不可避免测试该服务的一些性能&#xff0c;比如占有cpu的使用量&#xff0c;使用的memory的大小等&#xff0c;比较简单的方式是在服务中起一个并行的线程&#xff0c;每隔一段时间打印这些关注量的大小&#xff0c;之后…

invalid use of incomplete type class ui(new Ui::MainWindow)报错,解决方案

invalid use of incomplete type class ui(new Ui::MainWindow报错&#xff0c;解决方案 原因解决方案 原因 就是在我改控件button的名字的时候&#xff0c;没有选中控件&#xff0c;导致吧mainwindow的名字改了。。。 解决方案 吧mainwindow的名字改回来 MainWindow 完美解…

element 级联 父传子

html代码例子 父组件 <el-cascaderstyle"width: 100%"change"unitIdChange":options"unitOptions"filterablev-model"formInline.unitId":props"unitProps"/></el-form-item>//改变级联传值到这个组件里面<r…