Lab1 Xv6 and Unix utilities

Boot xv6 (easy)

从指定仓库clone代码,而后编译-运行,尝试一部分命令,没问题之后就能够正式开始了!

sleep (easy)

试验要求

实现Unix程序sleep,使程序暂定指定数量的ticks。并将解决方案放在文件user/sleep.c中。

一些提醒:

  • 参考其余代码如果获取用户输出参数,并对用户输出参数做出肯定判断(未指定工夫报错exit(1)
  • 能够应用零碎调用sleep
  • main函数最初退出应调用exit(0)
  • 将程序增加到Makefile中

具体实现

留神参数判断和工夫小于0的状况。主函数实现如下:

int main(int argc, char* argv[]) {    if (argc < 2) {        printf("Usage: sleep [time]\n");        exit(1);    } else {        int time = atoi(argv[1]);        if (time < 0) exit(1);        sleep(time);        exit(0);    }}

试验后果

  • 程序成果:
  • 测试后果:

pingpong (easy)

试验要求

实现父子过程之间传输数据并打印过程ID和后果。具体为:父过程发一字节,子过程收到打印"<pid>: receive ping",再把数据发回父过程,退出;父过程收到数据打印"<pid>: receive pong"并退出。文件保留在user/pingpong.c中。

一些提醒:

  • 创立管道pipe
  • 创立子线程fork
  • read从管道读取,write写入管道
  • getpid获取过程ID

具体实现

注意事项:

  • 管道读写的方向(0读1写)
  • 留神敞开文件描述符
  • 父过程wait期待子过程完结,这样可保障输入程序

具体实现:

int main() {    int fd1[2], fd2[2], pid;    char buf;    pipe(fd1);    pipe(fd2);    pid = fork();    if (pid < 0) {        fprintf(2, "fork() error\n");        exit(1);    } else if (pid > 0) { // father        close(fd1[0]);        close(fd2[1]);        write(fd1[1], "a", 1);        read(fd2[0], &buf, 1);        close(fd1[1]);        close(fd2[0]);        wait(0);        printf("%d: received pong\n", getpid());    } else { // child        close(fd2[0]);        close(fd1[1]);        read(fd1[0], &buf, 1);        write(fd2[1], &buf, 1);        close(fd2[1]);        close(fd1[0]);        printf("%d: received ping\n", getpid());    }    exit(0);}

试验后果

  • 失败后果
    其实最开始我没有加wait期待子过程完结,这样的后果就会造成最根本的并发编程谬误,导致父子过程输入的后果可能是交错在一起的,比方这样:

    当然也能够通过sleep或其余形式解决
  • 失常后果
  • 测试后果

primes (moderate)/(hard)

试验要求

实现一个并发的素数筛选程序。具体思路参考:

Hins:

  • 因为XV6资源有余,应及时敞开文件描述符
  • 当写入端敞开时read返回0
  • 间接将int类型数据写入管道

具体实现

试验思路:

  • 首先能够明确父过程的工作:创立管道、开启子过程、写入2~35数据、而后期待就完了,因而父过程绝对简略。
  • 依据图片,子过程的工作:

    • 创立管道、创立子过程
    • 从管道接收数据,第一个肯定为素数
    • 而后利用该素数淘汰后续数据中是该素数整数倍的数据
    • 未淘汰的数据写入管道传给本人的子过程
  • 不难发现,所有的子过程行为十分类似,因而可用递归实现。
  • 若用递归,则首先须要思考终止条件:子过程一个数据都收不到时。即其父过程找不到任何符合条件的数据要发送,就敞开了管道的写入端,子过程read时一个数据都没收到就返回了0。
  • 因而,在子过程中可先read一个int数据,若后果大于0则将收到的数作为判断后续数据的基准素数,若后果等于0,则满足递归终止条件间接完结过程。

注意事项:

  • 及时敞开文件描述符,的确是不够
  • 及时回收子过程

试验代码:

  • 子过程递归函数:

    void processSon(int* dad_fd) {  close(dad_fd[1]);  int first_n, fd[2], pid, ret;  ret = read(dad_fd[0], &first_n, sizeof(int));  if (ret == 0) exit(0);  printf("prime %d\n", first_n);  pipe(fd);  pid = fork();  if (pid < 0) {      fprintf(2, "fork() error\n");      exit(1);  } else if (pid > 0) {      int n;      close(fd[0]);      while (1) {          ret = read(dad_fd[0], &n, sizeof(int));          if (ret > 0) {              if (n % first_n != 0) {                  write(fd[1], &n, sizeof(int));              }          } else {              break;          }      }      close(fd[1]);      close(dad_fd[0]);      wait(0);  } else {      processSon(fd);  }}
  • 主函数

    int main() {  int fd[2], pid;  pipe(fd);  pid = fork();  if (pid < 0) {      fprintf(2, "fork() error\n");      exit(1);  } else if (pid > 0) { // father      close(fd[0]);      for (int i = 2; i < 36; ++i) {          write(fd[1], &i, sizeof(int));      }      close(fd[1]);      wait(0);  } else { // son      processSon(fd);  }  exit(0);}

测试后果

  • 程序成果
  • 测试后果

find (moderate)

试验要求

实现简易版UNIX find程序:查找指定目录下所有文件。保留于user/find.c

hints:

  • 可参考ls.c
  • 可通过递归降落到子目录,但不要递归“.”和“..”
  • 应用strcmp()判断字符串是否雷同

具体实现

ls.c中失去的提醒:

  • 可在main实现缺省参数,在本体函数值专一于有参数失常状况
  • 利用struct statint fstat(int, struct stat*)获取文件状态,其中type字段可判断文件类型(XV6有文件、目录、设施三种类型)
  • 目录文件自身蕴含的文本信息,可利用struct dirent提取,该构造体中的name字段保留目录中文件名

实现思路:

  • 根本与ls.c思路一样。main函数实现缺省参数。而后调用find函数
  • find函数每次关上一个文件,而后判断该文件类型:
  • 若是文件或设施,就间接打印(递归终止条件)
  • 若是目录,就利用struct dirent读取目录下所有文件名,而后每个文件都递归调用find

注意事项:

  • 不要递归“.”和“..”
  • 提前结束时留神敞开关上的文件描述符

试验代码

  • find函数——实现局部,不思考缺省参数

    void find(const char* dir) {  int fd;  struct stat st;   if ((fd = open(dir, 0)) < 0) {      fprintf(2, "find: cannot open %s\n", dir);      return;  }  if (fstat(fd, &st) < 0) {      fprintf(2, "find: cannot stat %s\n", dir);      close(fd);      return;  }  switch (st.type) {  case T_DEVICE:  case T_FILE: {      printf("%s\n", dir);      break;  }  case T_DIR: {      struct dirent de;      char newpath[512];      char* p;      if (strlen(dir) + 1 + DIRSIZ + 1 > sizeof(newpath)) {          printf("find: path too long\n");          break;      }      strcpy(newpath, dir);      p = newpath + strlen(newpath);      *p++ = '/';      while (read(fd, &de, sizeof(de)) > 0) {          if (de.inum == 0 || strcmp(de.name, ".") == 0 ||              strcmp(de.name, "..") == 0)              continue;          memmove(p, de.name, DIRSIZ);          p[DIRSIZ] = '\0';          find(newpath);      }      break;  }  default:      break;  }  close(fd);}
  • 主函数——思考缺省参数,调整后调用find

    int main(int argc, char* argv[]) {  int i;  if (argc < 2) {      find(".");  } else {      for (i = 1; i < argc; i++)          find(argv[i]);  }  exit(0);}

测试后果

  • 程序成果
  • 测试后果

xargs (moderate)

试验要求

实现简易版UNIX xargs程序:该程序在命令行输出参数形容一个命令作为其指定命令,而后从规范输出读取数据,将每行字符作为其指定命令附加的参数运行该命令。保留在user/xargs.c中。

hits:

  • 应用forkexec,并在父过程中期待子过程完结
  • 每次读取一个字符直到呈现换行符('\n')
  • kernel/param.h中申明了MAXARG示意最大参数数量

具体实现

实现代码:

  • 次要逻辑函数xargs

    void xargs(int argc, char* argv[]) {  char buf[BUF_SIZE], *start, *cur;  int len, pid, read_idx = 0;  while ((len = read(0, buf + read_idx,                     BUF_SIZE - read_idx - 1)) > 0) {      read_idx += len;      start = cur = buf;      while (1) {          while (cur - buf < read_idx && *cur != '\n')              ++cur;          if (*cur == '\n')              *cur = '\0';          else              break;          argv[argc] = start;          pid = fork();          if (pid > 0) {              wait((int*)0);              // printf("%d end\n", pid);          } else {              // for (int i = 0; i <= argc + 1; ++i) {              //     printf("argv[%d]=%s, ", i, argv[i]);              // }              // printf("begin %s by %d ...", argv[0], getpid());              // 我也不晓得为什么我的这个文件打不开,利用cat或者vi命令都会卡住不动              if (strcmp(argv[2], "./console") == 0) exit(0);                if (exec(argv[0], argv) < 0) {                  fprintf(2, "exec() error\n");                  }              exit(0);          }          start = cur + 1;      }      memmove(buf, start, cur - start);      read_idx = cur - start;  }}
  • 仍然利用主函数main实现缺省参数

    int main(int argc, char* argv[]) {  char* xargv[MAXARG];  if (argc == 1) {      xargv[0] = "echo";      xargs(argc, xargv);  } else {      for (int i = 0; i < argc - 1; ++i) {          xargv[i] = argv[i + 1];      }      xargs(argc - 1, xargv);  }  exit(0);}

碰到的坑:

  • ./console文件打不开问题
    在过了find | xargs echo ...的命令之后感觉应该没问题了(事实上的确没问题了),而后去尝试find | xargs grep hello,而后就会卡住不动,只能退出虚拟机。贴出来的代码也能够看到,我在exec前打印了要执行的命令、参数和过程号,完结过程也打印一下。加上打印的find | xargs grep hello运行后果如图:

    能够发现这个29号过程怎么都完结不了,其对应的文件就是./console。我始终认为是我程序自身的问题(也可能的确是我的问题),就始终debug,一行一行去试,发现的确是这个子过程不完结,父过程始终wait。(当初想想,其实能够在grep中打个断点再持续debug,之后再去尝试吧,但大概率会在read时阻塞)
    而后我明确了,的确是这个文件的问题。重启虚拟机后用其余命令比方cat/vi试了一试,发现也会卡住:

    遂作罢,只能特定地跳过这个文件了(还是我太菜)
    通过ls能够查看这个文件的信息。发现这个文件是个设施文件,大小为0。据此,我也只能揣测所有试图从该文件中读取数据的程序都读不到数据,也就是read会阻塞,也没方法返回0。

    如果我的揣测是对的,那么对于grep命令,或者须要先判断要搜寻的文件大小是否大于0,而后再去读其中的内容会好一些。
  • 第二个坑——测试时规范输入的'hello'数量
    测试文件中对于xargs的一部分:

    被正文掉的是本来的测试语句,通过判断"hello"在规范输入中呈现的数量是否为3来确定正确与否。其中xargstest.sh的内容通过echomkdir创立一些文件用于检测,也就是运行find | xargs grep hello时呈现的头几行:

    它创立了三个有"hello"的文件。然而find命令还另外输出了b,也就是说文件b要被查两次,这么说后果应该是4个hello,而且命令也在规范输入里啊!于是我每次测试它就讲它须要3个而我出了8个。为了过关,我只能把测试的"hello"数量改了。

测试后果

  • 程序成果

  • 测试后果

总结

Lab1还是比拟敌对也挺乏味的,带我这个小菜鸡正式入了这个课的门。但即便如此,也在最初一题的坑上卡了良久,还须要马不停蹄!

Lab1测试后果