Linux:进程程序替换

程序替换

一、作用/目的

fork() 之后,父子各自执行父进程代码的一部分如果子进程就想执行一个全新的程序呢?进程的程序替换来完成这个功能!程序替换是通过特定的接口,加载磁盘上的一个全新的程序(代码和数据),加载到调用进程的地址空间中!

二、替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以掉用 exec 前后该进程的id没有改变。

本质:加载(在当前进程当中加载新的代码和数据)

三、替换函数

有七种以exec开头的函数,统称exec函数:

cpp 复制代码
#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[]);
// 上面六个函数是库函数
// 下面这个函数是系统调用,envp没有传,默认是environ
int execve(const char *filename, char *const argv[], char *const envp[]);

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。

如果调用出错则返回 -1(路径或者选项写错了)

所以 exec 函数只有出错的返回值,而没有成功的返回值。

命名理解

  • l(list):表示参数采用列表
  • v(vector):参数用数组
  • p(path):有p自动搜索环境变量PATH
  • e(env):表示自己维护环境变量

1.execl

cpp 复制代码
int execl(const char *path, const char *arg, ...);
  • path:你要执行的程序在哪里(路径/文件名)
  • ...:可变参数列表,(要执行的命令怎么写,这里就怎么写),必须以NULL结尾。
  • 部分参数可以省略,但是不建议。

直接看代码和现象:

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 
  4 
  5 int main()
  6 {
  7     printf("我变成了一个进程:%d\n", getpid());
  8 
  9 
 10     execl("/usr/bin/ls", "ls", "-a", "-l", NULL);//程序替换函数                                                                                              
 11 
 12     printf("我的代码运行中...\n");
 13     printf("我的代码运行中...\n");
 14     printf("我的代码运行中...\n");
 15     printf("我的代码运行中...\n");
 16     printf("我的代码运行中...\n");
 17     printf("我的代码运行中...\n");
 18     printf("我的代码运行中...\n");
 19     printf("我的代码运行中...\n");
 20 
 21     return 0;
 22 }

现象:

bash 复制代码
[zhangsan@hcss-ecs-f571 exec]$ ./myexec 
我变成了一个进程:22058
total 20
-rw-rw-r-- 1 zhangsan zhangsan   79 Dec 21 22:33 Makefile
-rwxrwxr-x 1 zhangsan zhangsan 8512 Dec 21 22:33 myexec
-rw-rw-r-- 1 zhangsan zhangsan  537 Dec 21 22:32 myexec.c

可以看到,execl函数直接执行ls函数,后面的打印程序的代码被覆盖了。

注意:参数列表要以NULL结尾。


下面案例同上,虽然exec在错误的时候有返回值,但是下面代码这种方式也可以直接判断,execl执行错误,就会执行exit(1),说明execl执行错误了。

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 16         execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 17         exit(1);                                                                                                                                       
 18     }
 19     int status = 0;
 20     pid_t rid = waitpid(id, &status, 0);
 21     if(rid > 0)
 22     {
 23         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 24     }
 25     
 26 
 27     return 0;
 28 }

2.execlp

cpp 复制代码
int execlp(const char *file, const char *arg, ...);
  • p(path):有p自动搜索环境变量PATH,子进程可以继承父进程的PATH。
  • file:不用带路径了,只需要程序名就可以
  • ...:可变参数列表,(要执行的命令怎么写,这里就怎么写),必须以NULL结尾。
  • 部分参数可以省略,但是不建议。

代码:

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行");
 17         execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数                                                                                           
 18        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 19        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 20         exit(1);
 21     }
 22     int status = 0;
 23     pid_t rid = waitpid(id, &status, 0);
 24     if(rid > 0)
 25     {
 26         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 27     }
 28 
 29 
 30     return 0;
 31 }

运行结果:

bash 复制代码
[zhangsan@hcss-ecs-f571 exec]$ ./myexec 
我变成了一个进程:3174
下面代码都是子进程在执行
total 28
drwxrwxr-x 2 zhangsan zhangsan 4096 Dec 22 00:17 .
drwxrwxr-x 3 zhangsan zhangsan 4096 Dec 21 22:26 ..
-rw-rw-r-- 1 zhangsan zhangsan   79 Dec 21 22:33 Makefile
-rwxrwxr-x 1 zhangsan zhangsan 8720 Dec 22 00:17 myexec
-rw-rw-r-- 1 zhangsan zhangsan  707 Dec 22 00:17 myexec.c
wait sucess, exit code:0
[zhangsan@hcss-ecs-f571 exec]$ 

3.execv

cpp 复制代码
int execv(const char *path, char *const argv[]);
  • v(vector):参数用数组
  • path:你要执行的程序在哪里(路径/文件名)

代码:

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行\n");
 17         char* const argv[]={
 18            (char*) "ls",
 19            (char*) "-a",
 20            (char*) "-l",
 21            NULL
 22         };
 23         execv("/usr/bin/ls", argv);//程序替换函数                                                                                                      
 24        // execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数
 25        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 26        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 27         exit(1);
 28     }
 29     int status = 0;
 30     pid_t rid = waitpid(id, &status, 0);
 31     if(rid > 0)
 32     {
 33         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 34     }
 35 
 36 
 37     return 0;
 38 }

这里的ls也是一个程序,也是用c写的,所以argv数组是作为main(int argc, char* argv) 函数的参数传进去的。

4.execvp

cpp 复制代码
int execvp(const char *file, char *const argv[]);
  • v(vector):参数用数组
  • p(path):有p自动搜索环境变量PATH,子进程可以继承父进程的PATH。
cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行\n");
 17         char* const argv[]={
 18            (char*) "ls",
 19            (char*) "-a",
 20            (char*) "-l",
 21            NULL
 22         };
 23 
 24         execvp(argv[0], argv);//程序替换函数                                                                                                              
 25        // execv("/usr/bin/ls", argv);//程序替换函数
 26        // execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数
 27        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 28        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 29         exit(1);
 30     }
 31     int status = 0;
 32     pid_t rid = waitpid(id, &status, 0);
 33     if(rid > 0)
 34     {
 35         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 36     }
 37 
 38 
 39     return 0;
 40 }

**问题:**能替换系统命令,能不能替换自己写的程序???

下面代码中的othercmd是自己用C++实现的程序。

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行\n");
 17         execl("./othercmd","./othercmd", NULL);//程序替换函数
 18       // char* const argv[]={
 19       //     (char*) "ls",
 20       //     (char*) "-a",
 21       //     (char*) "-l",
 22       //     (char*) "--color=auto",
 23       //     NULL
 24       //  };                                                                                                                                           
 25 
 26        // execvp(argv[0], argv);//程序替换函数
 27        // execv("/usr/bin/ls", argv);//程序替换函数
 28        // execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数
 29        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 30        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 31         exit(1);
 32     }
 33     int status = 0;
 34     pid_t rid = waitpid(id, &status, 0);
 35     if(rid > 0)
 36     {
 37         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 38     }
 39 
 40 
 41     return 0;
 42 }

运行结果:

cpp 复制代码
[zhangsan@hcss-ecs-f571 exec]$ ./myexec 
我变成了一个进程:23083
下面代码都是子进程在执行
我是自己的c++程序
wait sucess, exit code:0

同理,也可以直接替换其他语言

创建了一个test.sh的文件,里面是一下bash是命令

bash 复制代码
  1 #!/bin/bash 
  2 # echo "hello word"
  3 cnt=1                                                                                                                                                  
  4 while [ $cnt -le 10 ]
  5 do
  6     echo "hello $cnt"
  7     let cnt++
  8 done

直接用execl来程序替换

cpp 复制代码
 1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行\n");
 17         execl("/usr/bin/bash","bash","./cmd/test.sh", NULL);//程序替换函数
 18       // char* const argv[]={
 19       //     (char*) "ls",                                                                                                                             
 20       //     (char*) "-a",
 21       //     (char*) "-l",
 22       //     (char*) "--color=auto",
 23       //     NULL
 24       //  };
 25 
 26        // execvp(argv[0], argv);//程序替换函数
 27        // execv("/usr/bin/ls", argv);//程序替换函数
 28        // execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数
 29        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 30        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 31         exit(1);
 32     }
 33     int status = 0;
 34     pid_t rid = waitpid(id, &status, 0);
 35     if(rid > 0)
 36     {
 37         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 38     }
 39 
 40 
 41     return 0;
 42 }

运行结果:

bash 复制代码
[zhangsan@hcss-ecs-f571 exec]$ ./myexec 
我变成了一个进程:26397
下面代码都是子进程在执行
hello 1
hello 2
hello 3
hello 4
hello 5
hello 6
hello 7
hello 8
hello 9
hello 10
wait sucess, exit code:0

5.execvpe

cpp 复制代码
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • v(vector):参数用数组
  • p(path):有p自动搜索环境变量PATH
  • e(env):表示自己维护环境变量,可以传全新的环境变量

这里先自己实现一个c++程序

cpp 复制代码
  1 #include <iostream>
  2 #include <stdio.h>
  3 
  4 int main(int argc, char *argv[], char *env[])
  5 {
  6    for(int i = 0; i < argc;i++)
  7    {
  8        printf("argv[%d]: %s\n", i, argv[i]);
  9    }
 10    std::cout << "\r\n";                                                                                                                                
 11    for(int j = 0;env[j]; j++)
 12    {
 13        printf("env[%d]:%s\n",j, env[j]);
 14    }
 15     std::cout << "我是自己的c++程序"<< std::endl;
 16     return 0;
 17 }

这个程序可以打印出自己的参数和环境变量表

在另外一个函数中,调用上面的C++程序,并且修改环境变量

cpp 复制代码
  1 #include <stdio.h>
  2 #include <unistd.h>
  3 #include <stdlib.h>
  4 #include <sys/wait.h>
  5 #include <sys/types.h>
  6 
  7 
  8 int main()
  9 {
 10     printf("我变成了一个进程:%d\n", getpid());
 11 
 12     pid_t id = fork();
 13     if(id == 0)
 14     {
 15         sleep(2);
 16         printf("下面代码都是子进程在执行\n");
 17         char* myargv[] = {
 18            (char*) "othercmd",
 19            (char*) "-a",
 20            (char*) "-b",
 21             NULL
 22         };
 23         char* myenv[] = {
 24            (char*) "path=/home/zhangsan/learn_-linux/learn_12_19/exec/cmd"
 25            ,NULL
 26         };
 27         extern char** environ;
 28         putenv((char*)"haha=aaaaaaaaaaaaaaaaaa");
 29         execvpe("./cmd/othercmd", myargv, environ);//父亲的环境变量 + 自己的环境变量
 30         execvpe("./cmd/othercmd", myargv, myenv);//覆盖式的使用全新的环境变量表                                                                        
 31         execvpe("./cmd/othercmd", myargv, environ);// 使用父进程的环境变量表
 32         exit(3);
 33         //  execl("/usr/bin/bash","bash","./cmd/test.sh", NULL);//程序替换函数
 34       // char* const argv[]={
 35       //     (char*) "ls",
 36       //     (char*) "-a",
 37       //     (char*) "-l",
 38       //     (char*) "--color=auto",
 39       //     NULL
 40       //  };
 41 
 42        // execvp(argv[0], argv);//程序替换函数
 43        // execv("/usr/bin/ls", argv);//程序替换函数
 44        // execlp("ls", "ls",  "-a", "-l", NULL);//程序替换函数
 45        // execl("/usr/bin/ls", "-a", "-l", NULL);//程序替换函数
 46        // execl("/usr/bin/top", "top", "-d", "1", "-n", "3", NULL);
 47         exit(1);
 48     }
 49     int status = 0;
 50     pid_t rid = waitpid(id, &status, 0);
 51     if(rid > 0)
 52     {
 53         printf("wait sucess, exit code:%d\n", WEXITSTATUS(status));
 54     }
 55     
 56 
 57     return 0;
 58 }

运行结果:会发现成功设置了自己的环境变量

结论:命令行参数和环境变量表,都是父进程通过exec*传给你的。

相关推荐
多想和从前一样6 小时前
Linux 中安装 Miniconda
linux·服务器·miniconda
学习3人组6 小时前
docker run 命令详解
运维·docker·容器
get_obj6 小时前
宝塔PHP7.4安装ZIP扩展
linux·服务器·数据库
11小猪会飞116 小时前
.net使用Jenkins 构建、部署到windows服务器上
运维·jenkins
世转神风-6 小时前
Ubuntu 24.04-国内镜像源替换
linux·ubuntu
rayylee6 小时前
使用 Windows 自带 ssh 的 X11转发功能并配置 ssh 和 VSCode
linux·运维
枉费红笺6 小时前
Linux / macOS 环境下解压 ZIP 文件的标准命令与常用变体
linux·运维·macos
云游牧者6 小时前
ubuntu 22.04系统修改网卡名称方法
linux·运维·ubuntu
默|笙6 小时前
【Linux】进程控制(1)进程创建、终止
linux·运维·服务器