问题描述

​ 探索、理解并掌握操作系统命令释器的设计原和实现机制,基于 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); //内部命令cd的实现
void my_pwd(char **arr); //内部命令pwd的实现
void my_echo(); //内部命令echo的实现
void my_exit(); //内部命令exit的实现
HQueue init_history(HQueue q); //历史记录队列初始化
HQueue add_history(HQueue q,int num); //添加历史记录
void my_history(HQueue q); //内部命令history的实现
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; //是否退出myshell
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) { //循环直到遇到exit
printf("myshell> "); //命令提示符
fflush(stdin);
fgets(cmd, 256, stdin); //读入命令
int i=0;
while (cmd[i]!='\n')
{
i++;
}
cmd[i] = '\0'; //将\n替换为\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) { //内部命令cd
my_cd(arr);
}
else if (strcmp(arr[0], "pwd") == 0) { //内部命令pwd
my_pwd(arr);
}
else if (strcmp(arr[0], "echo") == 0) { //内部命令echo
my_echo(arr);
}
else if (strcmp(arr[0], "history") == 0) { //内部命令history
my_history(history);
}
else if (strcmp(arr[0], "exit") == 0) { //内部命令exit
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 命令作为示例

与预期相符