摘要
本章主要是说一下进程替代用到的函数exec以及自己实现的简易shell
一、进程程序替换
1、替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
也就是说可以利用一个函数进行控制另外一个函数,就像可以利用c语言写一个程序从而控制c++的程序,c++的写法也不需要我会,只需要给一个参数的传递,c++那边用自己的语法进行去编写就可以利用c语言进行控制了。
2 、替换函数
其实有六种以exec开头的函数,统称exec函数,这里是直接利用man指令进行查看,这里我是使用man 3 exec进行查看,但是刚开始我没有找到,然后发现是因为没有安装库函数包,下面这行代码是进行安装的。
sudo yum install man-pages
3、函数解释与命令理解
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。
命名理解如下:
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
4、代码演示
1、execl
如上方函数的execl的使用如下方代码所示就是,个人上方手册查到的函数用法,execl就是需要绝对路径去进行访问使用,也就是环境变量,然后后面是指令使用,这里是利用ls指令进行演示的,
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/wait.h> 5 6 int main() 7 { 8 pid_t id=fork(); 9 if(id==0) 10 { //子进程 11 printf("子进程开始运行,pid:%d\n",getpid()); 12 sleep(1); 13 execl("/usr/bin/ls","ls","-a","-l",NULL); 14 exit(1); 15 } 16 else 17 { //父进程 18 printf("父进程开始运行,pid:%d\n",getpid()); 19 int status=0; 20 pid_t id=waitpid(-1,&status,0);//阻塞等待,等到子进程死去,得到他的错误码 21 if(id>0) 22 { 23 printf("wait success,exit code:%d\n",WEXITSTATUS(status)); 24 } 25 } 26 return 0; 27 } ~
2、execv
这个函数后面是可以利用数组进行访问这个这个命令的指令,也就是可以用数组进行匹配使用。
8 int main() 9 { 10 pid_t id=fork(); 11 if(id==0) 12 { //子进程 13 printf("子进程开始运行,pid:%d\n",getpid()); 14 sleep(1); 15 char *const _argv[NUM]= 16 { 17 (char*)"-ls", 18 (char*)"-a", 19 (char*)"-l", 20 NULL 21 }; 22 // execl("/usr/bin/ls","ls","-a","-l",NULL); 23 execv("/usr/bin/ls",_argv); 24 exit(1); 25 } 26 else 27 { //父进程 28 printf("父进程开始运行,pid:%d\n",getpid()); 29 int status=0; 30 pid_t id=waitpid(-1,&status,0);//阻塞等待,等到子进程死去,得到他的错误码 31 if(id>0) 32 { 33 printf("wait success,exit code:%d\n",WEXITSTATUS(status)); 34 } 35 } 36 return 0; 37 } ~
3、execlp
下方这块代码和execl使用方式差不多,但是有一点不同,就是环境变量的寻找这个不需要绝对路径。
int main() 9 { 10 pid_t id=fork(); 11 if(id==0) 12 { //子进程 13 printf("子进程开始运行,pid:%d\n",getpid()); 14 sleep(1); W> 15 char *const _argv[NUM]= 16 { 17 (char*)"-ls", 18 (char*)"-a", 19 (char*)"-l", 20 NULL 21 }; 22 //execl("/usr/bin/ls","ls","-a","-l",NULL); 23 execlp("ls","ls","-a","-l",NULL); 24 //execv("/usr/bin/ls",_argv); 25 exit(1); 26 } 27 else 28 { //父进程 29 printf("父进程开始运行,pid:%d\n",getpid()); 30 int status=0; 31 pid_t id=waitpid(-1,&status,0);//阻塞等待,等到子进程死去,得到他的错误码 32 if(id>0) 33 { 34 printf("wait success,exit code:%d\n",WEXITSTATUS(status)); 35 } 36 } 37 return 0; 38 }
4、execvp
这里和上面execl差不多,但是也只需要相对路径就可以了,这里就演示了四个,后面两个用法就是按照手册进行更改就可以了。
8 int main() 9 { 10 pid_t id=fork(); 11 if(id==0) 12 { //子进程 13 printf("子进程开始运行,pid:%d\n",getpid()); 14 sleep(1); 15 char *const _argv[NUM]= 16 { 17 (char*)"-ls", 18 (char*)"-a", 19 (char*)"-l", 20 NULL 21 }; 22 //execl("/usr/bin/ls","ls","-a","-l",NULL); 23 //execlp("ls","ls","-a","-l",NULL); 24 //execv("/usr/bin/ls",_argv); 25 execvp("ls",_argv); 26 exit(1); 27 } 28 else 29 { //父进程 30 printf("父进程开始运行,pid:%d\n",getpid()); 31 int status=0; 32 pid_t id=waitpid(-1,&status,0);//阻塞等待,等到子进程死去,得到他的错误码 33 if(id>0) 34 { 35 printf("wait success,exit code:%d\n",WEXITSTATUS(status)); 36 } 37 } 38 return 0; 39 }
二、简易的myshell
这里将实现一个简易的shell,shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。所以要写一个shell,需要循环以下过程:
1. 获取命令行
2. 解析命令行
3. 建立一个子进程(fork)
4. 替换子进程(execvp)
5. 父进程等待子进程退出(wait)
从而就可以拆分出下面几步
1. 打印出提示信息
2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"(这里完全可以利用strtok这个函数进行拆分字符串)
4. TODO,内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
5. fork()
也就是对应上面的五个过程,代码如下测试如下。
#include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <sys/wait.h> 5 #include <string.h> 6 #include <sys/types.h> 7 8 #define NUM 1024 9 #define SIZE 32 10 #define SEP " " 11 12 char cmd_line[NUM]; 13 char* g_argv[SIZE]; 14 15 int main() 16 { 17 while(1) 18 { //1、打印出信息提示 19 printf("[root@localhost myshell]#"); 20 fflush(stdout); 21 memset(cmd_line,'\0',sizeof cmd_line); 22 //2、获取从键盘输入的各种数据 23 if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL) 24 { 25 continue; 26 } 27 cmd_line[strlen(cmd_line)-1]='\0'; 28 //3、命令字符串进行解析 29 g_argv[0]=strtok(cmd_line,SEP);//第一调用,需要首先获取原始字符串 30 int index=1; 31 if(strcmp(g_argv[0], "ls") == 0) 32 { W> 33 g_argv[index++] = "--color=auto"; 34 } 35 if(strcmp(g_argv[0], "ll") == 0) 36 { W> 37 g_argv[0] = "ls"; W> 38 g_argv[index++] = "-l"; W> 39 g_argv[index++] = "--color=auto"; 40 } W> 41 while(g_argv[index++]=strtok(NULL,SEP)); 42 if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute 43 { 44 if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd .. 45 46 continue; 47 } 48 pid_t id=fork(); 49 if(id==0) 50 { 51 printf("下面功能让子进程进程的\n"); 52 execvp(g_argv[0],g_argv); 53 exit(1); 54 } 55 int status=0; 56 pid_t ret=waitpid(id,&status,0); 57 if(ret>0)printf("exit code:%d\n",WEXITSTATUS(status)); 58 59 } 60 return 0; 61 62 }
三、代码
myshell
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <string.h> #include <sys/types.h> #define NUM 1024 #define SIZE 32 #define SEP " " char cmd_line[NUM]; char* g_argv[SIZE]; int main() { while(1) { //1、打印出信息提示 printf("[root@localhost myshell]#"); fflush(stdout); memset(cmd_line,'\0',sizeof cmd_line); //2、获取从键盘输入的各种数据 if(fgets(cmd_line,sizeof cmd_line,stdin)==NULL) { continue; } cmd_line[strlen(cmd_line)-1]='\0'; //3、命令字符串进行解析 g_argv[0]=strtok(cmd_line,SEP);//第一调用,需要首先获取原始字符串 int index=1; if(strcmp(g_argv[0], "ls") == 0) { g_argv[index++] = "--color=auto"; } if(strcmp(g_argv[0], "ll") == 0) { g_argv[0] = "ls"; g_argv[index++] = "-l"; g_argv[index++] = "--color=auto"; } while(g_argv[index++]=strtok(NULL,SEP)); if(strcmp(g_argv[2], "cd") == 0) //not child execute, father execute { if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd .. continue; } pid_t id=fork(); if(id==0) { printf("下面功能让子进程进程的\n"); execvp(g_argv[0],g_argv); exit(1); } int status=0; pid_t ret=waitpid(id,&status,0); if(ret>0)printf("exit code:%d\n",WEXITSTATUS(status)); } return 0; }
exec
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #define NUM 10 int main() { pid_t id=fork(); if(id==0) { //子进程 printf("子进程开始运行,pid:%d\n",getpid()); sleep(1); char *const _argv[NUM]= { (char*)"-ls", (char*)"-a", (char*)"-l", NULL }; //execl("/usr/bin/ls","ls","-a","-l",NULL); //execlp("ls","ls","-a","-l",NULL); //execv("/usr/bin/ls",_argv); execvp("ls",_argv); exit(1); } else { //父进程 printf("父进程开始运行,pid:%d\n",getpid()); int status=0; pid_t id=waitpid(-1,&status,0);//阻塞等待,等到子进程死去,得到他的错误码 if(id>0) { printf("wait success,exit code:%d\n",WEXITSTATUS(status)); } } return 0; }