mit6.828 Homewoek 2:shell

Homewoek 2:shell

下载 6.828 shell 并阅读代码。shell 包含两个主要部分:解析shell命令和实现。

将下列指令复制到 t.sh

ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls |  sort | uniq | wc
rm y

编译 sh.c ,并执行指令:

此执行将打印错误消息,因为尚未实现上述功能。在本任务的其余部分中,将实现这些功能。

实现命令

补充 runcmd 中空缺的代码。使用 man 3 exec 查看 execv 手册。

  1. int execv (const char * path, char * const argv[]); :用来执行参数 path 字符串所代表的文件路径, 与 execl() 不同的地方在于 execv() 只需两个参数, 第二个参数利用数组指针来传递给执行文件。

该函数如果执行成功,则不会返回;若执行出错,会返回 -1 ,具体的错误代码可以通过全局变量 errno 查看,还可以通过 stderr 得到具体的错误描述字符串。

  1. int chdir(const char *path); :函数应使path参数指向的路径名所命名的目录成为当前工作目录;也就是说,路径的起点搜索不是以 “ /” 开头的路径名。转换成功返回 0 ;否则返回 -1 且当前目录不变。

来自https://blog.csdn.net/a747979985/article/details/95094094

本次 shell 作业执行可执行文件主要是通过该函数来实现。

这次作业真的让我一头雾水,参考了如下博客,对 shell 大概的执行逻辑与代码实现有了头绪。

  1. Implement a Shell by yourself -- MIT xv6 shell :这篇博客分析了实现的过程,对从整体去把握 shell 的运行流程很有帮助。其次,他的实现代码运用了 access() 函数来检查权限,代码很简洁,但是不易理解。
  2. 【xv6学习之HW1】shell :这篇博文对上述博文以及代码进行了进一步解析,讲解了 access() 函数以及权限判断各个定义,有助于去理解上一篇博客的代码。

这是我的代码 https://github.com/a74731248/journal-of-mit-6.828/blob/master/homework/HW1/sh.c 。我在代码中把自己的理解作为注释加了上去,挺详细的,希望对大家有帮助。

实现部分

execcmd

    // 寻找可执行文件
    if(execv(ecmd->argv[0], ecmd->argv) == -1) 
    {
      chdir("/bin/");
      if(execv(ecmd->argv[0], ecmd->argv) == -1) 
      {
        chdir("/usr/bin/");
        execv(ecmd->argv[0], ecmd->argv);
      }
    }
    fprintf(stderr, "exec %s failed.
", ecmd->argv[0]);
    _exit(0);

redircmd

    close(rcmd->fd);
    if(open(rcmd->file, rcmd->flags, S_IRUSR | S_IWUSR) == -1)
    {
      fprintf(stderr, "file %s can't find
", rcmd->file);
      _exit(0);
    }

pipecmd

    if(pipe(p) < 0)
    {
      fprintf(stderr, "call syscall pipe() failed in line %d
", __LINE__);
      _exit(0);
    }
    if(fork1() == 0)
    {
      // 子进程输出绑定到管道的 fd1
      close(1);   
      dup(p[1]);    

      close(p[0]);
      close(p[1]);

      // 执行管道左侧的命令
      runcmd(pcmd->left);
    }

    if(fork1() == 0)
    {
      // 绑定管道的 fd0 到子进程的输入
      close(0);
      dup(p[0]);

      close(p[0]);
      close(p[1]);

      // 运行管道右侧命令
      runcmd(pcmd->right);
    }

    close(p[0]);
    close(p[1]);

    // 这里我不太理解
    wait(&r);
    wait(&r);

问题记录

记录一下自己实现过程中的理解以及疑问(管道通信部分):

理解:

管道左侧的程序需用使用子进程1去执行,它需要从标准输入中读取参数,然后将程序运行结果输出到管道中去,故需要关闭子进程1的 fd1 ,然后将管道的写端口(p[1]) 拷贝到 fd1 中,从而实现子进程1输出到管道中。

管道右侧的程序需要用子进程2去执行,它需要从管道中读取参数,故关闭子进程2的 fd0 ,然后将管道的读端口(p[0])拷贝到 fd0 中,从而实现子进程2从管道中读取参数。

疑问:

为什么每次都需要关闭管道的描述符(即 close(p[0]); close(p[1]);)?

是因为管道对应的描述符已经重定向了,避免管道的内容受到其他进程的影响吗?

总结

  1. 了解了 fork、dup、close、pipe 等系统调用的使用。
  2. 了解了 shell 是如何实现输入命令的分解。
  3. 利用 execv 实现了 exec 指令,使得 shell 能够加载并执行文件(命令)。
  4. 大致理解了进程之间的管道通信机制,通过文件描述符拷贝实现进程输入输出重定向。
  5. 实现了进程之间的管道通信。

参考

Implement a Shell by yourself -- MIT xv6 shell

MIT6.828学习之homework2:shell

【xv6学习之HW1】shell

原文地址:https://www.cnblogs.com/joe-w/p/12613870.html