【Linux】进程控制--程序替换

目录

📖一、程序替换函数的现象

📖二、程序替换的基本原理

📖三、程序替换接口学习

execl 和 execlp 函数

execv 和 execvp 函数

📖四、完结 


📖一、程序替换函数的现象

#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, pid:%d, ppid:%d\n", getpid(), getppid());
    return 0;
}

可以发现,我们的after并没有打印出来

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


📖二、程序替换的基本原理

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

补充

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


📖三、程序替换接口学习

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

  • 系统调用:
#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, const char *arg, ...);
int execle(const char *path, const char *arg, ..., 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 argv[], char *const envp[]);


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

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


第二个参数就是解决第二个问题的,也有两种解决方案。

如何执行新替换的可执行程序本质上就是要知道执行该程序的指令以及参数是什么。

l 表示参数采用列表,以可变参数的形式将指令和选项传进去(命令行中输入什么,这里就传什么),最后要以 NULL 结尾。

v 表示采用字符串指针数组的方式,把指令以及选项都存在一个字符串指针数组中(最后必须是 NULL),然后把这个数组作为实参传给程序替换函数。这个参数最终会作为命令行参数传递给新替换进来的可执行程序。


第二个参数我们分别举例:

execl 和 execlp 函数

  • 参数作用:第二个参数 const char *arg 及后续可变参数(以 NULL 结尾 ),是传递给要执行程序的命令行参数。这些参数会依次填充到程序的 argv 数组(在 int main(int argc, char *argv[]) 中 )中。例如在 execl("/bin/ls", "ls", "-l", NULL); 里,"ls" 是第一个参数,会被 ls 程序当作自身程序名(多数程序习惯将 argv[0] 当作自身标识 ),"-l" 是第二个参数,传递给 ls 程序表示以长格式显示文件信息。
  • 注意事项:参数数量和顺序要与被执行程序期望的命令行输入一致,且必须以 NULL 作为参数列表结束标志,告知函数参数传递完毕。

execv 和 execvp 函数

  • 参数作用:第二个参数 char *const argv[] 是一个指针数组,数组中的每个元素都是一个指向 char 类型的指针,分别指向要传递给程序的命令行参数。例如 char *argv[] = {"ls", "-a", NULL}; execv("/bin/ls", argv); ,argv 数组里的元素依次对应 ls 程序所需的命令行参数。
  • 与 execl 等区别execl 等函数是通过可变参数列表逐个传递参数,而 execv 系列是通过指针数组一次性传递参数集合,本质上都是为被执行程序提供运行所需参数信息。

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

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


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", "-a", "-l", NULL};
    printf("befor: I am a process, pid: %d, ppid: %d\n", getpid(), 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, NULL, 0);
    if(ret > 0)
    {
        printf("wait success, my pid: %d, ret pid: %d\n", getpid(), ret);
    }
    return 0;
}
// otherexe.cc
#include <iostream>

using namespace std;

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

 代码解释:其中 otherexe 是我们自己写的一个 C++ 程序,在 mycommand 程序中先创建一个子进程,然后在该子进程中调用程序替换函数 execl,将我们自己写的 otherexe 程序替换进来。

写了两遍otherexe不会冲突吗?

  • 第一个 "./otherexe" 是要执行的可执行文件的路径 ,这里表示当前目录下的 otherexe 文件。
  • 第二个 "otherexe" 是传递给 otherexe 程序的第一个命令行参数 。一般来说,程序可以通过 argv[0] (在 main 函数的参数列表中获取,如 int main(int argc, char *argv[]) )来接收并使用这个参数,很多程序会将其当作自身程序名来使用或显示相关信息等,但具体如何使用取决于 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, char* argv[], char* env[])
{
    printf("befor: pid: %d, ppid: %d, mycommand running...\n", getpid(), getppid());
    pid_t id = fork();
    if (id == 0)
    {
        // child
        sleep(2);
        // exec类函数的标准写法
        char* const myenv[] = {
            "MY_PATH=./usr/yzq/linux-s",
            "MY_VALUE=12345",
            "NAME=yzq",
            NULL
        };// 自定义环境变量
        execle("./otherexe", "otherexe", "-a", "-w", "-z", NULL, myenv);// 替换成otherexe程序
        printf("%s\n", strerror(errno));
        exit(errno);// 如果执行了这段代码说明程序替换失败
    }
    return 0;
}
// otherexe.cc
#include <iostream>
#include <unistd.h>
#include <stdlib.h>

using namespace std;

int main(int argc, char* argv[], char* env[])
{
    printf("befor: pid: %d, ppid: %d, otherexe running...\n", getpid(), 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;
}


📖四、完结 

创作不易,留下你的印记!为自己的努力点个赞吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值