新开坑 6.s081 系列,这个系列的 lab 其实已经做了一些了,但是没有整理出来,现在陆续整理出来,整理的过程也能加深对知识点的理解。这个 lab 的内容主要是熟悉 xv6 及其系统调用。

Boot xv6

说明一下我这里的环境,使用的是 docker 镜像的方式,然后将代码路径映射到本地,在本地写代码,在容器里调试。这里附上一条 docker 命令,其他编的译环境搭建进到容器里执行 tool guide 命令安装相应的工具即可。

1
docker run --name xv6 -v ~/yourcodefolder/xv6-labs-2020:/yourcodefolderincontainer/xv6-labs-2020  -it ubuntu  /bin/bash

启动 xv6 执行 make qemu 验证是否能正常启动。

Exercises

sleep

这里需要先看一下 xv6 的第一章,关于系统调用的部分。xv6 的内核为程序运行提供服务,每个运行的程序称为进程,进程包括执行的代码指令,数据和运行栈。当进程需要使用内核提供的服务时就调用内核提供的系统调用进入内核,内核执行相应的服务程序并返回结果给用户态。因此一个进程可以在用户态和内核态交替执行。

内核使用 CPU 提供的特权指令功能确保在用户态执行的程序只能访问用户态对应的内存,内核具有执行特权指令的权限,而用户态不可以,如果需要执行特权指令就需要通过系统调用进入内核态执行。

xv6 提供了一系列的服务包括,进程,内存,文件描述符,管道,文件系统等。unix shell 就是使用这些系统调用服务的代表。

调用 sleep 系统调用实现用户态的 sleep 程序。

实现的话根据 hint 来,比较简单。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  if(argc < 2 || argc> 2){
    fprintf(2, "Usage: sleep 1...\n");
    exit(1);
  }

  int ticks = atoi(argv[1]);
  if(sleep(ticks) != 0){
    fprintf(2, "Error on syscall sellp");
    exit(1);
  }

  exit(0);
}

pingpong

这里是要实现两个进程通过管道进行系统调用。关于 pipe 系统调用,创建管道时传入两个文件描述符,一个用于读,另一用于写。这里需要先创建一个管道,然后 fork 出一个子进程出来,fork 出来的子进程仍然拥和父进程一样的指向管道的文件描述符,然后通过 read 系统调用从管道中读数据,如果管道中没有数据时阻塞直到有数据读出来或者是所有指向管道写端的文件描述符都关闭了就返回 0。

实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  int p[2];
  char buf[1];
  pipe(p);
  if (fork() == 0) {
    read(p[0], buf, sizeof buf);
    write(p[1], buf, sizeof buf);
    fprintf(1, "%d: received ping\n", getpid());
  } else {
    write(p[1], buf, sizeof buf);
    wait(0);
    read(p[0], buf, sizeof buf);
    fprintf(1, "%d: received pong\n", getpid());
  }
  exit(0);
}

primes

这里要实现一个质数筛,其原理在 https://swtch.com/~rsc/thread / 有详细的介绍。

下面给出了一个示意图,基本原理是每个进程在一个管道中用一个质因子来筛。

代码实现,这里要注意的是管道某一端不用了以后一定要立即关闭,不然可能描述符不够用,或者 read 一直阻塞在那里导致进程无法退出。

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

void
filter(int in, int out, int prime) {
  while (1) {
    int t;
    if (read(in, &t, sizeof(int)) == 0) {
      close(in);
      break;
    }
    if (t % prime != 0) {
      write(out, &t, sizeof(int));
    }
  }
  close(out);
}

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

  if (fork() == 0) {
    close(p[0]);
    for (int i = 2; i < 36; i++) {
      write(p[1], &i, sizeof(int));
    }
    close(p[1]);
  } else {
    close(p[1]);
    while (1) {
      int t;
      int n = read(p[0], &t, sizeof(int));
      if (n < 0) continue;
      if (n == 0) break;
      printf("prime %d\n", t);

      int cp[2];
      pipe(cp);
      if (fork() == 0) {
        close(cp[0]);
        filter(p[0], cp[1], t);
      } else {
        close(p[0]);
        close(cp[1]);
        p[0] = cp[0];
      }
    }
  }

  exit(0);
}

find

这里可以参数 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
77
78
79
80
81
82
83
84
85
86
87
#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), '\0', DIRSIZ-strlen(p));
  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, "find: cannot find %s\n", path);
    return;
  }

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

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

  case T_DIR:
    if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
      printf("find: 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(stat(buf, &st) < 0){
        printf("find: cannot stat %s\n", buf);
        continue;
      }

      if (strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0) {
        find(buf, name);
      }
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{
  if(argc < 3){
    printf("Usage: find path filename\n");
    exit(0);
  }
  find(argv[1], argv[2]);
  exit(0);
}

xargs

xargs 的作用是从标准输入读出每一行作为命令行参数,例如 find . b | xargs grep hello 就是在 b 目录下的每个文件里查找 hello 字符串。

整个过程就是在标准输入中提取每一行作为参数,再调用 fork, 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
46
47
48
49
50
51
52
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

int
main(int argc, char *argv[])
{
  if (argc < 2) {
    printf("Usage: xargs command\n");
    exit(1);
  }
  char buf[512];
  char *xargv[MAXARG];
  xargv[0] = argv[1];
  xargv[1] = argv[2];
  int m = 0;
  int i = 0;
  while (read(0, &buf[i], 1) == 1) {
    if (buf[i] == '\n' || buf[i] == '\0') {
      buf[i] = '\0';

      int k = 2;
      int b = 0;
      int e = 0;

      while (e < i) {
        while (e < i && buf[e] != ' ') e++;
        int l = e - b + 1;
        char s[l];
        s[l-1] = '\0';
        memmove(s, buf + b, e - b);
        xargv[k++] = s;
        while (e < i && buf[e] == ' ') e++;
        b = e;
      }

      i = 0;
      m++;
      if (fork() == 0) {
        exec(xargv[0], xargv);
      } else {
        continue;
      }
    }
    i++;
  }
  while (m--) {
    wait(0);
  }
  exit(0);
}

总结

这个 lab 主要是熟悉如果使用系统调用,以及一些基础的系统调用的原理,至于系统调用是如何执行的并没有涉及,应该在后面的 lab 中会有。