【Linux】:程序替换

news2024/9/27 9:29:25

 朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux程序替换的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 程序替换

2. 单进程的程序替换

2.1 单进程程序替换的原理

3. 多进程的程序替换

3.1 多进程程序替换原理

4. 剩余接口的介绍以及使用 

4.1 execl函数介绍

4.2 execlp函数介绍

4.3 execv函数介绍 

4.4 execvp函数介绍

4.5 替换别的程序 

5. 环境变量和程序替换

5.1 从bash开始继承 

5.2 从父进程开始继承 

5.3 环境变量默认继承

5.4 execle函数介绍 

5.5 execve系统调用 


1. 程序替换

我们创建的所有子进程,执行的代码都是父进程代码的一部分!那么如果我们想让子进程执行新的程序呢?执行全新的代码和访问全新的数据,不再和父进程有所瓜葛,那么就需要用到程序替换。

这么多程序替换的函数调用,到底该怎么样用呢?我们直接开始使用:

2. 单进程的程序替换

为了便于理解,我们先从单进程的程序替换开始:

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

先来看最简单的程序替换接口,其中里面的三个点表示的意思就是可变参数,使用的方法和我们的printf函数中的可变参数一样;

我们程序要能运行起来,第一步先要找到这个程序,第二部如何运行。

  • const char* path表示:新程序的文件路径 + 文件名;
  • const char *arg表示:这个程序怎么运行(简单的说我们在命令行怎么输就怎么写,最终以NULL结尾)。

返回值:成功没有返回值,失败返回-1。

我们先来演示一遍:让ls命令替换掉我们写的可执行

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

int main()
{
    printf("pid: %d, exec command begin\n", getpid());
    sleep(3);
    // 程序替换
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    printf("pid: %d, exec command begin\n", getpid());

    return 0;
}

通过结果可以发现,我们通过execl成功完成了程序替换,但是代码中的最后一行代码为什么没有打印出来呢?这个到后续再解释!

2.1 单进程程序替换的原理

我们已经完成了单进程的程序替换,那么它的原理是什么呢?

当一个进程运行起来时,OS会为它创建PCB、虚拟地址空间、页表,将它的代码、数据加载到物理内存中,并且然后通过页表建立起虚拟到物理的映射关系;当进行程序替换时,execl就将要替换的程序代码和数据直接在物理内存中替换覆盖掉,此时原来的代码和数据就会被新替换的代码和数据覆盖掉,继续向后执行也就执行的新程序的代码和数据,这个过程中并没有创建新的进程。

3. 多进程的程序替换

见识过单进程的程序替换以及程序替换的原理,接下来替换一下多进程:

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);
        // 程序替换
        execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

3.1 多进程程序替换原理

首先,进程具有独立性,其次我们都知道,父子进程代码共享,数据以写时拷贝的方式各自私有一份,那么在程序替换的时候,把子进程的数据替换这没问题,因为父子数据各自私有,但是将代码替换,这就有点不能忍了,父子进程代码共享,既然子进程都执行的是替换后的代码,为什么父进程在程序替换的时候还是继续执行他自己代码呢?

是因为在多进程的程序替换时,当子进程启动程序替换覆盖它们的共享代码时,也会发生写时拷贝的方式,重新开辟一块空间,然后进行替换写入,这样父子进程的代码就不共享,子进程继续执行它替换后的代码,父进程继续执行自己的代码。

在替换完之后,子进程怎么知道从新程序的哪里开始执行呢?

我们的代码在编译形成可执行程序的时候会有一个程序入口地址--entry地址;

CPU内部的eip寄存器可以记录我们程序当前运行到哪里了,所以当程序替换之后,新程序的eip程序计数器就被修改为entry地址,所以就可以重新开始运行了。 

接下来解决上面遗留的问题: 代码中的最后一行代码为什么没有打印出来呢?

是因为在程序替换以后,代码和数据都被覆盖了,所以execl函数替换成功后,后续的代码没有机会执行了,因为已经被替换掉了!

所以我们不用判断它的返回值,如果继续执行了后续代码,说明程序替换出错了。

4. 剩余接口的介绍以及使用 

要完成程序替换需要满足以下两点:

  • 1. 必须先要找到需要替换的程序。
  • 2. 必须告诉替换函数该怎么执行。

这些程序替换的函数前面的单词都相同,主要是后缀不同,代表的意思也不同。

4.1 execl函数介绍

execl中的l表示列表的意思,在我们传递使用方法的时候,就像一个列表一样,最后以NULL结尾。也就类似于一个list链表一样。

4.2 execlp函数介绍

execlp中的p表示要找的目标可执行程序会自动在环境变量PATH中根据file去查找。

execlp("ls", "ls", "-a", "-l", NULL);
// 两个ls表示的含义不一样
// 第一个表示要执行的文件
// 第二个表示如何执行

4.3 execv函数介绍 

execv中v表示的是数组传参的意思,可以将如何执行保存一个数组中,然后传递该数组即可。

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

int main()
{

    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);

        // 程序替换
        char *const argv[] = {
           "ls",
           "-a",
           "-l",
           NULL
        };
        execv("/usr/bin/ls", argv);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

4.4 execvp函数介绍

execvp就是execv和execlp和结合,可以直接传递文件名,并且执行方式以数组传参的方式传递。

        char *const argv[] = {
           "ls",
           "-a",
           "-l",
           NULL
        };
        execvp("ls", argv);
        //execvp("argv[0]", argv);

4.5 替换别的程序 

程序替换不仅可以替换系统的命令,还可以替换我们自己写的任何程序:

用C++程序来替换C语言程序

#include <iostream>

int main()
{
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    std::cout << "hello C++" << std::endl;
    return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{

    pid_t id = fork();
    if(id == 0)
    {
        // child
        printf("pid: %d, exec command begin\n", getpid());
        sleep(3);
        // 程序替换
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    // parent
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
    {
        printf("wait success, rid: %d\n", rid);
    }

    return 0;
}

我们在命令行执行可执行程序时用的是./exe,那么为什么在传参时只传递exe,因为./表示的意思是先找到该程序,在运行,execl第一个参数已经找到了,所以只需要传递exe即可。

5. 环境变量和程序替换

在环境变量章节说过环境变量具有全局属性,那么当我们进行程序替换的时候,子进程对应的环境变量是可以直接从父进程中来的,父进程的环境变量从bash中来,我们可以通过代码来验证一下:

5.1 从bash开始继承 

同样的我们还是使用程序替换的方式,来看看子进程替换之后从bash继承下来的环境变量。

先在命令行自定义一些环境变量:

export MYENV1=1111111111111
export MYENV2=2222222222222

源test.cc

#include <iostream>

int main(int argc, char *argv[], char *env[])
{
    int i = 0;
    for(; env[i]; i++)
    {
        std::cout << env[i] << std::endl;  // 打印环境变量
    }
    return 0;
}

源myprocess.c

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

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);
    return 0;
}

可以看到父进程继承了bash的环境变量,子进程继承了父进程的环境变量。

5.2 从父进程开始继承 

在程序中导入环境变量的接口为:putenv();

#include <stdlib.h>
int putenv(char *string);

在父进程中导入一个环境变量,看看子进程能否继承:

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

int main()
{
    char *env_val = "MYVAL5=5555555555555555555555555";
    putenv(env_val);
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execl("./test", "test", NULL);
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

 

5.3 环境变量默认继承

在进程地址空间这一章节我们了解到了进程地址空间中有环境变量和命令行参数,在父进程创建子进程的过程中,子进程以父进程为模版,子进程的地址空间也就以父进程的地址空间为模版创建了,进而父进程的环境变量和命令行参数也就都会被继承下来,所以通过地址空间可以让子进程继承父进程的环境变量和数据,所以说这是一种默认的行为。

那么继承下去的环境变量为什么不会收到程序替换的影响呢?环境变量也是数据呀!

在程序替换的过程中,只会替换程序的代码和数据,并不会对环境变量产生影响。

5.4 execle函数介绍 

将父进程的环境变量传递给子进程有两种方式:

1. 直接使用(默认集成,需要传)。

2. 使用execle函数的第三个参数直接传递。

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

extern char ** environ; 

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execle("./test", "test",NULL, environ);  // 直接传
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if(rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

如果要传递我们自己的环境变量,可以先定义,再传递;同样我们也可以把命令行参数传递给子进程,然后通过程序替换来展现出来:

源test.cc

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

int main(int argc, char *argv[], char *env[])
{
    for(int i = 0; i < argc ; i++)
    {
        std::cout << i << "->" <<  argv[i] << std::endl;
    }
    std::cout << "################################" << std::endl;
    for(int i = 0; environ[i]; i++)
    {
        std::cout << i << " : " << env[i] << std::endl;
    }
    return 0;
}

源process.c

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

int main()
{
    char *const myenv[] = {
        "MYVAL1=11111111111111",
        "MYVAL2=22222222222222",
        "MYVAL3=33333333333333",
        "MYVAL4=44444444444444",
        NULL};

    pid_t id = fork();
    if (id == 0)
    {
        printf("pid: %d, exec command begin\n", getpid());
        sleep(1);
        execle("./test", "test", "-a", "-b", NULL, myenv); // 直接传
        printf("pid: %d, exec command begin\n", getpid());
    }
    pid_t rid = waitpid(id, NULL, 0);
    if (rid == id)
        printf("wait success, rid: %d\n", rid);

    return 0;
}

可以看到传递的环境变量不是新增,而是覆盖是传递!

程序替换可以将命令行参数、环境变量通过自己的参数,传递给被替换程序的mian函数中!

5.5 execve系统调用 

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!      

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

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

相关文章

AWS-WAF-Log S3存放,通过Athena查看

1.创建好waf-cdn 并且设置好规则和log存储方式为s3 2. Amazon Athena 服务 使用 &#xff08;注意s3桶位置相同得区域&#xff09; https://docs.aws.amazon.com/zh_cn/athena/latest/ug/waf-logs.html#waf-example-count-matched-ip-addresses 官方文档参考,建一个分区查询表…

批量发送定制邮件内容

需要给以下学员发送作业反馈邮件&#xff08;文件名为&#xff1a;学员作业反馈.xlsx&#xff09; 学员序号学员姓名学员邮箱作业反馈20090001海龙3177261496qq.com第1题&#xff1a;少了一个a20090002真达3177261496qq.com第2题&#xff1a;少了一个b20090003张三3177261496…

Python: 分块读取文本文件

在处理大文件时&#xff0c;逐行或分块读取文件是很常见的需求。下面是几种常见的方法&#xff0c;用于在 Python 中分块读取文本文件&#xff1a; 1、问题背景 如何分块读取一个较大的文本文件&#xff0c;并提取出特定的信息&#xff1f; 问题描述: fopen(blank.txt,r) quot…

昇思25天学习打卡营第17天|应用实践之SSD目标检测

基本介绍 今天要学习的内容是计算机视觉领域中的目标检测任务。与图像分类相比&#xff0c;目标检测更难&#xff0c;因为目标检测不仅要检测出图片中的物体的类别&#xff0c;还要检测出该物体的位置。现主流的目标检测算法大致可分为两种&#xff0c;一种是基于CNN的&#xf…

Type-C/DP1.4到HDMI2.0替代龙讯LT8711HE,集睿智远CS5262

NCS8622是一款高性能低功耗的Type-C/DP1.4到HDMI2.0转换器&#xff0c;设计为连接USB Type-C源或DP1.4源到HDMI2.0。 NCS8622集成了符合DP1.4标准的接收器&#xff0c;以及符合HDMI2.0标准的发射器。此外&#xff0c;CC控制器用于将CC通信到实现DP Alt模式。DP接收器集成了HDCP…

软考五个高级科目怎么选?如何一口气拿下证书!

软考高级包括&#xff1a; 信息系统项目管理师、系统分析师、系统架构设计师、网络规划设计师、系统规划与管理师等五个考试。 一、各科特点&#xff1a; 信息系统项目管理师 特点&#xff1a;主要从事信息系统项目管理方面的工作&#xff0c;要求掌握项目管理的知识体系和实…

类与对象-继承-构造和析构顺序

构造和析构顺序 #include<iostream> using namespace std;class Base { public:Base(){cout << "Base构造函数" << endl;}~Base(){cout << "Base析构函数" << endl;} };class Son :public Base { public:Son(){cout <&l…

C#开发:下载node.js指定版本

一、打开官网 二、找到指定版本 三、选择程序包msi下载 四、验证下载是否成功 cmd输入&#xff1a; node -v npm -v

从零开始学量化~Ptrade使用教程(四)——股票普通买卖与回购业务

股票普通买卖 股票买入 通过选择委托方向实现股票的买入与卖出&#xff0c;可根据输入的价格自动查询可买数量。 用鼠标点击【买入】&#xff0c;如图所示&#xff1a; 输入股票代码并选中后&#xff0c;选择委托类型&#xff0c;若为限价类型&#xff0c;输入委托价格&#xf…

如何用Vue3和Plotly.js绘制交互式瀑布图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Plotly.js 在 Vue 中创建瀑布图 应用场景 瀑布图广泛用于可视化财务报表和展示增量变化&#xff0c;例如利润表、现金流量表和收入分析。它们通过将正值和负值堆叠在垂直轴上&#xff0c;清晰地展示每个…

宿州降本 提质 增效 数据采集监控平台提高生产自动化水平

在当今竞争激烈的市场环境中&#xff0c;企业追求降本、提质、增效已成为发展的关键。而我们的[数据采集监控平台名称]数据采集监控平台&#xff0c;正是助力企业实现这一目标的强大工具。 LP-SCADA数据采集监控平台是工业4.0中主要的数据采集系统之一&#xff0c;主要针对产线…

告别堆积,迎接清新:回收小程序,打造无废生活新选择

在快节奏的现代生活中&#xff0c;物质的丰富与便利似乎成为了我们日常的一部分&#xff0c;但随之而来的&#xff0c;是日益增长的废弃物堆积问题。街道边、社区里&#xff0c;甚至是我们的家中&#xff0c;废弃物品仿佛无孔不入&#xff0c;逐渐侵蚀着我们的生活空间与环境质…

漏洞挖掘思路分享 | 首次尝试cnvd捡洞

因为那天项目刚忙完闲来无事&#xff0c;尝试捡个cnvd洞&#xff0c;cnvd录取要五千万资产&#xff0c;自己又懒得找毕竟捡洞嘛&#xff0c;索性去cnvd上进行搜集 直接开搜弱口令&#xff0c;因为我比较喜欢有登录框的站&#xff0c;这样搜索出来的资产可能就更容易进行挖掘 随…

CSS动画和JavaScript动画,大扫盲,有代码示例。

小伙伴们都知道&#xff0c;css和JavaScript都可以做动画&#xff0c;但是大都不知道二者的区分&#xff0c;该如何选择&#xff0c;贝格前端工场借助本文为大家分享一下。 一、css动画和JavaScript动画的区分 CSS动画和JavaScript动画都可以用于在网页上创建动态效果&#x…

微信小程序style动态绑定Object不生效处理方法

渲染的时候style变成了[Object Object] 解决方法: 给Object外面加一个[] <image :style"[imgStyle]" :src"url"></image>

人工智能建立在对象存储上的真正原因

tl;dr: 在这篇文章中&#xff0c;我们将探讨 AI 工作负载依赖高性能对象存储的四个技术原因。 1. 对非结构化数据没有限制 在当前的机器学习范式中&#xff0c;性能和能力与计算成比例&#xff0c;计算实际上是数据集大小和模型大小的代理&#xff08;神经语言模型的缩放定律&a…

嘎嘎详细的三维变换详细讲解,包括视图变换、投影变换等,超级通俗易懂!

前置二维空间的各种变换笔记&#xff1a;二维变换 三维空间中的齐次坐标 从二维变换开始引申&#xff0c;可得到三维中的一个点的表达方式为 ( x , y , z , 1 ) ⊤ (\mathbf{x}, \mathbf{y}, \mathbf{z}, 1)^{\top} (x,y,z,1)⊤&#xff0c;也就是w1&#xff0c;而三维的向量…

插入排序算法(C语言版)

直接插入排序 插入排序&#xff08;insert sort&#xff09;是一种简单的排序算法&#xff0c;它的工作原理与手动整理一副牌的过程非常相似。 具体来说&#xff0c;我们在未排序区间选择一个基准元素&#xff0c;将该元素与其左侧已排序区间的元素逐一比较大小&#xff0c;并…

了解劳动准备差距:人力资源专业人员的战略

劳动准备差距是一个紧迫的问题&#xff0c;在全球人事部门回应&#xff0c;谈论未开发的潜力和错过的机会。想象一下&#xff0c;人才和需求之间的悬崖之间有一座桥&#xff0c;这促使雇主思考&#xff1a;我们是否为员工提供了足够的设备来应对未来的考验&#xff1f; 这种不…

认识R155法规(UN Regulation No. 155)-MUNIK

背景 Background 随着汽车新四化&#xff08;电动化、智能化、网联化、共享化&#xff09;政策的提出&#xff0c;大数据和人工智能等技术的发展&#xff0c;以及软件驱动汽车、舱驾一体、行泊一体等新型架构概念的提出&#xff0c;车内外智能传感器采集的大量数据&#xff08…