朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux程序替换的相关知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!
C 语 言 专 栏:C语言:从入门到精通
数据结构专栏:数据结构
个 人 主 页 :stackY、
C + + 专 栏 :C++
Linux 专 栏 :Linux
目录
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系统调用
标签:程序,pid,Linux,进程,rid,include,替换 From: https://blog.csdn.net/Yikefore/article/details/140264002朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!