作业地址

主要实现下面三部分内容:

  • 运行命令
  • I/O 重定向
  • 管道

测试脚本如下:

1
2
3
4
5
6
ls > y
cat < y | sort | uniq | wc > y1
cat y1
rm y1
ls |  sort | uniq | wc
rm y

Executing simple commands

You may want to change the 6.828 shell to always try /bin, if the program doesn’t exist in the current working directory, so that below you don’t have to type “/bin” for each program. If you are ambitious you can implement support for a PATH variable.

从提示中得到,可以从 PATH 中得到程序的运行目录, 而通过 getenv() 能获取到具体的环境字符串

1
char *getenv(const char *name)

另外,还需要检查调用进程是否对指定文件(即命令对应的文件)是否有执行某种操作的权限,可通过 access() 进行检查

1
2
3
4
5
6
7
int access(const char * pathname, int mode)
    /* 主要模式(mode)有 */
    /* R_OK      测试读许可权 */
    /* W_OK      测试写许可权 */
    /* X_OK      测试执行许可权 */
    /* F_OK      测试文件是否存在 */
    /* 成功返回 0, 失败返回 1 */

而具体运行可调用 exec() 函数族,具体可参考 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
void runcmd(struct cmd *cmd) {
  // ...
  switch (cmd->type) {
    // ...
    case ' ':
      ecmd = (struct execcmd *)cmd;
      if (ecmd->argv[0] == 0)
        _exit(0);
      // fprintf(stderr, "exec not implemented\n");
      // Your code here ...
      if (!access(ecmd->argv[0], F_OK)) {  // 检查当前目录
        execv(ecmd->argv[0], ecmd->argv);
      } else {  // 扫描 PATH 内目录
        int i = 0, k = 0;
        char *token, *savePtr;
        char *envPaths[100];  // 存放 getenv("PATH") 切割后字符串
        char *envPathStr = getenv("PATH");
        token = strtok_r(envPathStr, ":", &savePtr);
        while (token) {
          envPaths[i++] = token;
          token = strtok_r(NULL, ":", &savePtr);
        }

        char *absPath;
        int isFound = 0;
        for (k = 0; k < i && !isFound; ++k) {
          absPath = (char *)malloc(strlen(envPaths[k] + strlen(ecmd->argv[0]) + 1) * sizeof(char));
          strncpy(absPath, envPaths[k], sizeof(envPaths[k]));
          strncat(absPath, "/", sizeof("/"));
          strncat(absPath, ecmd->argv[0], sizeof(ecmd->argv[0]));
          if (!access(absPath, F_OK|X_OK)) {
            isFound = 1;
            execv(absPath, ecmd->argv);
          }
          free(absPath);
        }

        if (!isFound) {
          fprintf(stderr, "%s: Command not found.\n", ecmd->argv[0]);
        }
      }
      break;
      // ...
  }
}

I/O redirection

参考 xv6 book P10 内容了解文件描述符

主要的实现思路是首先关闭原程序的标准输入/输出,然后打开指定文件作为新的输入和输出

具体实现如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void runcmd(struct cmd *cmd) {
  // ...
  switch (cmd->type) {
    // ...
    case '>':
    case '<':
      rcmd = (struct redircmd *)cmd;
      // fprintf(stderr, "redir not implemented\n");
      // Your code here ...
      close(rcmd->fd);  // 关闭标准输入输出

      if (open(rcmd->file, rcmd->flags, 0644) < 0) {
        fprintf(stderr, "Open %s failed.\n", rcmd->file);
        exit(0);
      }

      runcmd(rcmd->cmd);
      break;
  }
}

Implement pipes

参考 xv6 book P13 管道部分,主要涉及 pipe()dup() 两个函数

int pipe(int p[])
建立缓冲区,并把缓冲区通过 fd 形式给程序调用。其中 p[0] 作为缓冲区读取端, p[1] 作为缓冲区写入端
int dup(int old_fd)
产生一个 fd 指向 old_fd 指向的文件并返回新的 fd

可以参考下 CS134 Lecture1 的一张图

主要实现如下

 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
void runcmd(struct cmd *cmd) {
  // ...
  switch (cmd->type) {
    // ...
    case '|':
      pcmd = (struct pipecmd *)cmd;
      // fprintf(stderr, "pipe not implemented\n");
      // Your code here ...
      if (pipe(p) < 0) {
        fprintf(stderr, "pipe failed\n");
      }

      if (!fork1()) {
        close(1);  // 关闭标准输出
        dup(p[1]);  // 把标准输出重定向到 p[1],管道写入端
        close(p[0]);  // 关闭 p 文件描述符
        close(p[1]);  // 关闭 p 文件描述符
        runcmd(pcmd->left);  // left 标准输入不变,标准输出流入管道
      }

      if (!fork1()) {
        close(0);  // 关闭标准输入
        dup(p[0]);  // 把标准输入重定向到 p[0],管道读取端
        close(p[0]);  // 关闭 p 文件描述符
        close(p[1]);  // 关闭 p 文件描述符
        runcmd(pcmd->right);  // right 标准输入从管道读取,标准输出不变
      }

      close(p[0]);
      close(p[1]);
      wait(&r);
      wait(&r);
      break;
  }
}

结果

1
2
3
$ ./a.out < t.sh
4       4      18
4       4      18