MIT 6.1810 Operating System Engineering

Reference: https://pdos.csail.mit.edu/6.1810/2022/index.html

6.1810 是MIT的著名的操作系统课程6.828的后续版本,主要区别是6.828基于x86指令集,6.1810基于risc-v指令集。之前做6.828的时候,因为太菜了,实在是做不下去🥺,一度弃坑。趁着毕业之前还有很多时间,再来一遍,争取这次能够做完这套课程。

实验环境设置

我平常的开发设备是MacBook M1,按照Tools[1]中的macOS的工具安装方法,安装了risc-v的cross compile编译工具之后,发现并不能编译成功。多次尝试未果之后,果断放弃,选择拥抱远端环境。这次就没有像上次一样用阿里云的服务器了,这次用的是GitHub提供的Codespaces[2]。这套环境,可以很方便在不同设备上进行使用,可以选择用本地的vscode,也可以选择用浏览器版本的vscode,还可以用ssh连接端口使用vim,超级方便🤣。唯一的不好的地方就是网络差的时候用起来有点卡。

按照Tools[1]中提供的步骤,在Codespaces[2]中安装必要的环境和工具。

sudo apt-get update && sudo apt-get upgrade -y
sudo apt-get install -y git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

Lab: Xv6 and Unix utilities

这是第一个Lab,主要是熟悉下环境,然后编写几个命令行小工具。

sleep(easy)

在xv6中实现UNIX当中的sleep函数。

user/sleep.c的代码实现如下

/*
    filename: user/sleep.c
*/

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

int main(int argc, char *argv[]){
    if(argc != 2){
        exit(0);
    }
    // atoi 是将字符串转换成数字,定义在user/user.h中
    int n = atoi(argv[1]);
    sleep(n);
    exit(0);
}

实现完成之后,需要在Makefile文件中添加上这个文件生成的执行文件。

UPROGS=\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_sleep\  # 新增此行

然后在命令行中执行./grade-lab-util sleep来检查是否正确。

参考的commit: https://github.com/dianhsu/xv6-riscv/commit/eee6ab6d5a3c7df8f9f0fff1ac036ae38105ab3a

pingpong(easy)

在xv6中实现UNIX当中的pingpong函数。

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

int main(int argc, char* argv[]){
    char buf[16];
    int p_p2c[2], p_c2p[2];
    if(pipe(p_p2c) < 0){
        exit(1);
    }
    if(pipe(p_c2p) < 0){
        exit(1);
    }
    if(fork() > 0){
        close(p_p2c[0]);
        close(p_c2p[1]);

        write(p_p2c[1], "p", 1);
        read(p_c2p[0], buf, 1); 
        fprintf(2, "%d: received pong\n", getpid());

        close(p_p2c[1]);
        close(p_c2p[0]);
        wait(0);
    }else{
        close(p_p2c[1]);
        close(p_c2p[0]);

        read(p_p2c[0], buf, 1);
        fprintf(2, "%d: received ping\n", getpid());
        write(p_c2p[1], "p", 1);

        close(p_p2c[0]);
        close(p_c2p[1]);
        exit(0);
    }

    exit(0);
}

实现完成之后,需要在Makefile文件中添加上这个文件生成的执行文件。

UPROGS=\
	$U/_grind\
	$U/_wc\
	$U/_zombie\
	$U/_sleep\  
    $U/_pingpong\ # 新增此行

primes(moderate)

#include "kernel/types.h"
#include "user/user.h"
int isPrime(int v){
    for(int i = 2; i * i <= v; ++i){
        if(v % i == 0){
            return 0;
        }
    }
    return 1;
}
int main(){
    int p[2];
    int c[2];
    if(pipe(p) < 0){
        exit(1);
    }
    if(pipe(c) < 0){
        exit(1);
    }
    if(fork() > 0){
        close(p[0]);
        close(c[1]);
        char buf[8];
        for(int i = 2; i <= 35; ++i){
            buf[0] = i;
            write(p[1], buf, 1);
        }
        close(p[1]);
        while(read(c[0], buf, 1) != 0){
            fprintf(2, "prime %d\n", (int)buf[0]);
        }
        close(c[0]);
        wait(0);
    }else{
        close(p[1]);
        close(c[0]);
        char buf[8];
        while(read(p[0], buf, 1) != 0){
            int v = buf[0];
            if(isPrime(v)){
                if(fork() == 0){
                    write(c[1], buf, 1);    
                    close(c[1]);
                    exit(0);
                }
                wait(0);
            }
            if(v == 35) break;
        }
        close(p[0]);
        close(c[1]);
        exit(0);
    }
    exit(0);
}

find(moderate)

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/fs.h"
#include "user/user.h"
void checkAndShow(char* path, char* name){
    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;
    memmove(buf, p, strlen(p));
    memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));
    for(int i = 0; ; ++i){
        if(p[i] == '\0' && name[i] == '\0'){
            printf("%s\n", path);
            break;
        }else if(p[i] == '\0' || name[i] == '\0'){
            break;
        }else if(p[i] != name[i]){
            break;
        }
    }
}
void find(char* program, char *path, char* name){
    char buf[512], *p;
    int fd;
    struct dirent de;
    struct stat st;
    if((fd = open(path, 0)) < 0){
        fprintf(2, "%s: cannot open %s\n", program, path);
        return;
    }
    if(fstat(fd, &st) < 0){
        fprintf(2, "%s: cannot stat %s\n", program, path);
        close(fd);
        return;
    }
    switch(st.type){
        case T_FILE:
            if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
                printf("%s: path too long\n", program);
                break;
            }
            checkAndShow(path, name);
            break;
        case T_DIR:
            if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
                printf("%s: path too long\n", program);
                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("%s: cannot stat %s\n", program, buf);
                    continue;
                }
                if(st.type == T_DIR && (p[0] == '.' && (p[1] == '\0' || (p[1] == '.' && p[2] == '\0')))){
                    continue;
                }else{
                    find(program, buf, name);
                }
            }
            break;
    }
    close(fd);
}

int main(int argc, char* argv[]){
    if(argc == 2){
        find(argv[0], ".", argv[1]);
        exit(0);
    }
    if(argc != 3){
        exit(0);
    }
    char *dir = argv[1];
    char *name = argv[2];
    find(argv[0], dir, name);
    exit(0);
}

xargs(moderate)

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

#define MAXLEN 1024
char *argvs[MAXARG + 1];
char buf[512];
char argBuf[MAXLEN];

int main(int argc,  char* argv[]){
    if(argc <= 1){
        exit(0);
    }
    int idx = 0;
    argvs[idx] = 0;
    while(idx + 1 < argc){
        argvs[idx] = argv[idx + 1];
        ++idx;
        argvs[idx] = 0;
    }
    int cnt = 0;
    int pos = 0;
    while((cnt = read(0, buf, sizeof(buf))) > 0){
        if(pos + cnt >= MAXLEN) exit(0);
        memmove(argBuf + pos, buf, cnt);
        pos += cnt;
    }
    for(int i = 0; i < pos; ++i){
        if(i == 0 || argBuf[i - 1] == '\0'){
            if(idx >= MAXARG) exit(0);
            argvs[idx++] = argBuf + i;
        }
        if(argBuf[i] == '\n') argBuf[i] = '\0';
    }
    int pid;
    if((pid = fork()) > 0){
        wait(0);
    }else{
        exec(argvs[0], argvs);
        exit(0);
    }
    exit(0);
}

Lab: system calls

System call tracing (moderate)

In this assignment you will add a system call tracing feature that may help you when debugging later labs. You’ll create a new trace system call that will control tracing. It should take one argument, an integer “mask”, whose bits specify which system calls to trace. For example, to trace the fork system call, a program calls trace(1 << SYS_fork), where SYS_fork is a syscall number from kernel/syscall.h. You have to modify the xv6 kernel to print out a line when each system call is about to return, if the system call’s number is set in the mask. The line should contain the process id, the name of the system call and the return value; you don’t need to print the system call arguments. The trace system call should enable tracing for the process that calls it and any children that it subsequently forks, but should not affect other processes.

In this task, we are going to design a trace program in order to trace the system calls. The trace program will take one argument, an integer “mask”, whose bits specify which system calls to trace. The other arguments will be the program to be traced and the arguments of traced program. For example, in the first case(trace 32 grep hello README), the program will trace the read system call, due to the 1 << SYS_read in kernel/syscall.h is 32. The grep is the running program, hello and README will pass to grep as arguments.

Step1. Add a system call named trace

In user/user.h, we add a system call named trace:

// other system calls
int trace(int);
// other system calls

and we modify user/usys.pl to add the system call:

entry("trace");

In kernel/syscall.h, we define a new system call named trace:

#define SYS_trace 22

In kernel/proc.h, we add a mask flag in struct proc:

struct proc{
    // other fields
    int mask;
}

In kernel/sysproc.c, we add the implementation of trace, we use argint to get the mask and set it to the current process:

uint64
sys_trace(void)
{
  int mask;
  if(argint(0, &mask) < 0){
    return -1;
  }
  myproc()->mask = mask;
  return 0;
}

In kernel/proc.c, we modify the fork function, we copy the mask from the parent process to the child process:

int 
fork(void){
    // other code
    np->sz = p->sz;
    np->mask = p->mask;
    // other code
}

Finally, we print system calls usage in kernel/syscall.c, and we should create a system call name array in kernel/syscall.c:

static uint64 (*syscalls[])(void) = {
// other system calls
[SYS_trace]   sys_trace,
// other system calls
};
extern int sys_trace(void);
static char* syscall_name[] = {
"",
"fork",
"exit",
"wait",
"pipe",
"read",
"kill",
"exec",
"fstat",
"chdir",
"dup",
"getpid",
"sbrk",
"sleep",
"uptime",
"open",
"write",
"mknod",
"unlink",
"link",
"mkdir",
"close",
"trace",
};
void
syscall(void)
{
    int num;
    struct proc *p = myproc();
    num = p->trapframe->a7;
    if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
        p->trapframe->a0 = syscalls[num]();
        if((p->mask >> num) & 1){
            // if the system call is traced, print the system call name and return value.
            printf("%d: syscall %s -> %d\n", p->pid, syscall_name[num], p->trapframe->a0);
        }
    } else {
        printf("%d %s: unknown sys call %d\n",
                p->pid, p->name, num);
        p->trapframe->a0 = -1;
    }
}

Commit: https://github.com/dianhsu/xv6-riscv/commit/25048b45071e4331a7374c75b9ef076877c56d91

FAQs

In Linux environment, make report error: -Winfinite-recursion?

solve: add -Wno-infinite-recursion to Makefile.

CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -Wno-infinite-recursion

References


MIT 6.1810 Operating System Engineering
https://www.dianhsu.com/2022/09/11/6-1810/
Author
Dian Hsu
Posted on
September 11, 2022
Licensed under