目录
进程替换是什么?
进程替换需要怎样操作?
替换函数
命名理解
不创建子进程进行进程替换
关于替换程序时的写时拷贝
fork创建子进程进行替换
函数1:execl
函数2:execv
函数3:execlp
函数4:execvp
函数5:execle
进程替换是什么?
父进程fork()创建子进程之后,父子各自执行父进程代码的一部分
父子进程代码共享,数据写时拷贝各自一份
那么此时,子进程想执行一个全新的程序呢? 子进程想有自己的代码呢??应该怎么办?
此时就需要进程替换,来完成这个功能。
官方的说法是:程序替换,是通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用进程的地址空间中!
可参考:进程的创建 程序地址空间
fork常规用法
一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。---就叫做程序替换
图例:
- 进程替换,有没有创建新的子进程? 没有!
- 那么如何理解所谓的将程序放入内存中! 是通过加载!
- 所谓的exec*函数本质,就是如何加载程序的函数!
- 编译有编译器,加载也有加载器注意磁盘和内存都是硬件,那么加载的时候,系统会提供相关函数来进行把磁盘的数据搬到内存中就是加载
进程替换需要怎样操作?
a.不创建子进程进行进程替换(无意义,主要用于演示)
b.fork创建子进程进行替换
替换函数
EXEC(3) Linux Programmer's Manual EXEC(3) NAME execl, execlp, execle, execv, execvp, execvpe - execute a file SYNOPSIS #include <unistd.h> extern char **environ; 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[]); Feature Test Macro Requirements for glibc (see feature_test_macros(7)): execvpe(): _GNU_SOURCE
命名理解
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
示例:
函数1:execl
l:参数是列表
int execl(const char *path, const char *arg, ...); path :路径+目标文件名 arg :参数包,包含了若干个参数 ... :可变参数列表(最后一个参数为NULL,标识参数传递完毕)
不创建子进程进行进程替换
示例:
当前未进行程序替换:
[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("\n"); printf("****************process begin*************\n"); printf("hello world\n"); printf("*****************process end**************\n"); printf("\n"); return 0; } [wxq@VM-4-9-centos code_4_10_2]$ ./test ****************process begin************* hello world *****************process end************** [wxq@VM-4-9-centos code_4_10_2]$
进行程序替换①:
[wxq@VM-4-9-centos code_4_10_2]$ make gcc -o test process_replace.c [wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("\n"); printf("****************process begin*************\n"); execl("/usr/bin/ls", "ls", "-l",NULL); printf("hello world\n"); printf("*****************process end**************\n"); printf("\n"); return 0; } [wxq@VM-4-9-centos code_4_10_2]$ ./test ****************process begin************* total 20 -rw-rw-r-- 1 wxq wxq 69 Apr 10 19:46 Makefile -rw-rw-r-- 1 wxq wxq 321 Apr 10 20:54 process_replace.c -rwxrwxr-x 1 wxq wxq 8472 Apr 10 20:54 test [wxq@VM-4-9-centos code_4_10_2]$
进行程序替换②:
[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { printf("\n"); printf("****************process begin*************\n"); // execl("/usr/bin/ls", "ls", "-l",NULL); execl("/usr/bin/top", "top",NULL); printf("hello world\n"); printf("*****************process end**************\n"); printf("\n"); return 0; } [wxq@VM-4-9-centos code_4_10_2]$ ./test ****************process begin************* top - 20:57:31 up 220 days, 23:12, 3 users, load average: 0.01, 0.03, 0.05 Tasks: 126 total, 1 running, 120 sleeping, 0 stopped, 5 zombie %Cpu(s): 0.5 us, 0.3 sy, 0.0 ni, 99.0 id, 0.2 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 2046504 total, 152152 free, 279976 used, 1614376 buff/cache KiB Swap: 0 total, 0 free, 0 used. 1568788 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 473 root 20 0 973744 36212 16904 S 1.3 1.8 11:39.93 YDService 20006 root 20 0 757184 17520 2624 S 0.3 0.9 671:00.83 barad_agent 1 root 20 0 43728 3916 2436 S 0.0 0.2 21:57.28 systemd 2 root 20 0 0 0 0 S 0.0 0.0 0:08.15 kthreadd 4 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H 6 root 20 0 0 0 0 S 0.0 0.0 3:17.70 ksoftirqd/0 7 root rt 0 0 0 0 S 0.0 0.0 1:11.94 migration/0 8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh 9 root 20 0 0 0 0 S 0.0 0.0 51:58.18 rcu_sched 10 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 lru-add-drain 11 root rt 0 0 0 0 S 0.0 0.0 0:52.50 watchdog/0 12 root rt 0 0 0 0 S 0.0 0.0 0:45.59 watchdog/1 13 root rt 0 0 0 0 S 0.0 0.0 1:11.67 migration/1 14 root 20 0 0 0 0 S 0.0 0.0 2:59.24 ksoftirqd/1 16 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/1:0H 18 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kdevtmpfs 19 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 netns 20 root 20 0 0 0 0 S 0.0 0.0 0:04.50 khungtaskd 21 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 writeback 22 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kintegrityd 23 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset 24 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset 25 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 bioset [wxq@VM-4-9-centos code_4_10_2]$
注意:可变模板参数最后一定要传NULL,不然会导致程序替换失败
这里有一个问题,为什么???这里的内容没有被显示在屏幕上???
- 其实,这里的命令根本就没有被执行。
- 原因是execl是程序替换,调用该函数成功之后,会将当前进程的所有代码以及数据都进行替换。包括已经执行的和未执行的!
- 所以,—旦调用成功,后续的所有代码,都不会执行。
- execl为什么调用成功,没有返回值呢?(注:失败,返回-1,继续执行原进程的后续代码)
- execl根本不需要返回值进行判断,可以理解为它在替换的过程中,把自己都干掉了,所以产生的新代码和它没有关系
关于替换程序时的写时拷贝
其实这里还涉及到一个写时拷贝的问题:在之前的博客中提及过,父进程创建子进程,代码是共享的,地址空间也是共享的,因为代码是不能更改的,而数据写时拷贝。那么发生了进程替换,父子进程还是共用一块代码,一个地址空间吗???那不就乱套了吗?所以我们需要捋顺这一观点:
首先:
父进程创建子进程,如果父进程或者子进程对数据进行写入,那么操作系统就会发生写时拷贝。
此时代码不会进行写时拷贝,代码是不可更改的,因为代码存储在常量区,父进程或者子进程只有读取的权限
(此时:代码共享,数据写时拷贝。父子进程共用虚拟地址空间)
其次:
如果子进程发生替换程序,意味着新程序会从磁盘往内存中加载,这不就是写入吗?
此时,代码会不会发生写实拷贝,答案是肯定,因为要保证进程的独立性,此时,父子的代码必须要分离
(此时:代码,数据写时拷贝,重新给子进程建立虚拟地址空间)
fork创建子进程进行替换
- 用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支)
- 子进程往往要调用一种exec函数以执行另一个程序
- 当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动进程开始执行。
- 调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
示例:
函数1:execl
-----------------------------------------------测试代码process_replace.c (下面的测试代码脚本都是这个)-------------------------------------
[wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> int main() { pid_t id = fork(); if( id == 0 ) { //child process printf("子进程开始运行, pid:%d\n", getpid()); execl("/usr/bin/ls", "ls", "-l", NULL); exit(1); } else { printf("父进程开始运行, pid:%d\n", getppid()); int status = 0; pid_t ret = waitpid(-1, &status, 0); //-1 :等待任意子进程 if(ret > 0) { printf("waitpid success! exit code: %d\n", WEXITSTATUS(status)); } } return 0; } [wxq@VM-4-9-centos code_4_10_2]$ make gcc -o test process_replace.c [wxq@VM-4-9-centos code_4_10_2]$ ./test 父进程开始运行, pid:16226 子进程开始运行, pid:29294 total 20 -rw-rw-r-- 1 wxq wxq 69 Apr 10 19:46 Makefile -rw-rw-r-- 1 wxq wxq 985 Apr 10 21:26 process_replace.c -rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:26 test waitpid success! exit code: 0
观察输出可以发现,其实父进程或者子进程的运行顺序是由操作系统决定的,但是,父进程一定会等待子进程运行结束。
函数2:execv
v:表示参数是数组
execv和execl只有传参上的区别
int execv(const char *path, char *const argv[]);
图例:
示例:
输入:
//参数是列表形式
// execl("/usr/bin/ls", "ls", "-l", NULL);
char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};
// 参数的数组形式
execv("/usr/bin/ls", _argv);
输出:
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test
父进程开始运行, pid:16226
子进程开始运行, pid:1093
total 20
-rw-rw-r-- 1 wxq wxq 69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1127 Apr 10 21:41 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:41 test
waitpid success! exit code: 0
函数3:execlp
p:我会去自己的环境变量PATH中进行查找,你不用告诉我你要执行的程序在哪
补:执行程序需要带路径吗?答案是需要的,但是系统的程序是不需要的的,就是因为环境变量。详细可以参考:Linux的环境变量
int execlp(const char *file, const char *arg, ...);
示例:
输入:
//参数是列表形式
// execl("/usr/bin/ls", "ls", "-l", NULL);
// char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};
// 参数的数组形式
// execv("/usr/bin/ls", _argv);
//不需要传入路径
execlp("ls", "ls", "-a", "-l", NULL);
输出:
[wxq@VM-4-9-centos code_4_10_2]$ vim process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test
父进程开始运行, pid:16226
子进程开始运行, pid:4772
total 28
drwxrwxr-x 2 wxq wxq 4096 Apr 10 21:55 .
drwxrwxr-x 8 wxq wxq 4096 Apr 10 19:42 ..
-rw-rw-r-- 1 wxq wxq 69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1258 Apr 10 21:53 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 21:55 test
waitpid success! exit code: 0
[wxq@VM-4-9-centos code_4_10_2]$
其实这里有个小概念需要明白,p不是代表不需要传入路径吗?那为什么还要:输入 "ls", "ls" ,"-a", "-l" NULL
你不是告诉我,execlp,p不是表示path,不是说不需要带路径吗?为什么这里还需要写第一个"ls"???
图例:
函数4:execvp
那这个不就是很好理解了嘛,v表示参数是数组,p表示不需要传入路径
int execvp(const char *file, char *const argv[]);
示例:
输入:
char* _argv[32] = {(char*)"ls", (char*)"-l", NULL};
//参数是列表形式
// execl("/usr/bin/ls", "ls", "-l", NULL);
// 参数的数组形式
// execv("/usr/bin/ls", _argv);
//不需要传入路径
//execlp("ls", "ls", "-a", "-l", NULL);
execvp("ls", _argv);
输出:
[wxq@VM-4-9-centos code_4_10_2]$ make
gcc -o test process_replace.c
[wxq@VM-4-9-centos code_4_10_2]$ ./test
父进程开始运行, pid:16226
子进程开始运行, pid:7646
total 20
-rw-rw-r-- 1 wxq wxq 69 Apr 10 19:46 Makefile
-rw-rw-r-- 1 wxq wxq 1287 Apr 10 22:05 process_replace.c
-rwxrwxr-x 1 wxq wxq 8680 Apr 10 22:06 test
waitpid success! exit code: 0
[wxq@VM-4-9-centos code_4_10_2]$
函数5:execle
int execle(const char *path, const char *arg, ..., char * const envp[]);
这个其实是有点上难度的,因为这里的 e 表示的是环境变量
怎么去描述这个函数呢???
咱先描述另外一个东西再来带入到这个函数。
比如说,我们上面执行的都是系统的命令,假如程序替换我想替换一个我自己写的程序行不行呢?
示例:
我们自己写一个程序 mycmd
通过程序 test (process_replace.c) 创建子进程去调用我们自己写的程序mycmd
[wxq@VM-4-9-centos code_4_10_2]$ ll total 36 -rw-rw-r-- 1 wxq wxq 125 Apr 10 22:25 Makefile -rwxrwxr-x 1 wxq wxq 8456 Apr 10 22:32 mycmd -rw-rw-r-- 1 wxq wxq 496 Apr 10 22:32 mycmd.c -rw-rw-r-- 1 wxq wxq 1287 Apr 10 22:05 process_replace.c -rwxrwxr-x 1 wxq wxq 8680 Apr 10 22:06 test [wxq@VM-4-9-centos code_4_10_2]$ cat mycmd.c #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char* argv[]) { if(argc != 2) { printf("can not execute!\n"); exit(-1); } else if(strcmp(argv[1], "-a") == 0) { printf("hello a!\n"); } else if(strcmp(argv[1], "-b") == 0) { printf("hello b!\n"); } else if(strcmp(argv[1], "-c") == 0) { printf("hello c!\n"); } else { printf("default!\n"); } return 0; } [wxq@VM-4-9-centos code_4_10_2]$ cat process_replace.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> int main() { pid_t id = fork(); if( id == 0 ) { //child process printf("子进程开始运行, pid:%d\n", getpid()); char* _argv[32] = {(char*)"ls", (char*)"-l", NULL}; //参数是列表形式 // execl("/usr/bin/ls", "ls", "-l", NULL); // 参数的数组形式 // execv("/usr/bin/ls", _argv); //不需要传入路径 //execlp("ls", "ls", "-a", "-l", NULL); // execvp("ls", _argv); execl("/home/wxq/study_4/code_4_10_2/mycmd", "mycmd", "-a", NULL); exit(1); } else { printf("父进程开始运行, pid:%d\n", getppid()); int status = 0; pid_t ret = waitpid(-1, &status, 0); //-1 :等待任意子进程 if(ret > 0) { printf("waitpid success! exit code: %d\n", WEXITSTATUS(status)); } } return 0; } [wxq@VM-4-9-centos code_4_10_2]$ make gcc -o test process_replace.c [wxq@VM-4-9-centos code_4_10_2]$ ./test 父进程开始运行, pid:16226 子进程开始运行, pid:15842 hello a! waitpid success! exit code: 0
通过上面的测试,我们可以看到,我们自己写的程序是可以被程序替换,并且成功执行的。
上述逻辑:
ok,现在有了这个基础,我们再来聊一聊execle这个函数
接下来写的东西围绕着(execle函数在进行程序替换的时候,可以把环境变量传递到我们想要执行的程序里面)
意思就是我们在 process_replace.c中设置一个环境变量,然后传递给mycmd,看看mycmd是不是能使用这个环境变量。
第一步:
因为这里我们并没有设置环境变量,也没有传入,所以这里为空
第二步:在 process_replace.c 中添加环境变量,再使用execle进行程序替换
所以函数execle不仅调用了程序mycmd还给mycmd传入了环境变量
但是从严格意义上来说,上述关于程序替换的接口并不是操作系统提供给我们的,操作系统提供的接口只有一个:execve
NAME execve - execute program SYNOPSIS #include <unistd.h> int execve(const char *filename, char *const argv[], char *const envp[]);
这6个接口是经过封装提供给我们的,为的是满足不同的调用场景。底层都是execve这个接口
NAME
execl, execlp, execle, execv, execvp, execvpe - execute a file