【Linux取经路】进程控制——程序替换

在这里插入图片描述

一、单进程版程序替换看现象

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

int main()
{
    printf("befor: I am a process,pid:%d,ppid:%d\n", getpid(), getppid());

    //exec类函数的标准写法
    execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
    
    printf("after: I am a process, getppid());
    return 0;
}

在这里插入图片描述


在调用了程序替换函数 execl 后,我们的进程去执行了 ls 指令,并且原进程的 after 信息没有打印。在前面的文章中讲过,指令本质上就是可执行程序。因此程序替换函数 execl 可以用其它进程来替换当前进程。

二、程序替换的基本原理

当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 pid 并未改变。

在这里插入图片描述


补充

  • 程序替换成功后,exec 系列函数后续的代码不会被执行,只有替换失败才有可能执行后续代码。exec 系列函数,只有失败返回,没有成功返回。

  • Linux 中形成的可执行程序本质上是 EFL 格式的文件,该文件有一个表头,里面保存了该可执行程序的入口地址。

三、程序替换接口学习

和程序替换有关的接口一共有七种,其中一个是系统调用,剩下六个都是库函数。

  • 系统调用:
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
  • 库函数
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, ...);
int execle(const char *path, ..., 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 envp[]);

上面这六种函数的参数主要是为了解决以下两个问题:如何找到新替换进来的程序?如何执行新替换的程序。因此:

第一个参数就是解决第一问题的,有两种解决方案,第一种是函数名不带 p 的,此时第一个形参 path 表示的是可执行程序的全路径;第二种是函数名代 p 的,此时第一个形参 file 表示的是可执行程序的名字,函数会拿着这个名字去 PATH 环境变量下搜索这个可执行程序。

第二个参数就是解决第二个问题的,也有两种解决方案。如何执行新替换的可执行程序本质上就是要知道执行该程序的指令以及参数是什么。l 表示参数采用列表,以可变参数的形式将指令和选项传进去(命令行中输入什么,这里就传什么),最后要以 NULL 结尾。v 表示采用字符串指针数组的方式,把指令以及选项都存在一个字符串指针数组中(最后必须是 NULL),然后把这个数组作为实参传给程序替换函数。这个参数最终会作为命令行参数传递给新替换进来的可执行程序。

第三个参数 envp,如果使用这个参数,那么新替换进来的进程将采用覆盖的策略彻底替换掉父进程的环境变量,即使用该参数后,新替换进来的进程不再继承父进程的环境变量,而是完全以 envp 数组中的内容作为自己的环境变量。

小Tips:在将进程地址空间的时候画过一张关于进程地址空间的图,其中有一部分存储的就是命令行参数和环境变量,而子进程的进程地址空间是继承自父进程的,环境变量也是在这个时候继承下去的,因此一个进程的环境变量在该进程创建的时候就已经被该进程从父进程那里继承下来了,在程序替换的过程中,环境变量信息不会被替换

// 首先在 bash 中加入自定义的环境变量
export MY_VALUE=123456
// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, char* argv[], char* env[])
{
    printf("befor: pid: %d,ppid: %d,mycommand running...\n", getppid());
    printf("mycommand get MY_VALUE: %s\n", getenv("MY_VALUE"));
    pid_t id = fork();
    if (id == 0)
    {
        // child
        sleep(2);
        // exec类函数的标准写法
        char* const child_argv[] = {"otherexe", "-b", "-c", NULL};
        execv("./otherexe", child_argv);// 替换成otherexe程序
        printf("%s\n", strerror(errno));
        exit(errno);// 如果执行了这段代码说明程序替换失败
    }
    // father
    sleep(4);
    pid_t ret = waitpid(id, NULL, 0);
    if(ret > 0)
    {
        printf("wait success,my pid: %d,ret pid: %d\n", ret);
    }
    return 0;
}
#include <iostream>
#include <unistd.h>
#include <stdlib.h>

using namespace std;

int main(int argc,otherexe running...\n", getppid());
    printf("otherexe get MY_VALUE: %s\n", getenv("MY_VALUE"));
    return 0;
}

在这里插入图片描述


分析:首先在 bash 中添加环境变量 MY_VALUE,mycommand 程序作为 bash 的子进程一定会继承该环境变量,在 mycommand 先创建一个子进程,它也会继承 mycommand 的环境变量,然后在子进程中调用 execv 接口进程程序替换,将 otherexe 程序替换进来,在 otherexe 程序中我们调用 getenv 接口,仍然可以获得 MY_VALUE 这个环境变量的值,这证明了我们上面说的:在进行程序替换的过程中,环境变量信息不会被替换。补充一点,如果要在当前进程的地址空间中新增环境变量可以调用 putenv 接口。

小结:在 bash 中执行的所有程序其实都是子进程,并且都是通过 exec 系列函数将程序对应的代码和数据加载到内存中。因此 exev 系列函数起到加载器的效果,函数里面一定会涉及到内存申请、外设访问等动作。

3.1 替换自己写的可执行程序

// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>

int main()
{
    char* const argv[] = {"ls", NULL};
    printf("befor: I am a process,pid: %d,ppid: %d\n", getppid());
    pid_t id = fork();
    if (id == 0)
    {
        // child
        sleep(2);
        // exec类函数的标准写法
        execl("./otherexe", "otherexe", NULL);
        printf("%s\n", strerror(errno));
        exit(errno);// 如果执行了这段代码说明程序替换失败
    }
    // father
    sleep(6);
    pid_t ret = waitpid(id, ret);
    }
    return 0;
}
// otherexe.cc
#include <iostream>

using namespace std;

int main()
{
    cout << "Hello Linux!" << endl;
    return 0;
}

代码解释:其中 otherexe 是我们自己写的一个 C++ 程序,在 mycommand 程序中先创建一个子进程,然后在该子进程中调用程序替换函数 execl,将我们自己写的 mycommand 程序替换进来。这里有一个细节就是 execl 的第二参数到底传 ./otherexe 还是 otherexe,因为上面说过在 bash 中如何输入这个参数就怎么传,在 bash 中执行 otherexe 程序输入的指令是:./otherexe,其中 ./ 主要是为了告诉 bash otherexe 这个程序就在当前目录下。而 execl 函数的第一个参数就已经将 otherexe 程序的详细路径传进去了,所以第二个参数可以直接传 otherexe。但经过验证传 otherexe./otherexe 都可以。

3.2 第三个参数 envp 验证

// mycommand.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <errno.h>

int main(int argc, getppid());
    pid_t id = fork();
    if (id == 0)
    {
        // child
        sleep(2);
        // exec类函数的标准写法
        char* const child_argv[] = {"otherexe", NULL};
        char* const myenv[] = {
            "MY_PATH=./usr/wcy/linux-s",
            "MY_VALUE=asdfg",
            "NAME=wcy",
            NULL
        };// 自定义环境变量
        execle("./otherexe", "-w", "-z", myenv);// 替换成otherexe程序
        printf("%s\n", ret);
    }
    return 0;
}
// otherexe.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>

using namespace std;

int main(int argc, getppid());
    cout << "命令行参数:" << endl;
    for(int i = 0; argv[i]; i++)
    {
        cout << i << ": " << argv[i] << endl;
    }
    cout << "我是环境变量:" << endl;
    for(int i = 0; env[i]; i++)
    {
        cout << i << ": " << env[i] << endl;
    }
    return 0;
}

在这里插入图片描述

四、结语

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,春人的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是春人前进的动力!

在这里插入图片描述

原文地址:https://blog.csdn.net/weixin_63115236/article/details/135955719

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


linux常用进程通信方式包括管道(pipe)、有名管道(FIFO)、信号(signal)、消息队列、共享内存、信号量、套接字(socket)。管道用于具有亲缘关系的进程间通信,有名管道的每个管道具有名字,使没有亲缘关系的进程间也可以通信。信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除
Linux性能观测工具按类别可分为系统级别和进程级别,系统级别对整个系统的性能做统计,而进程级别则具体到进程,为每个进程维护统计信息。&#xD;&#xA;&#xD;&#xA;按实现原理分,可分为基于计数器和跟踪以及剖析。含义如下:&#xD;&#xA;&#xD;&#xA;计数器:内核维护的统计数据,通常为无符号整型,用于对发生的事件计数,比如,网络包接收计数器,磁
本文详细介绍了curl命令基础和高级用法,包括跳过https的证书验证,详细追踪整个交互过程,可用于调用网络后端接口,诊断http和https网络服务故障。
本文包含作者工作中常用到的一些命令,用于诊断网络、磁盘占满、fd泄漏等问题。命令包括ping、fping、tcpdump、lsof、netstat、/proc/$pid/fd、du、grep、traceroute、dig。
linux的平均负载表示运行态和就绪态及不可中断状态(正在io)的进程数目,用uptime查看到负载很高,既有可能是CPU利用率高,也可能是大量在等待io的进程导致,用mpstat查看每个CPU的使用情况,查看CPU的使用率或者CPU花在等待io的时间,接着用pidstat定位具体的进程
CPU上下文频繁切换会导致系统性能下降,切换分为进程切换、线程切换及中断切换,进程切换的开销较大,除了需要保存寄存器和程序计数器中的值还需保存全局变量、栈等到内存中,以便下次运行恢复,而同一进程中的线程切换开销会小很多,只需更新寄存器和线程独有的栈,共享资源如打开的文件、全局变量等无需切换,当硬件中
1.top命令 作用:该命令可以按CPU使用.内存使用和执行时间对任务进行排序,常用来监控系统中占用CPU或内存较高的程序及CPU和内存的负载。 默认视图: 当想看系统负载时,可观察汇总的%CPU中的us用户进程和sy系统进程是否占用CPU很高,相加接近100%就说明占用很高了,有些程序可能得不到及
文章浏览阅读1.8k次,点赞63次,收藏54次。Linux下的目录权限!!!粘滞位!!!超详解!!!
文章浏览阅读1.6k次,点赞44次,收藏38次。关于Qt的安装、Windows、Linux、MacBook_mack book 安装qt
本文介绍了使用shell脚本编写一个 Hello
文章浏览阅读1.5k次,点赞37次,收藏43次。【Linux】初识Linux——了解操作系统的发展历史以及初次体验Linux编程环境
文章浏览阅读3k次,点赞34次,收藏156次。Linux超详细笔记,个人学习时很认真的记录的,觉得好的麻烦点个赞。
文章浏览阅读6.8k次,点赞109次,收藏114次。【Linux】 OpenSSH_9.3p1 升级到 OpenSSH_9.5p1(亲测无问题,建议收藏)_openssh_9.5p1
文章浏览阅读3.5k次,点赞93次,收藏78次。初识Linux中的线程,理解线程的各种概念,理解进程地址空间中的页表转换,介绍pthread线程库并理解线程库!
文章浏览阅读863次。出现此问题为Linux文件权限问题,解决方案为回到引擎目录执行命令。输入用户密码后运行./UnrealEditor。_increasing per-process limit of core file size to infinity.
文章浏览阅读2.9k次。使用文本编辑器:打开CSV文件,并使用文本编辑器(如Notepad++、Sublime Text、Visual Studio Code等)来查看文件的字符编码格式。通常在编辑器的底部状态栏或设置中可以找到当前编码的显示。请注意,上述方法并非绝对准确,特别是当文件没有明确的编码标识时。因此,如果你发现CSV文件在不同的工具或方法中显示不同的编码格式,可能需要进行进一步的分析和判断,或者尝试使用不同的编码转换方法。该命令将输出文件的MIME类型和编码信息。使用命令行工具:在命令行中,你可以使用。_shell读取csv文件逐行处理
本文介绍了如何在Linux系统中升级gcc版本,以便更好地支持C++11及以上版本的新特性。通过升级gcc,可以提升编译器的功能和性能,获得更好的开发体验。详细的步骤和方法请参考原文链接。
文章浏览阅读4.4k次,点赞6次,收藏19次。Mosquitto是一个开源的MQTT消息代理服务器。MQTT是一个轻量级的、基于发布/订阅模式的消息传输协议。 mosquitto的安装使用比较简单,可以方便的来进行一些测试。_linux mosquitto
文章浏览阅读7.2k次,点赞2次,收藏12次。Linux中,用于根目录下有一个.ssh目录,保存了ssh相关的key和一些记录文件。_~/.ssh/
文章浏览阅读4.5k次,点赞5次,收藏18次。首先需要安装 snmp ,使用下面的命令进行安装安装完毕之后,使用下面的命令查看是否安装成功当命令行显示如图即为安装成功。_snmp工具