操作系统实验1--Linux命令解析程序设计与实现

问题描述

​ 探索、理解并掌握操作系统命令释器的设计原和实现机制,基于 Linux内核进行相应命令解释程序的设计和实现,并在 Linux操作平台上加以实现。

实验要求

Linux 命令解释程序功能设计要求:
(1)选取和设计实现一组内部命令(五条以上);
(2)外部命令执行采用直接调用 exec 系统调用的方式来实现;
(3)至少一条内部命令采用直接调用相应系统调用的方式来实现;
(4)系统环境变量(至少包括当前目录)支持;
(5)在 Linux 操作系统上启用(或替换原命令解释程序 Shell)并测试验证。

项目结构

函数一览

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); //外部命令的实现

数据结构和全局变量定义

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命令的作用是切换工作目录,直接使用了系统调用,设计如下:

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命令的作用是显示工作目录,设计如下:

void my_pwd(char **arr) {
    char wd[4096];
        puts(getcwd(wd, 4096));
}

​ 该函数使用了 getcwd 函数来获取路径并且存入字符数组wd中,用puts输出

内部命令echo的设计

​ echo命令的作用是输出命令里的字符,设计如下:

void my_echo() {
    char temp[256];
    int i;
    for(i=5;i<strlen(historycmd);i++)
    {
        temp[i-5]=historycmd[i];
    }
    puts(temp);
}

内部命令history的设计

​ history命令的作用是显示历史输入的命令,设计如下:

//在程序开始之初调用,初始化链队列
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;
}
//该函数在每输入一条非空指令后调用,将新输入的指令存入链队列
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;
}
//该函数按顺序输出序号和历史指令
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

void my_exit()
{
    printf("即将退出myshell\n");
    IS_STOP=1;
}

​ 该函数将全局变量 IS_STOP 置为1,主循环独取后会停止

环境变量的支持

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] 的可执行程序,若存在则执行。

外部命令的实现

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调用外部命令

主函数设计

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 数组。

#include<stdio.h>
int main()
{
    printf("Hello World\n");
}

直接输入hello

与预期相符

外部命令

以ping 命令作为示例

与预期相符


操作系统实验1--Linux命令解析程序设计与实现
https://blog.loststar.tech/posts/9de5b70b/
作者
Loststar
发布于
2020年3月21日
许可协议