MIT6.S061 lab util

课程简介

MIT新出的操作系统课,和6.828一样都用xv6,但是对我来说,这个更好做一些。

环境配置

我用的是ArchLinux,在win10的VMware虚拟机里面安装的。 但是现在我使用的是直接在ArchLinux里面再用qemu建立一个ArchLinux虚拟机的方法。

1
sudo pacman -S riscv64-linux-gnu-binutils riscv64-linux-gnu-gcc riscv64-linux-gnu-gdb qemu-arch-extra

但是,Arch的软件包太新了,尤其是qemu的版本,太新了,所以需要把qemu和依赖的liburing包降级,降级使用downgrade这个软件,qemu降级到5.1版本,liburing到0.7版本。downgrade可以用yay安装。 riscv64-linux-gnu-gcc的版本要降到10.1。

1
sudo yay -S downgrade

主要内容

sleep(easy)

1
int sleep(int n) //Pause for n clock ticks
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char const *argv[])
{
    /* code */
    if (argc <= 1) {
        fprintf(2, "error: you must type a number!\n");
    }
    else if (argc == 2) {
        int number = atoi(argv[1]);
        sleep(number);
    }
    exit(0);
}

代码没啥难度,确实是easy

pingpong(easy)

1
2
3
4
int fork() //Create a process, return child's PID
int pipe(int p[]) //Create a pipe, put read/write file descriptors in  p[0] and [1]
int read(int fd, char *buf, int n) //Read n byte into buf;returns number read;or 0 if end of file
int write(int fd, char *buf, int n) //Write n bytes from buf to file descriptor fd;returns n

这个不像easy啊,不过要是把xv6-book开头几页读一读,也就easy了。主要难度在理解fork上,父进程和子进程得到的是不同的fork返回值,子进程得到的是0,用这个区分两者的代码。

 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
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char const *argv[])
{
    /* code */
    int p1[2];
    int p2[2];
    pipe(p1);
    pipe(p2);

    int pid = fork();
    if (pid > 0) {
        // printf("current pid: %d\n", getpid());
        // printf("parent: child=%d\n", pid);

        write(p1[1], "z", 1);
        char tmp_father[1];
        // read(p1[1], tmp_father, 1);
        if (read(p2[0], tmp_father, 1) >= 0) {
            printf("%d: received pong\n", getpid());
        }
        // pid = wait((int *) 0);
        // printf("child %d is done\n", pid);
    }
    else if (pid == 0) {
        // sleep(10);
        // printf("current pid: %d\n", getpid());

        char tmp_child[1];
        // read(p1[0], tmp_child, 1);
        if (read(p1[0], tmp_child, 1) >= 0) {
            printf("%d: received ping\n", getpid());
        }
        write(p2[1], tmp_child, 1);
        // printf("child: exiting\n");
        exit(0);
    }
    else {
        printf("fork error\n");
    }
    
    exit(0);
}

primes(moderate/hard)

这个程序用到了fork,pipe,wait,read,write,把数据都写入管道之后,要把p[1]给他close,这样read的时候,返回值会变成0,就可以判断出数据到头了。

这个程序废了好大劲才做出来,不过靠自己一个人做出来,忍住不看其他人的方法,是非常有成就感的。

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int helper(int *p) {
    int p1[2];
    int n;
    int tmp[1];
    pipe(p1);

    if (read(p[0], tmp, 1) != 0) {
        n = tmp[0];
        printf("prime %d \n", tmp[0]);
    }
    else {
        exit(0);
    }

    while(read(p[0], tmp, 1) != 0) {
        // printf("current pid: %d\n", getpid());
        // printf("tmp[i]: %d\n", tmp[0]);
        if (tmp[0] % n != 0) {
            write(p1[1], tmp, 1);
        }
    }
    close(p[0]);
    close(p1[1]);

    if (fork() == 0) {
        // sleep(2);
        helper(p1);
        exit(0);
    }
    else {
        // int pid = wait((int *) 0);
        // printf("pid %d is done\n", pid);
        wait((int *) 0);
        exit(0);
    }
}

int main(int argc, char const *argv[]) {
    /* code */
    int p[2];
    pipe(p);

    int ls[34];
    int k = 2;
    for (int i = 0; i < sizeof(ls) / sizeof(int); i++) {
        ls[i] = k;
        k += 1;
    }

    for (int i = 0; i < sizeof(ls) / sizeof(int); i++) {
        write(p[1], ls + i, 1);
    }
    close(p[1]);

    helper(p);
    exit(0);
}

在做这个程序期间,发现了一个bug,与C语言的特性相关,C语言变量入栈是和声明顺序一样的,所以先声明的先入栈,所以先入栈的在后入栈的底下,地址更小,取数据的时候是从一个地址往后取,那么取先入栈的变量的数据的时候,就有可能取到后入栈的变量的数据,因为他们内存挨在一块。我遇到的就是一个这样的错误。

image-20210629213636339

程序如上图这么写的时候,结果是:

但是,把变量换一个位置:

输出就变成了这样的:

奥莫昔洛依~

find(moderate)

从ls.c上面改一改就可以,直接放代码:

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char* fmtname(char *path) {
  static char buf[DIRSIZ+1];
  char *p;

  // Find first character after last slash.
  for(p=path+strlen(path); p >= path && *p != '/'; p--)
    ;
  p++;

  // Return blank-padded name.
  if(strlen(p) >= DIRSIZ)
    return p;
  memmove(buf, p, strlen(p));
  memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
  buf[strlen(p)] = 0;
  return buf;
}

void find(char *path, char *name) {
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

  if((fd = open(path, 0)) < 0){
    fprintf(2, "ls: cannot open %s\n", path);
    return;
  }

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

  switch(st.type){
    case T_FILE:
      if (!strcmp(fmtname(path), name)) {
        printf("%s\n", path);
      }
      break;

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

int main (int argc, char *argv[])
{
  find(argv[1], argv[2]);

  return 0;
}

就使用ls的递归写法就好,虽然不太习惯他的写法。

xargs(moderate)

要实现的命令的功能如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ echo hello too | xargs echo bye
bye hello too

$ mkdir a
$ echo hello > a/b
$ mkdir c
$ echo hello > c/b
$ echo hello > b
$ find . b | xargs grep hello
$ $ $ $ $ $ hello
hello
hello

就是输出三个文件里面的三个hello

实现的思路:

  1. xargs自己后面的命令,参数,存起来,用字符指针数组params
  2. 从标准输入读入的buf,包括了需要空格,回车和添加到params字符指针数组里面的参数
  3. 如果遇到空格,那是遇到一个新的参数了,需要继续添加
  4. 如果遇到回车,那是要去执行了,开一个子进程执行,父进程等待,然后清空params数组里面刚刚添加的参数,原始添加的参数和命令不清除
  5. buf的末尾也有一个回车

代码如下:

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"

int main (int argc, char *argv[])
{
  if (argc < 3) {
    fprintf(2, "arguments are not enough\n");
    return 0;
  }
  char buf[50];
  char *params[MAXARG];
  char *cmd = argv[1];
  char *p;
  int index = 0;
  int ind = 0;
  int n;
  for (int i = 1; i < argc; i++) {
    params[index++] = argv[i];
  }
  ind = index;//记录index原始参数的下标
  n = read(0, buf, 50);
  p = buf;
  /* printf("xargs: %s\n", buf); */
  /* printf("xargs: %d\n", n); */

  for (int i = 0; i < n; i++) {
    if (buf[i] == ' ') {
      buf[i] = 0;
      params[index++] = p;
      /* printf("xargs: %d\n", p); */
      p = buf + i + 1;

    } else if (buf[i] == '\n') {
      /* printf("xargs: %d\n", i); */
      buf[i] = 0;
      params[index++] = p;
      /* printf("xargs: %d\n", p); */
      p = buf + i + 1;
      /* for (int i = 0; i < 8; i++) { */
        /* printf("%s ", params[i]); */
      /* } */
      printf("\n");
      if (fork() == 0) {
        exec(cmd, params);
        exit(0);
      } else {
        wait((int *) 0);
        for (int i = ind; i < MAXARG; i++) {
          params[i] = 0;
        }
        index--;
        continue;
      }
    }
  }

  return 0;
}

至此,第一个lab的必做题目都搞定了~

updatedupdated2022-06-022022-06-02