
一、程序替换1.1 进程替换概念当我们进行fork()创建子进程时其作用是创建一个子进程子进程拷贝了父进程的代码和数据继续执行父进程接下来的代码。而我们创建子进程不全是为了执行与父进程同样的代码这时候就要用到程序替换让子进程调用exec函数来执行另一个程序子进程的代码和数据会完全被新程序进行覆盖替换从新程序起始历程开始执行。1.2 进程替换的原理当我们调用exec系列函数时内核会销毁当前进程原有的代码、数据、堆与栈再将新程序的代码、数据以及环境变量加载到进程地址空间。整个过程仅替换进程的用户空间内容进程内核描述符本身没有改动因此进程PID保持不变。1.3 六种替换函数替换函数有六种exec开头的函数统称为exec函数#include unistd.h 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[]);函数返回值解释函数执行成功时不会有任何返回值会继续进行程序的运行而函数执行失败时则会返回-1所以函数只会在执行失败时才会返回-1值。函数命名理解llist表示参数使用列表vvector表示参数使用数组ppath有p会自动搜寻环境变量PATHeenv表示自己维护环境变量而事实上在exec系列函数中只有execve是内核提供的真正系统调用其余五个均为C库封装的函数它们只是在参数形式上做了适配最终都会调用execve来完成进程替换。二、简单shell命令行解释器我们所写的简单shell命令行解释器有几个关键的函数PrintCommandlist函数、GetCommand函数、GetCommand函数、CheckBuildinexecute函数、ExecuteCommand函数。1 #includestdio.h 2 #includestdlib.h 3 #includestring.h 4 #includeunistd.h 5 #includesys/wait.h 6 #includesys/types.h 7 #includeiostream 8 #includestring 9 10 #define MAXSIZE 128 11 #define MAXGARGC 30 12 13 char *gargv[MAXGARGC]; 14 int gargc 0; 15 const char* sec ; 16 17 char cwd[1024]; 18 19 static std::string rFindDir(const std::string p) 20 { 21 if(p /) 22 { 23 return p; 24 } 25 std::string psep /; 26 auto pos p.rfind(psep); 27 if(pos std::string::npos) 28 { 29 return std::string(); 30 } 31 return p.substr(pos1); 32 } 33 34 const char* GetUsername() 35 { 36 char *name getenv(USER); 37 if(name NULL) 38 { 39 return None; 40 } 41 return name; 42 } 43 44 const char* GetHostname() 45 { 46 char *name getenv(HOSTNAME); 47 if(name NULL) 48 { 49 return None; 50 } 51 return name; 52 } 53 54 const char* GetPwd() 55 { 56 char *name getenv(PWD); 57 if(name NULL) 58 { 59 return None; 60 } 61 return name; 62 } 63 64 void PrintCommandlist() 65 { 66 printf([%s%s %s]#, GetUsername(), GetHostname(), rFindDir(GetPwd()).c_str()); 67 fflush(stdout); 68 } 69 70 int GetCommand(char commandline[], int size) 71 { 72 if(NULLfgets(commandline, size, stdin)) 73 { 74 return 0; 75 } 76 commandline[strlen(commandline) - 1] \0; 77 return strlen(commandline); 78 } 79 80 void ParseCommand(char commandline[]) 81 { 82 gargc 0; 83 memset(gargv, 0, sizeof(gargv)); 84 gargv[0] strtok(commandline, sec); 85 while(gargv[gargc] strtok(NULL, sec)); 86 87 //int i 0; 88 //for(; i gargc; i) 89 // printf(gragv[%d]:%s\n, i, gargv[i]); 90 } 91 92 int CheckBuildinexecute() 93 { 94 if(strcmp(gargv[0], cd) 0) 95 { 96 if(gargc 2) 97 { 98 //修改当前路径 99 chdir(gargv[1]); 100 //还要修改环境变量 101 char pwd[1024]; 102 getcwd(pwd, sizeof(pwd)); 103 snprintf(cwd, sizeof(cwd), PWD%s, pwd); 104 putenv(cwd); 105 } 106 return 1; 107 } 108 return 0; 109 } 110 111 int ExecuteCommand() 112 { 113 if(gargv[0] NULL) 114 { 115 return 0; 116 } 117 pid_t id fork(); 118 if(id 0) 119 { 120 printf(进程创建失败\n); 121 return -1; 122 } 123 else if(id 0) 124 { 125 execvp(gargv[0], gargv); 126 exit(1); 127 } 128 else 129 { 130 int status 0; 131 pid_t rid waitpid(id, status, 0); 132 } 133 return 0; 134 } 135 136 int main() 137 { 138 char command_line[MAXSIZE] {0}; 139 while(1) 140 { 141 //1.命令行模拟 142 PrintCommandlist(); 143 if(GetCommand(command_line,sizeof(command_line)) 0) 144 continue; 145 //usleep(100000); 146 //3.解释器 147 ParseCommand(command_line); 148 //4.内建命令需要父进程执行 149 if(CheckBuildinexecute()) 150 { 151 continue; 152 } 153 //5.普通命令 154 ExecuteCommand(); 155 } 156 return 0; 157 }2.1 核心函数介绍PrintCommandlist打印自定义 shell 提示符格式为[用户名主机名 当前目录]#并刷新标准输出等待用户输入命令。GetCommand使用fgets读取用户输入的一行命令去除末尾换行符返回命令字符串长度读取失败返回 0。ParseCommand利用strtok对输入的命令字符串按空格分割填充全局参数数组gargv统计参数个数gargc完成命令词解析。CheckBuildinexecute判断是否为内建命令仅实现了cd内建命令直接在父进程执行调用chdir切换目录并更新 PWD 环境变量是内建命令返回 1否则返回 0。ExecuteCommand执行外部命令调用fork创建子进程子进程通过execvp完成进程替换运行目标程序父进程调用waitpid阻塞等待子进程执行完毕。2.2 简易shell优缺点该简易 shell 命令行解释器缺少多项核心功能未实现 exit 退出命令cd 命令缺少无参数、~ 家目录等场景处理不支持管道、输入输出重定向与后台任务无法处理多空格、引号等复杂命令解析缺少信号处理CtrlC 会中断 shell 本身同时缺少命令历史、Tab 补全错误提示也不够完善。但该简易shell命令行解释器虽功能有限却帮我们更系统地理解命令行解释器的基础执行流程读取用户输入、解析命令、区分内建命令与外部命令借助fork创建子进程配合exec完成进程替换同时等待子进程结束直观展现了shell的核心工作原理。