返回
Featured image of post MIT 6.S081 Lab 1

MIT 6.S081 Lab 1

MIT 6.S081操作系统课程配套实验——Lab 1

简介

最近一段时间,除了毕设就没有别的什么事情了,于是开始看起了鼎鼎大名的MIT(麻省理工学院)6.S081操作系统课程(实际是三天打鱼两天晒网…之前沉迷于战地1无法自拔)

对于操作系统这门课程,我之前只是自己看书自学了一遍,有很多知识都是一知半解,并没有深入了解其中的原理。这次也算是用这个课程来恶补一下我的基础吧。感谢MIT网课的两位教授,从课堂中我学到了很多很多东西,虽然网课总共只看了1/3,但也可以称得上受益匪浅了。

环境配置

本人的配置:Ubuntu 20.04 WSL2

根据官网上的教程配置即可

注意开启GDB的方式可能和网课上讲的不太一样,查阅资料后发现步骤应该是:

# 在xv6文件夹中
gdb-multiarch kernel/kernel
# 进入GDB后
set architecture riscv:rv64
target remote localhost:26000

代码详解

这个实验相对还是比较简单的,主要是起了热身的作用吧。除了prime,其他任务都很快就完成了。

sleep

// user/sleep.c

void Error_Handler()
{
    fprintf(2, "Usage: sleep [time]\n");
    exit(1);
}

int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        Error_Handler();
    }
    int time = 0;
    if ((time = atoi(argv[1])) == 0)
    {
        Error_Handler();
    }
    sleep(time);
    exit(0);
}

pingpong

// user/pingpong.c

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

int main()
{
    int p[2];
    pipe(p);

    if (fork() == 0)
    {
        close(p[1]);
        char buf[5];
        read(p[0], buf, 4);
        fprintf(1, "%d: received ping\n", getpid());
        close(p[0]);
        exit(0);
    }
    else
    {
        close(p[0]);
        write(p[1], "test", 4);
        close(p[1]);
        wait(0);
        fprintf(2, "%d: received pong\n", getpid());
        exit(0);
    }
}

前面两个都没什么好说的…

primes

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

int primes[11] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};

void prime_sieve(int *data_pipe, int n)
{
    // 注意此处一定要关闭前管道的写端,不然会无法退出子进程!
    close(data_pipe[1]);
    if (n > 10)
    {
        exit(0);
    }

    int inner_p[2];
    pipe(inner_p);

    if (fork() == 0)
    {
        prime_sieve(inner_p, n + 1);
    }
    else
    {

        close(inner_p[0]);
        int num;
        read(data_pipe[0], &num, 4);
        printf("prime %d\n", num);
        while (read(data_pipe[0], &num, 4) != 0)
        {
            if (num % primes[n] != 0)
            {
                write(inner_p[1], &num, 4);
            }
        }
        close(data_pipe[0]);
        close(inner_p[1]);
        wait(0);
        exit(0);
    }
}

int main()
{
    int p[2];
    pipe(p);
    if (fork() == 0)
    {
        prime_sieve(p, 0);
    }
    else
    {
        close(p[0]);
        int i;
        for (i = 2; i <= 35; i++)
        {
            write(p[1], &i, 4);
        }
        close(p[1]);
        wait(0);
    }
    exit(0);
}

这个task还是有点难度的,想出解决的思路不难,但很容易出bug,光debug就花了我三四个小时…

大坑是在创建子进程后,必须先把之前管道的写端给关掉,如果不关掉的话,后面继续fork子进程时,会把之前残留的这个管道写端又复制到子进程,导致子进程会有多个写端,则子进程会不断地等待写入,造成死循环,所有子进程都无法正确关闭。

这个bug隐藏地太深,也许也是我技术太菜的原因,我花了非常多的时间才发现了这个bug,不得不说是个大坑

find

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

void find(char *path, char *name)
{
    int fd;
    struct stat st;
    struct dirent de;
    char buf[512], *p;
    if ((fd = open(path, 0)) < 0)
    {
        fprintf(2, "find: cannot open %s\n", path);
        return;
    }
    if (fstat(fd, &st) < 0)
    {
        fprintf(2, "find: cannot stat %s\n", path);
        close(fd);
        return;
    }
    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, name) == 0)
        {
            printf("%s\n", buf);
        }
        if (stat(buf, &st) < 0)
        {
            fprintf(3, "find: cannot stat %s\n", buf);
            continue;
        }
        if (st.type == T_DIR && strcmp(".", de.name) != 0 && strcmp("..", de.name) != 0)
        {
            find(buf, name);
        }
    }
    close(fd);
}

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

这个task内容也算是比较简单的,比较麻烦的部分在于要参考其他程序中对于文件系统的处理方式,稍花一点时间就可以完全搬过来了

xargs

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

int main(int argc, char *argv[])
{
    // 构造参数列表
    char **args = (char **)malloc(MAXARG * sizeof(char *));
    int i;
    for (i = 0; i < argc - 1; i++)
    {
        args[i] = malloc(strlen(argv[i + 1]) + 1);
        strcpy(args[i], argv[i + 1]);
    }
    int command_i = i;
    char ch;
    char buf[50];
    i = 0;
    while (read(0, &ch, 1) != 0)
    {
        if (ch == '\n')
        {
            buf[i] = 0;
            args[command_i] = (char *)malloc(strlen(buf) + 1);
            strcpy(args[command_i], buf);
            memset(buf, 0, 50);
            i = 0;
            if (fork() == 0)
            {
                exec(args[0], args);
            }
            else
            {
                wait(0);
                int j;
                for (j = 1; j <= command_i; j++)
                {
                    free(args[j]);
                }
                free(args);
                // 重新构造参数列表
                args = (char **)malloc(MAXARG * sizeof(char *));
                int i;
                for (i = 0; i < argc - 1; i++)
                {
                    args[i] = malloc(strlen(argv[i + 1]) + 1);
                    strcpy(args[i], argv[i + 1]);
                }
                command_i = i;
            }
        }
        else if (ch == ' ')
        {
            buf[i] = 0;
            args[command_i] = (char *)malloc(strlen(buf) + 1);
            strcpy(args[command_i], buf);
            memset(buf, 0, 50);
            command_i++;
            i = 0;
        }
        else
        {
            buf[i++] = ch;
        }
    }
    exit(0);
}

由于做这个实验已经过了很久,对这个task的印象不是很深了,我记得我当初是读错了题目要求,所以刚开始怎么都无法解决。后面重新审了题,再加上参考了网上一些大佬的程序,最终还是完成了,总体而言确实是moderate的难度。

总结

对于OS基础薄弱的我来说,这个课程和配套的Lab确实教会了我很多,不光是概念上的知识,也有实际动手的经验。在Lab中,很多晦涩难懂、抽象的东西都变成了实实在在的代码,在将每个步骤拆解、拼装成程序后,自然地就将理论知识和实践融会贯通了。希望学完这门课之后,如果在面试中遇到操作系统原理相关的问题,我能够侃侃而谈,信心十足吧。

感谢MIT,感谢互联网,让全球的每个学生都能享受顶级的计算机知识教育!

(未完待续…)