问题描述
探索、理解并掌握操作系统命令释器的设计原和实现机制,基于 Linux内核进行相应命令解释程序的设计和实现,并在 Linux操作平台上加以实现。
实验要求
Linux 命令解释程序功能设计要求:
(1)选取和设计实现一组内部命令(五条以上);
(2)外部命令执行采用直接调用 exec 系统调用的方式来实现;
(3)至少一条内部命令采用直接调用相应系统调用的方式来实现;
(4)系统环境变量(至少包括当前目录)支持;
(5)在 Linux 操作系统上启用(或替换原命令解释程序 Shell)并测试验证。
项目结构
函数一览
1 2 3 4 5 6 7 8 9 10 11
| void print_welcome(); void split(char *src,const char *separator,char **dest,int *num); void my_cd(char **arr); void my_pwd(char **arr); void my_echo(); void my_exit(); HQueue init_history(HQueue q); HQueue add_history(HQueue q,int num); void my_history(HQueue q); int check_env(char **arr); void my_outer(char **arr);
|
数据结构和全局变量定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| int IS_STOP=0; char cmd[256]; char historycmd[256]; char *arr[128]; char *env[256]={"/home","/usr"}; int env_N=2;
typedef struct HistoryNode { char cmd[256]; struct HistoryNode *next; }HNode;
typedef struct HistoryQueue { HNode *head,*tail; int num; }HQueue;
|
详细设计
内部命令cd的设计
cd命令的作用是切换工作目录,直接使用了系统调用,设计如下:
1 2 3 4 5 6 7 8 9
| void my_cd(char **arr) { if (arr[1]&&!syscall(SYS_access,arr[1],0)) { syscall(SYS_chdir,arr[1]); } else { printf("目录不存在!\n"); } }
|
该函数使用了syscall 来调用系统调用SYS_access 来判断目录是否存在,以及使用了系统调用 SYS_chdir 来切换目录。
内部命令pwd的设计
pwd命令的作用是显示工作目录,设计如下:
1 2 3 4
| void my_pwd(char **arr) { char wd[4096]; puts(getcwd(wd, 4096)); }
|
该函数使用了 getcwd 函数来获取路径并且存入字符数组wd中,用puts输出
内部命令echo的设计
echo命令的作用是输出命令里的字符,设计如下:
1 2 3 4 5 6 7 8 9
| void my_echo() { char temp[256]; int i; for(i=5;i<strlen(historycmd);i++) { temp[i-5]=historycmd[i]; } puts(temp); }
|
内部命令history的设计
history命令的作用是显示历史输入的命令,设计如下:
1 2 3 4 5 6 7 8 9 10
| HQueue init_history(HQueue q) { q.head=(HNode*)malloc(sizeof(HNode)); if(q.head==NULL) { printf("init_history申请内存错误"); } q.tail=q.head; q.num=0; return q; }
|
1 2 3 4 5 6 7 8 9 10 11 12
| HQueue add_history(HQueue q,int num) { HNode *temp=(HNode*)malloc(sizeof(HNode)); if(temp==NULL) { printf("add_history申请内存错误"); } q.tail->next=temp; q.tail=q.tail->next; q.num=q.num+1; strcpy(q.tail->cmd,historycmd); return q; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| void my_history(HQueue q) { HNode *temp=q.head->next; if(temp==NULL) { printf("链表错误!\n"); } int i=0; for(i=0;i<q.num;i++) { printf("%d ",i+1); puts(temp->cmd); temp=temp->next; } }
|
内部命令exit的设计
exit的作用是退出shell
1 2 3 4 5
| void my_exit() { printf("即将退出myshell\n"); IS_STOP=1; }
|
该函数将全局变量 IS_STOP 置为1,主循环独取后会停止
环境变量的支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| int check_env(char** arr) { int is_exist=0; char wd[256]; if(!(access(arr[0],0))) { getcwd(wd, 256); strcat(wd,"/"); strcat(wd,arr[0]); is_exist=1; } else { int i; for(i=0;i<env_N;i++) { strcpy(wd,env[i]); strcat(wd,"/"); strcat(wd,arr[0]); if(!(access(wd,0))) { is_exist=1; break; } } } if(is_exist) { printf("%s在环境变量内!",arr[0]); printf("路径是:%s\n执行结果:\n",wd); pid_t pid = fork(); if (pid == 0) { execvp(wd, arr); return 255; } wait(NULL); } return is_exist; }
|
该函数使用access搜索在给定路径集合env里是否存在名为 arr[0] 的可执行程序,若存在则执行。
外部命令的实现
1 2 3 4 5 6 7 8
| void my_outer(char **arr) { printf("%s是外部命令!\n执行结果:\n",arr[0]); pid_t pid = fork(); if (pid == 0) { execvp(arr[0], arr); } wait(NULL); }
|
该函数使用exec调用外部命令
主函数设计
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| int main() { print_welcome(); int num; HQueue history; history=init_history(history); while (!IS_STOP) { printf("myshell> "); fflush(stdin); fgets(cmd, 256, stdin); int i=0; while (cmd[i]!='\n') { i++; } cmd[i] = '\0'; strcpy(historycmd,cmd); arr[0] = cmd; split(cmd," ",&arr[0],&num); arr[num]=NULL; if (!arr[0]){ break; }
else if (strcmp(arr[0], "cd") == 0) { my_cd(arr); } else if (strcmp(arr[0], "pwd") == 0) { my_pwd(arr); } else if (strcmp(arr[0], "echo") == 0) { my_echo(arr); } else if (strcmp(arr[0], "history") == 0) { my_history(history); } else if (strcmp(arr[0], "exit") == 0) { my_exit(); }
else if (check_env(arr)) {} else { my_outer(arr); } history=add_history(history,i); } }
|
验证测试
欢迎界面
内部命令
cd 和 pwd 命令
用pwd命令显示当前目录,cd切换后再用pwd显示切换后的目录
与预期相符
echo 命令
输出hello 17281144
与预期相符
history 命令
输出历史
与预期相符
exit命令
退出shell
与预期相符
环境变量
将如下代码编译成为名为 hello 的可执行文件,放在 /home 目录下,并且将目录加入 env 数组。
1 2 3 4 5
| #include<stdio.h> int main() { printf("Hello World\n"); }
|
直接输入hello
与预期相符
外部命令
以ping 命令作为示例
与预期相符