这是 MIT6.S081 的第一个 Lab ,目的是为了让学生熟悉 Xv6 和 Unix 的一些实用程序,例如 sleep find xargs 等。

sleep

这一关是实现 sleep 命令。由于在 user/user.h 目录中给出来了可以直接由 C 语言调用的函数 int sleep(int) ,这一关非常简单:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
  int sec;
  if (argc != 2) {
    fprintf(2, "Usage: sleep secends\n");
    exit(1);
  }
  sec = atoi(argv[1]);
  if (sec >= 0) {
    sleep(sec);
  } else {
    fprintf(2, "Usage: sleep secends\n");
  }
  exit(0);
}

pingpong

这一关是为了让我们熟悉管道的使用,文档中强调了 uses UNIX system calls to ''ping-pong'' a byte between two processes over a pair of pipes, one for each direction ,所以我们应该创建两个管道,将父子进程的读写交叉连接:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
  char buf[100];

  // parent to child
  int p2c[2];
  // child to parent
  int c2p[2];
  pipe(p2c);
  pipe(c2p);

  if (fork()) {
    // parent
    close(p2c[0]);
    close(c2p[1]);
    write(p2c[1], "ping", 4);
    read(c2p[0], buf, 4);
    printf("%d: received pong\n", getpid());
    close(p2c[1]);
    close(c2p[0]);
  } else {
    // child
    close(p2c[1]);
    close(c2p[0]);
    read(p2c[0], buf, 4);
    printf("%d: received ping\n", getpid());
    write(p2c[1], "pong", 4);
    close(p2c[0]);
    close(c2p[1]);
  }
  exit(0);
}

primes

这一关是使用多进程来实现筛法。本来一开始没懂他是什么意思,结果看到了文档中的一幅图就懂了。

sieve

写了个递归调用的函数,但是又不想再包装一层,所以当传入的文件描述符为 -1 时表示初始化:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define UPPER_BOUND 35

void send_and_close(int fd, int *num, int count) {
  write(fd, num, sizeof(int) * count);
  close(fd);
}

int receive_and_close(int fd, int *num) {
  int res = read(fd, num, sizeof(int) * UPPER_BOUND);
  close(fd);
  return res / sizeof(int);
}

void sieve(int fd) {
  int num[UPPER_BOUND];
  int rec_count = 0;

  // if fd < 0, init by building the array first
  if (fd < 0) {
    for (int i = 2; i <= UPPER_BOUND; ++i) {
      num[rec_count++] = i;
    }
  } else {
    rec_count = receive_and_close(fd, num);
  }

  if (rec_count <= 0) {
    return;
  }

  int p[2];
  pipe(p);

  if (fork() > 0) {
    // parent: send numbers
    close(p[0]);
    int filter_num = num[0];
    printf("prime %d\n", filter_num);
    int send_count = 0;
    for (int i = 0; i < rec_count; ++i) {
      if (num[i] % filter_num != 0) {
        num[send_count++] = num[i];
      }
    }
    send_and_close(p[1], num, send_count);
    wait(0);
  } else {
    // child: receive numbers
    close(p[1]);
    sieve(p[0]);
  }
}

int main(int argc, char *argv[]) {
  sieve(-1);
  exit(0);
}

find

文档中提示了可以参考 user/ls.c ,所以完全可以照着它抄抄改改,连函数 fmtname 都可以改写成 filename 复用。而且由于是改写的 ls 命令,所以错误处理就不用自己动手了。值得注意的是,在递归调用中要忽略掉 ... 目录,不然会有无限递归的情况。

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char *filename(char *path) {
  static char buf[DIRSIZ + 1];
  char *p;
  for (p = path + strlen(path); p >= path && *p != '/'; --p)
    ;
  p++;

  if (strlen(p) >= DIRSIZ) {
    return p;
  }
  memmove(buf, p, strlen(p));
  buf[strlen(p)] = 0;
  return buf;
}

void find(char *path, char *pattern) {
  int fd;
  if ((fd = open(path, 0)) < 0) {
    fprintf(2, "find: cannot open %s\n", path);
    return;
  }

  if (strcmp(filename(path), pattern) == 0) {
    printf("%s\n", path);
  }

  struct stat st;
  if (fstat(fd, &st) < 0) {
    fprintf(2, "find: cannot stat %s\n", path);
    return;
  }

  char buf[512];
  struct dirent de;
  if (st.type == T_DIR) {
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("ls: path too long\n");
    } else {
      strcpy(buf, path);
      char *p = buf + strlen(buf);
      *p++ = '/';
      while (read(fd, &de, sizeof(de)) == sizeof(de)) {
        if (de.inum == 0) {
          continue;
        }
        if (strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) {
          continue;
        }
        memmove(p, de.name, strlen(de.name));
        p[strlen(de.name)] = 0;
        find(buf, pattern);
      }
    }
  }
  close(fd);
}

int main(int argc, char *argv[])
{
  if (argc != 3) {
    printf("find: wrong argumens\n");
  }

  find(argv[1], argv[2]);
  exit(0);
}

当我删掉对 ... 目录的判断时,发现 Xv6 可以在崩溃后报错这是个无限递归,有时间可以研究一下是怎么实现的。我猜测是对栈和 call 指令进行了统计。

xargs

这一关是完成命令 xargs ,文档对这个实现的要求很低,不要求实现 -n 选项。所以可以直接将所有的 ' ''\t' 换成 '\0' ,这样一来就可以复用缓冲区,直接将 cmd 数组的每个元素指向 buf 的某个位置即可。

#include "kernel/param.h"
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char *argv[])
{
  char *cmd[MAXARG];
  char buf[1024];

  int cmd_cnt = 0;
  for (int i = 1; i < argc; ++i) {
    cmd[cmd_cnt++] = argv[i];
  }

  int len = read(0, buf, sizeof buf);
  buf[len] = '\0';

  if (len < 0) {
    fprintf(2, "xargs: input error\n");
    exit(1);
  }

  char *start = buf;
  for (char *p = buf; *p != '\0'; ++p) {
    if (*p == ' ' || *p == '\t') {
      *p++ = '\0';
      cmd[cmd_cnt++] = start;
      start = p;
    } else if (*p == '\n') {
      *p++ = '\0';
      cmd[cmd_cnt++] = start;
      cmd[cmd_cnt] = 0;
      start = p;
      cmd_cnt = argc - 1;
      if (fork() == 0) {
        exec(cmd[0], cmd);
      } else {
        wait(0);
      }
    }
  }

  exit(0);
}