1、替换原理
用fork创建子进程后执行的是和父进程相同的程序(也有可能执行不同的分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的ID并未改变。
2、替换函数:exec函数族
#include<unistd.h>
//argv[0]:可执行文件的文件名
//char *const envp[],构造环境变量
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 execve(const char *path,char *const argv[],char *const envp[]);
命名理解:
- l(list):表示参数采用列表
- v(vector):参数用数组
- p(path):有p自动搜索环境变量PATH
- e(env):表示自己维护环境变量
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,需自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,需自己组装环境变量 |
函数解释:
- 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
- 如果调用出错,则返回-1。
- exec函数只有出错的返回值,没有成功的返回值。
exec函数族关系图:
只有execve是真正的系统调用,其他的五个函数最终都通过调用execve来实现自己的功能。
3、实现一个简易的shell
首先编写一个shell,需要重复以下过程:
- 获取命令行
- 解析命令行
- 建立一个子进程(fork)
- 替换子进程(execvp)
- 父进程等待子进程(wait)
实现代码:
#include<stdio.h>
#include<unistd.h>
#include<wait.h>
#include<stdlib.h>
#include<string.h>
char *argv[8];
int argc = 0;
void do_parse(char* buf){
int i;
int status = 0;
for(argc=i=0;buf[i];i++){
if(!isspace(buf[i]) && status == 0){
argv[argc++] = buf + i;
status = 1;
}else if(isspace(buf[i])){
status = 0;
buf[i] = 0;
}
}
argv[argc] = NULL;
}
void do_execute(){
pid_t pid = fork();
if(pid > 0){
//father
int st;
while(wait(&st) != pid)
;
}else if(pid == 0){
//child
execvp(argv[0],argv);
perror("execvp");
exit(EXIT_FAILURE);
}else{
perror("fork!");
exit(EXIT_FAILURE);
}
}
int main(){
char buf[1024] = {};
while(1){
printf("myshell> ");
fflush(stdout);//刷新缓冲区
gets(buf);
do_parse(buf);//解析命令行
do_execute();//
}
return 0;
}
程序运行结果: