进程替换
- 一、概念
- 二、原理
- 三、替换函数
- 1. execl
- 2. execlp
- 3.execle
- 4.execv
- 5.execvp
- 6.execvpe
- 四、实现一个简易的shell
一、概念
当我们fork()生成子进程后,子进程的代码与数据可以来自其他可执行程序。把磁盘上其他程序的数据以覆盖的形式给子进程。这样子进程就可以执行全新的程序了,这种现象称为程序替换。
二、原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
三、替换函数
1. execl
①函数原型:
int execl(const char*path,const char*arg,...)
②参数解析:
- path:为可执行程序的路径;
- arg:如何执行这个可执行程序(执行该程序的指令);
- … :指的是给这执行程序携带的参数,在参数末尾加NULL表示参数结束;
- 返回值:函数调用失败返回-1,成功不返回
③示例:
替换成功:
#include<stdio.h>
#include<unistd.h>
int main()
{printf("before\n");execl("/usr/bin/ls","ls","-a","-l",NULL);printf("after\n");return 0;
}
替换失败:
#include<stdio.h>
#include<unistd.h>
int main()
{printf("before\n");execl("/usr/bin/la","ls","-a","-l",NULL);printf("after\n");return 0;
}
通过对比可以发现,替换成功以后,原程序后面的代码就不执行了,执行另一个程序 的代码,失败就接着执行原程序的代码。
2. execlp
①函数原型:
int execlp(const char *file, const char *arg, ...);
②参数解析:
- file:要替换的目标程序;
- arg:如何执行这个程序,…为给这个程序传的参数;
- 返回值:函数调用失败返回-1;
③示例:
比较于execl,execp默认在Linux环境变量PATH中查找可执行程序,所以传参可直接传可执行程序的名字,不用传绝对路径。
#include<stdio.h>
#include<unistd.h>
int main()
{printf("before\n");execlp("ls","ls","-a","-l",NULL);printf("after\n");return 0;
}
3.execle
①函数原型:
int execle(const char *path, const char *arg, ..., char * const envp[]);
②参数解析:
- path:替换目标程序路径;
- arg:如何执行这个程序,…为给这个程序传的参数;
- envp数组:要导入的环境变量;
- 返回值:失败返回-1;
③示例:
查看环境变量代码,用来验证后面的结果
#include<stdio.h>
#include<stdlib.h>
int main()
{printf("path:%s\n",getenv("PATH"));printf("aaa:%s\n",getenv("aaa"));return 0;
}
#include<stdio.h>
#include<unistd.h>
int main()
{char* envp[]={"aaa=hello",NULL};printf("before\n");execle("./a.out","./a.out",NULL,envp);printf("after\n");return 0;
}
注意:
- 导环境变量的数组最后以NULL结尾
- 导入环境变量后原系统环境变量的值被清空,这种导入环境变量的方式为覆盖式导入
4.execv
①函数原型:
int execv(const char *path, char *const argv[]);
②参数解析:
- path:替换目标程序路径;
- argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;
- 返回值:进程替换失败返回-1
③示例:
#include<stdio.h>
#include<unistd.h>
int main()
{char* argv[]={"ls","-a","-l",NULL};printf("before\n");execv("/usr/bin/ls",argv);printf("after\n");return 0;
}
注:可以发现argv数组与main函数的命令行参数相同
5.execvp
①函数原型:
int execvp(const char *file, char *const argv[]);
②参数解析:
- file:要替换的目标程序;
- argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;
③示例:略
6.execvpe
①函数原型:
int execvpe(const char *file, char *const argv[], char *const envp[]);
②参数解析:
- file:要替换的目标程序;
- argv数组:保存的是参数列表,将如何执行 可执行程序 和可执行程序需要的参数保存到字符串数组中,最后以NULL结尾表示参数结束;
- envp数组:要导入的环境变量;
③示例:略
小结:
替换函数前面的exec不变
- l:参数采用列表
- v:参数采用数组
- p:不需要输入路径,在环境变量自动搜索
- e:要导入自己的环境变量
所以execvp表示不需要输入路径,参数用数组传
execve表示需要输入路径,参数用数组传,自己维护环境变量
四、实现一个简易的shell
#include<stdio.h>
#include<unistd.h>
#include<assert.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<string.h>
#define MAX 1024
#define ARGC 64
#define SEP " "//将输入的字符串切割并保存到argv中
int split(char*commandstr,char*argv[])
{assert(commandstr);assert(argv);argv[0]=strtok(commandstr,SEP);if(argv[0]==NULL)return -1; //若为NULL,则重新输入int i=1;while(argv[i++]=strtok(NULL,SEP));return 0;
}
int main()
{ while(1){char commandstr[MAX]={NULL}; //用于保存用户输入的指令char*argv[ARGC]={NULL};printf("[lx@hecs-%d myshell]$ ",getpid());fflush(stdout);char*s=fgets(commandstr,sizeof(commandstr),stdin); //获取指令assert(s);(void)s;commandstr[strlen(commandstr)-1]='\0'; //去掉键盘输入的'\n'int n=split(commandstr,argv); //切割输入的指令字符串if(n!=0)continue;pid_t id=fork();if(id==0){execvp(argv[0],argv); //程序替换exit(0);}int status=0;waitpid(id,&status,0); //等待子进程}return 0;
}
可以看到简易版的myshell就实现好了。
myshellplus
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <assert.h>#define MAX 1024
#define ARGC 64
#define SEP " "int split(char* commandstr,char* argv[])
{assert(commandstr);assert(argv);argv[0] = strtok(commandstr,SEP);if(argv[0] == NULL) return -1;int i = 1;while((argv[i++] = strtok(NULL,SEP)));return 0;
}void showEnv()
{extern char** environ;for(int i = 0; environ[i]; i++) printf("%d:%s\n",i,environ[i]);
}int main()
{extern int putenv(char* string);char myenv[32][256];int env_index = 0;int exitCode = 0;while(1){char commandstr[MAX] = {0};char* argv[ARGC] = {NULL};printf("[hxy@mychaimachine]$ ");fflush(stdout);char* s = fgets(commandstr,sizeof(commandstr),stdin);assert(s);(void)s;commandstr[strlen(commandstr) - 1] = '\0'; // 去掉键盘输入的\nint n = split(commandstr,argv); // 切割字符串if(n != 0) continue;if(strcmp(argv[0],"cd") == 0){if(argv[1] != NULL) chdir(argv[1]);continue;}else if(strcmp(argv[0],"export") == 0){if(argv[1] != NULL){strcpy(myenv[env_index],argv[1]); // 用户自己定义的环境变量,需要bash自己来维护putenv(myenv[env_index++]);}continue;}else if(strcmp(argv[0],"env") == 0){showEnv(); // env查看环境变量时,其实看的是父进程bash的变量continue;}else if(strcmp(argv[0],"echo") == 0){const char* target_env = NULL;if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n",exitCode);continue;} else target_env = getenv(argv[1] + 1);if(target_env != NULL) printf("%s = %s\n",argv[1] + 1,target_env);}continue;}// ls设置颜色选项if(strcmp(argv[0],"ls") == 0){int pos = 0;while(argv[pos] != NULL){pos++;}argv[pos++] = (char*)"--color=auto";argv[pos] = NULL;}pid_t id = fork();if(id == 0){// 子进程execvp(argv[0],argv);exit(1);}int status = 0;pid_t ret = waitpid(id,&status,0);if(ret > 0){exitCode = WEXITSTATUS(status); // 获取最近一次进程的退出码}}return 0;
}
进程替换的知识就讲到这了,如有错误还望指出,886!!!