进程替换
进程替换是什么?
如下图所示:
进程替换就是,把进程B的代码和数据,替换正在执行的进程A的代码和数据在内存中的位置(若代码数据过多可能会改变页表),但进程A的整体部分不发生任何改变(task_struct、A进程地址空间等等)
其实就是用A进程的壳子执行B进程程序,不改变A进程的任何东西,只改变页表物理地址部分和内存中的数据和代码,不创建任何新的进程,并且子进程也不会退出。
替换的方法
一般用到以下六种函数
#include <unistd.h> |
---|
int exec l(const char *path, const char *arg, …); |
int exec lp(const char *file, const char *arg, …); |
int exec le(const char *path, const char *arg, …,char *const envp[]); |
int exec v(const char *path, char *const argv[]); |
int exec vp(const char *file, char *const argv[]); |
int exec ve(const char *path, char *const argv[], char *const envp[]); |
命名后缀:
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
程序运行时的环境变量信息(函数不会给你自动继承父进程的环境变量,需要手动设置)
返回值:
若替换失败则返回-1,但其实可以不用检查返回值因为:
-
调用成功一定执行替换的程序
-
调用失败一定执行原本的程序
-
int execl(const char *path, const char *arg, …);
void test1() { pid_t id = fork(); if(id == 0) { printf("你好\n"); /*********************************************开始替换******************************************/ execl("/usr/bin/ls","ls","-a","-l","-i",NULL); //你要执行谁,想怎么执行(在命令行怎么执行就怎么执行),可变参数列表以NULL结尾 //或者想要执行自己的程序 execl("./当前路径或者 /.../...绝对路径","可执行程序名",NULL); /*********************************************替换完成/失败******************************************/ printf("hello\n"); } sleep(1); printf("child exchange succeed\n"); }
- int execlp(const char *file, const char *arg, …);
void test2() { pid_t id = fork(); if(id == 0) { char* argv[] = {"ls","-a","-i","-l",NULL};//就是把可变参数列表以数组的形式传给execv printf("exchange test2--->:\n"); /*********************************************开始替换******************************************/ execv("/usr/bin/ls",argv); /*********************************************替换完成/失败******************************************/ printf("exchange fail\n"); } sleep(1); printf("exchange succeed\n"); }
- int execle(const char *path, const char *arg, …,char *const envp[]);
void test3() { pid_t id = fork(); if(id == 0) { printf("exchange test3---->\n"); /*********************************************开始替换******************************************/ execlp("ls","ls","-l","-a","-i",NULL); // 第一个你要执行的是谁但不用带路径,path会根据这个程序名去自动搜索它在什么位置,第二个是要怎么执行 /*********************************************替换完成/失败******************************************/ printf("exchange fail\n"); } sleep(1); printf("exchange succeed\n"); }
- int execvp(const char *file, char *const argv[]);
void test4() { pid_t id = fork(); if(id == 0) { printf("exchange test4---->\n"); char* argv[] = {"ls","-a","-l","-i",NULL}; /*********************************************开始替换******************************************/ execvp("ls",argv); //第一个参数告诉path要执行的程序他会自动去找路径,第二个参数从可变参数列表变为自定义数组 /*********************************************替换完成/失败******************************************/ printf("exchange fail\n"); } sleep(1); printf("exchange succeed\n"); }
- int execle(const char *path, const char *arg, …,char *const envp[]);
void test5() { pid_t id = fork(); if(id == 0) { printf("exchange test5---->\n"); /*********************************************开始替换******************************************/ char* env[] = {"my_env=hello",NULL}; execle("./print","print",NULL,env); //最后一个参数env指定了新程序的环境列表。参数env对应于新程序的environ数组 //传递自己的环境变量给print /*********************************************替换完成/失败******************************************/ printf("exchange fail\n"); } sleep(1); printf("exchange succeed\n"); } int main() { extern char** environ; for(int i = 0;environ[i];i++) { if(environ[i] == "PATH") continue;//path显示的太多,这里屏蔽掉 printf("%s\n",environ[i]); } return 0; }
- int execve(const char *path, char *const argv[], char *const envp[]);
void test6() { pid_t id = fork(); if(id == 0) { printf("exchange test5---->\n"); /*********************************************开始替换******************************************/ char* argv[] = {"print",NULL}; char* env[] = {"my_env=hello",NULL}; execve("./print",argv,env); /*********************************************替换完成/失败******************************************/ printf("exchange fail\n"); } sleep(1); printf("exchange succeed\n"); } int main() { extern char** environ; for(int i = 0;environ[i];i++) { if(environ[i] == "PATH") continue; printf("%s\n",environ[i]); } return 0; }
可以看出所有的函数都是在execve基础上封装的
进程替换
-
子进程需要替父进程执行一些任务就需要进程替换
-
进程替换只替换子进程在内存中的代码和数据,以及页表物理地址部分
-
进程替换不会创建新进程,不会退出子进程
-
虽然父子代码是共享的,但是进程替换会更改内存的代码和数据,所以要发生写实拷贝
-
fork创建子进程后,在代码中exec…只会替换子进程,因为进程具有独立性
程序替换的本质是把程序的代码数据加载到指定进程的上下文中
简易shell模拟
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <string.h>
void myshell()
{
char command[128];
char* argv[64];
while(1)
{
command[0] = 0;
printf("[awd@VM-16-4-centos myshell]----->"); //打印前缀
fflush(stdout); //刷新缓冲区
fgets(command,128,stdin); //输入命令
command[strlen(command) - 1] = 0; //先当作整个字符串存入command,-1是除去\n
//fflush(stdout);
//printf("%s\n",command);验证
const char* set = " "; //设置分隔符
argv[0] = strtok(command,set); //把字符串拆解成指令
int i = 1;
while( argv[i] = strtok(NULL,set) ) //类似strcpy,赋值到NULL退出
i++;
/*for(int j = 0;j < i;j++)
printf("%s\n",argv[j]);验证*/
if(strcmp(argv[0],"cd") == 0) //在子进程cd影响的只是子进程,所以要再父进程处理
{
if(argv[1])
chdir(argv[1]);
continue;
}
if(fork() == 0) //创建子进程
{
execvp(argv[0],argv); //替父进程执行这些指令
exit(1); //若执行到这说明替换失败,设置退出码为1
}
waitpid(-1,NULL,0); //等待任意一个子进程结束
int status = 0;
if(strcmp(argv[0],"echo") == 0 && strcmp(argv[1],"$?") == 0) //打印退出码和终止信号
printf("exit code:%d ,exit signal:%d \n",WEXITSTATUS(status),WTERMSIG(status));
}
}
int main()
{
myshell();
return 0;
}
通过这个简易shell来把之前学到的总结一下
- 一般让子进程替父进程执行一些第三方命令,那么就需要用到 进程替换和fork
- 子进程每次执行结束需要进程等待,为了结束他的僵尸进程并获取它的退出信息(退出码、终止信号)
- 每次进程退出后又会重新创建子进程,所以echo $? 查看的是最近一次执行的退出码
- 证明了每一次命令行执行的指令都是一次进程,是基于bash为父进程创建的子进程
上面这个简陋shell综合了 :fork、进程替换函数、进程等待函数、进程退出函数、退出码/终止信号,加深了这些接口的理解