【Linux 学习计划】-- 进程程序替换
目录
进城程序替换长什么样
进程程序替换原理
多进程与进程程序替换
进程程序替换方法
putenv 与 execvpe 相结合样例
结语
进城程序替换长什么样
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("process start\n");execl("/usr/bin/ls", "ls","-a","-l",NULL);printf("process end\n");return 0;
}
我们先来看如上这一串代码:
可以发现,我们写的程序居然实现出了 ls 的效果,并且我们原本会打印一个start一个end的,但是现在却是打印了 start 和执行了 ls,end 没了
而等到我们看完下文之后就会发现,这是因为整个程序在中间直接被(execl函数)替换掉了
包括代码和数据
这也就是为什么我们的end没有打印出来
进程程序替换原理
首先,进程被创建,所以有了进程地址空间、页表、task_struct,并且代码和数据都被映射到了物理内存之中
接着我们的程序执行替换之前的语句,且正常执行(在上文中就是将 start 打印出来)
然后发生了进程替换(假设替换为 ls)
因为 ls 也是一个可执行程序,他也有代码和数据且都被保存在磁盘上,程序替换时,系统就将 ls 的代码和数据全部从磁盘上面拷贝到内存中,并且将原本进程的代码和数据全部覆盖!!!
这就是进程程序替换的本质
所以我们可以知道,程序替换是没有创建新进程的,因为都是用的原进程的壳,然后把自己的代码数据覆盖进去而已
换一个角度:
站在被替换进程的角度上看,本质就是将这个进程加载到了内存中,接着跑起来
所以我们可以这么理解,excel函数,就相当于Linux上的加载函数,负责将程序从磁盘加载到内存中,并将程序调度运行起来
多进程与进程程序替换
创建进程我们用的是 fork,而创建子进程一般是有两种目的:
- 子进程执行父进程的一部分代码
- 子进程执行一个全新的程序
我们将多进程和程序替换结合起来,就是为了将子进程创建出来替换的
这个操作的上限是相当高的!!试想一下:
上面我们拿 ls 举例,ls 是程序吧,跑起来也是一个进程,而是进程就能替换
但如果今天我们用的不是ls,是我们自己写的python代码,Java代码等等脚本语言,编译成可执行程序之后,同样是变成进程跑起来,那我们同样可以使用程序替换将其变成某个程序的子进程
随着替换的增多,这就逐渐形成了一个大项目,这还不能说明他的上限吗?
而这种做法的本质是什么呢?
先是子进程被创建出来了,这时代码和数据都是只读
然后程序替换了,要覆盖原本子进程的代码和数据,就要发生修改,也就是写时拷贝
不一样的是,今天是代码和数据一起写实拷贝
并且修改完之后,页表、程序地址空间该加加,该改改
这就是多进程和程序替换的本质了
这里简单做个演示:
#!/usr/bin/bashcnt=0
while [ $cnt -le 10 ]
doecho "hello shell, cnt: ${cnt}"let cnt++
done
#!/usr/bin/python3print("hello python")
print("hello python")
print("hello python")
print("hello python")
简单写一个python一个bash
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{printf("process start\n");pid_t id = fork();if (id == 0){execl("/usr/bin/python3", "python3", "test.py", NULL);}// fahterint status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success, child exit code: %d\n", WEXITSTATUS(status));}printf("process end\n");return 0;
}
接着我们在.c文件中,使用execl 函数,将进程依次带入:
我们在上面都是调用的语言的解释器,通过解释器将文件翻译成二进制文件
当然你也可以自己手动将其变成可执行文件再替换进去
进程程序替换方法
exec *
如上 6 个函数就是我们实现进程替换的方法了
但是我们只讲 4 个,因为讲完之后,剩下的两个都是重复的,讲过的功能排列组合之后的结果罢了
execl
l 的意思,可以理解为 list ,其实就是第三个参数
先来讲第一个吧,也就是path,这里要传的是绝对或者相对路径
比如:/usr/bin/ls,记住,这里要的是找到这个程序所在的路径,目的是找到他
第二个参数表示使用方法,要用哪一个
所以如果要替换的是 ls 的话,那么第一个参数就是/usr/bin/ls,第二个就是 ls
至于第三个参数(...),叫做可变参数列表,表示具体使用的方法
比如你要用 ls,那么-a -l 就是你使用的方法
execv
这个其实和上面那个 execl 差不多,第一个参数是一样的,绝对或相对路径表示找到要替换的程序的位置
第二个参数则相当于是将上面的第二个和第三个参数全部放在一个数组里面,最后将这个数组传进去作为参数,仅此而已
execlp
这个其实和 execl 也差不多
l 代表 list,也就是第三个参数,并且这个函数和 execl 的后两个参数是完全一样的
不一样的只有第一个参数
这里我们只用直接输入你要执行的对象就可以了,比如你在execl 中要执行 ls 需要 /usr/bin/ls
但是这里不用,因为他会去环境变量中的PATH下去找,自己就能找得到
这里的p为了方便记忆,可以理解为环境变量中的 PATH
execvpe
其中,v 和 p 在上面已经讲过了,一个是将替换对象 + 方法放进数组中传进去,一个是不用加路径,函数会自己去环境变量中找
新东西就是这个 e 了,这个就是环境变量的意思
但是这里有个坑就是:
我们在里面定义的所有环境变量,会直接覆盖原本子进程的环境变量
所以我们有三种处理方法:
- 给子进程全新的环境变量
- 给子进程老的环境变量
- 修改部分老的环境变量给子进程
前两个都好说,一个全部自己写放进去,一个根本不用动
第三个我们就需要多学习一个函数了 —— putenv
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{printf("process start\n");pid_t id = fork();if (id == 0){// 1.execl("/usr/bin/ls", "ls","-a","-l",NULL);// 2.char* const argv[] = {"ls", "-a", "-l", NULL};execv("/usr/bin/ls", argv);// 3.execlp("ls", "ls","-a","-l",NULL);// 4.char* const envp[] = {"MY_ENV=hello", NULL};execvpe("ls", argv, envp);}// fahterint status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success, child exit code: %d\n", WEXITSTATUS(status));}printf("process end\n");return 0;
}
putenv 与 execvpe 相结合样例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
// 引入 environ 全局变量
extern char **environ; int main() {// 1. 先用 putenv 修改环境变量(比如追加/修改 MY_ENV)// putenv 会直接修改全局的 environ!putenv("MY_ENV=hello"); // 2. execvpe 的第三个参数直接传 environ,这样新进程会继承:// - 父进程原有所有环境变量(通过 environ 传递)// - 刚刚用 putenv 修改的变量(已经同步到 environ 里)char *argv[] = {"bash", "-c", "echo $MY_ENV; echo $PATH", NULL};execvpe("/bin/bash", argv, environ); // 执行到这说明 execvpe 失败perror("execvpe failed");return 1;
}
可以看到,最主要的是 environ 指针,这里面包含了所有的原进程的环境变量
我们在全局声明之后,直接调用putenv就能对其里面的内容进行修改
最终将environ放进函数中当第三个参数即可
结语
这篇文章到这里就结束啦!!~( ̄▽ ̄)~*
如果觉得对你有帮助的,可以多多关注一下喔