Linux实验报告

一、    实验内容

      Linux系统中,shell是用户和系统内核沟通的中介,它为用户使用操作系统的服务提供了一个命令界面。用户在命令提示符($或是#)下输入的每一个命令都由shell解释,然后传给内核执行。

   本实验是实现一个简单的shell,能在虚拟shell界面下响应一些简单的shell命令,不考虑输入/输出重定向以及管道。

二、    简单shell命令解释器的分析

   命令解释执行的过程为:1.初始化虚拟shell界面;2.获取用户指令(命令和参数);3.解析指令;4.寻找命令文件;5.执行命令。

三、    shell命令解析器的设计

   (1)在虚拟的shell界面上出现命令提示符($或#);

   (2)获取用户指令:获取用户在命令提示符后面输入的命令及其参数,并注意命令输入的最大长度;

   (3)解析指令:对用户输入的命令进行解析,解析出命令名和参数;

   (4)寻找命令文件:每个命令的执行都必须依靠对应的可执行文件,这些文件的存放路径存放在用户的PATH环境变量里;

   (5)执行命令:可通过fork( )系统调用创建一个进程来完成执行命令的任务,具体的命令执行用execv( )函数。

     注:为了简化程序设计的难度,对涉及输入/输出重定向以及管道的命令不予考虑。

四、    shell命令解析器实现的详细步骤

   1.在虚拟的shell界面上打印出命令提示符“#”或者“$”。

   通常在Linux命名提示符前会有路径或者机器名,我们可以先获取到当前执行的路径,由此调用一个外部函数get_current_dir_path(),返回值是一个指向当前目录绝对路径的字符串指针,然后连接到命令提示符之前即可。这样就得到了虚拟shell的命令提示行。因为用户不是只执行一个指令就结束了,还需要等待用户继续输入,所以用一个while死循环来循环执行该解析器。代码如下:

      char *path;

while (1)

   {

      path = get_current_dir_name();

      printf("%s>fangxu$",path);

}

      2.获取用户输入的指令及其参数

      通过getchar()或者gets()方法来获取用户输入的指令,设置一个缓冲区buffer来存储用户的输入,并定义缓冲区大小,如果用户输入的指令太长超出缓冲区大小,则提示用户输入指令太长并重新输入。定义一个临时变量inputLen来记录用户输入字符串的长度,如果超出缓冲区大小BUFFERSIZE,则发出提示并重复执行该程序允许用户再次重新输入指令;若没有超出,则把缓冲区的内容复制到input中,以此来存储用户输入的指令。代码如下(接上段代码printf之后):

//开始获取输入

    inputLen = 0;

    in_char = getchar();

    while (in_char !='\n')

    {

       if(inputLen < BUFFERSIZE)

          buffer[inputLen++] = in_char;

       in_char = getchar();

    }

    /*命令超长处理*/

    if (inputLen >= BUFFERSIZE)

    {

       printf("Your command is too long! Please re-enter your command!\n");

       inputLen = 0;

       continue;

    }

    else

       buffer[inputLen] = '\0';/*加上串结束符号,形成字串*/

    /*将命令从缓存拷贝到input中*/

    input = (char *) malloc(sizeof(char) * (inputLen+1));

    strcpy(input,buffer);

3. 解析指令:对用户输入的命令进行解析,解析出命令和参数

       在完成了指令的输入后自然是要解析该指令,以获取命令和参数了。在这个过程中我们需要区分命令和参数,通过for循环,找到用户输入字符串中的空格或者\t,以此来区分开命令名和参数,并保存在指针数组char *arg[]中,其中arg[0]保存的为命令名,其后参数从arg[1]开始依次保存。最后,获取到命令名和参数后再释放input空间。

      /* 获取命令和参数并保存在arg中*/

      for (i = 0,j = 0,k = 0;i <= inputLen;i++)

      {

        //把输入的命令分开为各条命令和参数

        if (input[i] == ' ' || input[i] =='\t' || input[i] == '\0')

        {

           if (j == 0) /*这个条件可以略去连在一起的多个空格或者tab*/

              continue;

           else

           {

              buffer[j++] = '\0';

              arg[k] = (char *) malloc(sizeof(char)*j);

              /*将指令或参数从缓存拷贝到arg中*/

              strcpy(arg[k],buffer);

              j = 0;  /*准备取下一个参数*/

              k++;

           }

        }

        else

        { 

           /*如果字串最后是‘&',则置后台运行标记为1*/

           if (input[i] == '&' && input[i+1] == '\0')

           {

              isBk = 1;

              continue;

           }

           buffer[j++] = input[i];

        } 

      }

      free(input);/*释放空间*/

4. 寻找命令文件

      接下来我们就该完成寻找命令文件的工作了。那么,到哪里去找用户输入的命令呢?我们知道shell为每个用户提供了一组环境变量。这些变量定义在用户的.login文件中。其中路径变量是一组绝对路径的列表,表明shell将如何搜索命令文件。也就是说,只要我们获取了路径变量PATH,然后依次搜索各路径,就可以确定用户输入的命令文件的位置了。此外,我们还需要在用户输入退出指令的时候能退出我们的shell回到Linux的shell下。在这里,我们设定了exit指令来完成这一功能。其中的函数is fileexist就是用来判断输入的指令是否存在。存在返回0,并将指令的路径保存在buffer中,否则返回一1。程序如下:

//如果输入的指令是exit则退出while,即退出程序

      if (strcmp(arg[0],"exit") == 0 )

      {

        printf("Bye bye\n");

        break;

      }

      if (isRd == 0)

      {   //非管道、重定向指令

        //在使用exec执行命令的时候,最后的参数必须是NULL指针,

        //所以将最后一个参数置成空值

        arg[k] = (char *) 0;

        //判断指令arg[0]是否存在

        if (is_fileexist(arg[0]) == -1 ){

           printf("This command is not found!\n");

           for(i=0;i<k;i++)

              free(arg[i]);

           continue;

        }

        /*释放申请的空间*/

        for (i=0;i<k;i++)

           free(arg[i]);

      }

   }

}

int is_fileexist(char *comm)

{

   char *path,*p;

   int i;

   i = 0;

   /*使用getenv函数来获取系统环境变量,用参数PATH表示获取路径*/

   path = getenv("PATH");

   p = path;

   while (*p != '\0'){

      /*路径列表使用‘:’来分隔路径*/

      if (*p != ':')

        buffer[i++] = *p;

      else{

        buffer[i++] = '/';

        buffer[i] = '\0';

        /*将指令和路径合成,形成pathname,并使用access函数来判断该文件是否存在*/

        strcat(buffer,comm);

        if (access(buffer,F_OK) == 0)  /*文件被找到*/

           return 0;

        else 

           /*继续寻找其它路径*/

           i = 0;

      } 

      p++;

   }

   /*搜索完所有路径,依然没有找到则返回-1*/

   return -1;

}

5. 执行命令

      当我们得到了命令的路径,就可以执行命令了。通过调用fork()创建一个子进程,在子进程中执行命令。fork()会创建一个新的子进程,其子进程会复制父进程的数据与堆栈空间并继承父进程的用户代码、组代码、环境变量、已打开的文件代码、工作目录和资源限制等。Linux使用COW (copy onwrite)技术,只有当其中一个进程试图修改欲复制的空间时才会做真正的复制动作。由于这些继承的信息是复制而来,并非指相同的内存空间,因此子进程对这些变量的修改和父进程不会同步。此外,子进程不会继承父进程的文件锁定和未处理的信号。需要注意的是,Linux不保证子进程会比父进程先执行或晚执行,因此编写程序时需注意死锁和竞争的发生。如果fork成功,则父进程会返回新建子进程的ID (PID,而在新建子进程中返回0,如果失败则返回-1。执行指令是通过调用exec函数。exec有一系列的变型函数:execl(), execle(), execlp(), execv(), execve()和execvp()。这里用到execv()函数,execv有两个参数,分别是指向命令路径的字符串指针和存放命令参数的数组指针。execv如果成功则不会返回,失败则返回-1。

程序如下:

if ((pid = fork()) ==0)    /*子进程*/

          execv(buffer,arg);

        else  /*父进程*/

          if (isBk == 0)  /*并非后台执行指令*/

             waitpid(pid,&status,0);

        /*释放申请的空间*/

        for (i=0;i<k;i++)

          free(arg[i]);

    后台执行指令和普通指令之间的区别就在于父进程是否执行waitpid( )。也就是说如果是后台执行指令,那么父进程不等到子进程结束就继续执行下去了,否则父进程一直要等到子进程结束才继续执行。一个执行命令的正常过程是,父进程创建子进程,并在执行命令的时候等待,一直等到子进程终结。如果在命令行的末尾添加“&”,那么shell将会创建一个子进程,并且启动它执行指定的命令,但是父进程将不会等到子进程结束。也就是说,父进程和子进程将会同时执行。当子进程执行命令的时候,父进程将会在标准输出设备(stdout)上打出另一个提示符并等待用户输入其他命令。

        至此,在不考虑管道以及输入输出重定向的情况下,shell命令解释器的功能已经完成。

五、    运行界面及结果分析

1.编译并执行shell.c文件,执行结果如下:

  

2.执行ls -l列举当前文件夹中的文件:

3.用mkdir命令创建文件夹files:

   4.用rmdir命令删除文件夹abc:

   5.用date命令显示系统日期时间(没有调整虚拟机Linux系统的时间,显示不太正确哈):

   6.用ps –e&命令显示前台和后天所有进程(命令后加&使之运行为后台进程):

    7.输入非Linux系统命令(提示找不到该命令):

    8.输入exit自定义命令退出程序进入Linux系统的shell:

六、    总结

   本次实验完成了简单的对shell命令的解析器。由于对于linux环境下的C语言编程的不熟悉,导致完成过程颇为曲折。但同时,也使自己对于Linux操作系统有了更为深入的了解。自己之前用的编程语言基本上都是完全面向对象的Java语言,偶尔使用C#语言,很少很少用C来写程序了。虽然照着书上敲了C代码,但为了让其正确运行,依旧费了不少力气。上网查资料,翻书等等,最终还是修改成功并使其运行了。在对C更为熟悉的同时,也使自己对于Liunx的一些基本命令更加了解。

学习Linux,不能只通过看书本上的知识,还要多上机编写程序,才会有深刻的体会,同时自己对应Linux各种机制才会有更好的理解,才能更好的运用编程,同时这样也能使自己的编程能力得到较大的提高。

    学习不能仅仅局限于书本以及老师的讲解,还有更多的在于自己的动手查找以及实践的过程中所学到的知识。

附源码:

#include <linux/unistd.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <stdlib.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <stdio.h>

#include <string.h>

#define BUFFERSIZE 50

extern char *get_current_dir_name(void);

extern char *getenv(const char *name);

extern pid_t waitpid(pid_t pid, int *status, int options);

char buffer[BUFFERSIZE+1];

main()

{

   char *path, *arg[10], *input;

   int  inputLen, isRd, isBk, i, j, k, pid, status;

   char in_char;

   while (1)

   {

     isRd = 0; //是否重定向标志

     isBk = 0; //是否后台运行标志

     path = get_current_dir_name();

     printf("%s>fangxu>$",path);

     //开始获取输入

     inputLen = 0;

     in_char = getchar();

     while (in_char !='\n')

     {

        if(inputLen < BUFFERSIZE)

          buffer[inputLen++] = in_char;

        in_char = getchar();

     }

     /*命令超长处理*/

     if (inputLen >= BUFFERSIZE)

     {

        printf("Your command is too long! Please re-enter your command!\n");

        inputLen = 0;

        continue;

     }

     else

        buffer[inputLen] = '\0';/*加上串结束符号,形成字串*/

     /*将命令从缓存拷贝到input中*/

     input = (char *) malloc(sizeof(char) * (inputLen+1));

     strcpy(input,buffer);

     /* 获取命令和参数并保存在arg中*/

     for (i = 0,j = 0,k = 0;i <= inputLen;i++)

     {

        //把输入的命令分开为各条命令和参数

        if (input[i] == ' ' || input[i] =='\t' || input[i] == '\0')

        {

          if (j == 0) /*这个条件可以略去连在一起的多个空格或者tab*/

             continue;

          else

          {

             buffer[j++] = '\0';

             arg[k] = (char *) malloc(sizeof(char)*j);

             /*将指令或参数从缓存拷贝到arg中*/

             strcpy(arg[k],buffer);

             j = 0;  /*准备取下一个参数*/

             k++;

          }

        }

        else

        { 

          /*如果字串最后是‘&',则置后台运行标记为1*/

          if (input[i] == '&' && input[i+1] == '\0')

          {

             isBk = 1;

             continue;

          }

          buffer[j++] = input[i];

        } 

     }

     free(input);/*释放空间*/

     //如果输入的指令是exit则退出while,即退出程序

     if (strcmp(arg[0],"exit") == 0 )

     {

        printf("Bye bye\n");

        break;

     }

     if (isRd == 0)

     {   //非管道、重定向指令

        //在使用exec执行命令的时候,最后的参数必须是NULL指针,

        //所以将最后一个参数置成空值

        arg[k] = (char *) 0;

        //判断指令arg[0]是否存在

        if (is_fileexist(arg[0]) == -1 ){

          printf("This command is not found!\n");

          for(i=0;i<k;i++)

             free(arg[i]);

          continue;

        }

        if ((pid = fork()) ==0)     /*子进程*/

          execv(buffer,arg);

        else /*父进程*/

          if (isBk == 0)  /*并非后台执行指令*/

             waitpid(pid,&status,0);

        /*释放申请的空间*/

        for (i=0;i<k;i++)

          free(arg[i]);

     }

   }

}

int is_fileexist(char *comm)

{

   char *path,*p;

   int i;

   i = 0;

   /*使用getenv函数来获取系统环境变量,用参数PATH表示获取路径*/

   path = getenv("PATH");

   p = path;

   while (*p != '\0'){

     /*路径列表使用‘:’来分隔路径*/

     if (*p != ':')

        buffer[i++] = *p;

     else{

        buffer[i++] = '/';

        buffer[i] = '\0';

        /*将指令和路径合成,形成pathname,并使用access函数来判断该文件是否存在*/

        strcat(buffer,comm);

        if (access(buffer,F_OK) == 0)  /*文件被找到*/

          return 0;

        else

          /*继续寻找其它路径*/

          i = 0;

     } 

     p++;

   }

   /*搜索完所有路径,依然没有找到则返回-1*/

   return -1;

}

相关推荐