MIT 6.S081 Lab1

159 阅读3分钟

Lab: Xv6 and Unix utilities

Boot xv6

第一个Lab的首个关卡,就是配置实验所需环境并成功启动xv6。

环境配置参考:Tools Used in 6.S081

使用git下载源码:

$ git clone git://g.csail.mit.edu/xv6-labs-2021
Cloning into 'xv6-labs-2021'...
...
$ cd xv6-labs-2021
$ git checkout util
Branch 'util' set up to track remote branch 'util' from 'origin'.
Switched to a new branch 'util'

这里也给自己新建一个仓库,记录所有Lab的完成情况:

$ git remote rename origin old-origin 
$ git remote add origin <新的仓库的URL>
$ git push -u origin util

这样在自己的仓库也有了util分支,实验预备代码都在old-origin下,完成情况记录到origin中。

进入实验目录中使用下面命令,编译运行xv6操作系统

$ make qemu

使用make grade可以检测每一关的完成情况。

sleep

完成sleep用户程序,很简单只需要调系统接口就行(#include顺序不能随意置换,遵循c构建逻辑)

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]){
    if(argc != 2){
        fprintf(2,"usage: sleep seconds\n");
        exit(1);
    }
    int time = atoi(argv[1]);
    sleep(time);

    exit(0);
}

Makefile文件UPROGS中添加

UPROGS=\
    ...
    $U/_find\

pingpong

这关就是学习使用pipe进行进程间通信,代码如下:

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

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

    if (fork() == 0)
    {
        /* child */
        int pid = getpid();
        char buf[2];
        if (read(p[0], buf, 1) != 1)
        {
            fprintf(2, "child: read failed\n");
            exit(1);
        }
        close(p[0]);
        printf("%d: received ping\n", pid);
        if (write(p[1], buf, 1) != 1)
        {
            fprintf(2, "child: write failed\n");
            exit(1);
        }
        close(p[1]);
        exit(0);
    }
    else
    {
        /* parent */
        pid = getpid();
        char message[2] = "a";
        char buf[2];
        buf[1] = 0;
        if (write(p[1], message, 1) != 1)
        {
            fprintf(2, "parent: write failed\n");
            exit(1);
        }
        // wait for child 
        close(p[1]);
        wait(0);
        if(read(p[0], buf, 1) != 1){
            fprintf(2, "parent: read failed\n");
            exit(1);
        }
        printf("%d: received pong\n", pid);
        close(p[0]);
        exit(0);
    }
}

Makefile中添加

    $U/_pingpong\

primes

实现一个多进程的质数筛,进程间使用pipe通信,虽然是hard难度,但是只要理解了这张图就会很清晰:

prime~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAgQW5lbW9uZV8=_q75.webp

根进程,也就是主进程,创建一个子进程,从2开始向该子进程分发每个整数,子进程使用自己接收的第一个整数作为prime的base,对其余每个整数作模运算,并筛掉结果为0的,其余整数输出给自己的子进程。

每个子进程都为自己创建一个子进程执行上述逻辑,同时wait创建的子进程,以实现并发控制。

代码如下:

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

__attribute__((noreturn)) void
new_proc(int p[2])
{
    int base_prime;
    int flag;
    int n;
    close(p[1]); /* only read */
    if (read(p[0], &base_prime, 4) != 4)
    {
        fprintf(2, "process failed to read from the pipe\n");
        exit(1);
    }
    printf("prime %d\n", base_prime);

    flag = read(p[0], &n, 4);
    if (flag)
    {
        /* read success */
        int new_pipe[2];
        pipe(new_pipe);
        if (fork() == 0)
        {
            /* grandson */
            new_proc(new_pipe);
        }
        else
        {
            close(new_pipe[0]);
            if (n % base_prime)
                write(new_pipe[1], &n, 4);
            while (read(p[0], &n, 4))
            {
                if (n % base_prime)
                    write(new_pipe[1], &n, 4);
            }
            close(p[0]);        /* close read pipe from parent*/
            close(new_pipe[1]); /* close write pipe for child */
            wait(0);
        }
    }
    exit(0);
}

int main(int argc, const char *argv[])
{
    int p[2];
    pipe(p);
    if (fork() == 0)
    {
        /* child */
        new_proc(p);
    }
    else
    {
        /* parent */
        close(p[0]); /* only write */
        for (int i = 2; i <= 35; i++)
        {
            if (write(p[1], &i, 4) != 4)
            {
                fprintf(2, "first process failed to write %d into the pipe\n", i);
                exit(1);
            }
        }
        close(p[1]);
        wait(0);
        exit(0);
    }
    return 0;
}

Makefile中添加

    $U/_primes\

find

实现find用户程序,其中的系统调用使用可以参考ls

代码如下:

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

// Regexp matcher from Kernighan & Pike,
// The Practice of Programming, Chapter 9.

int matchhere(char*, char*);
int matchstar(int, char*, char*);

int
match(char *re, char *text)
{
  if(re[0] == '^')
    return matchhere(re+1, text);
  do{  // must look at empty string
    if(matchhere(re, text))
      return 1;
  }while(*text++ != '\0');
  return 0;
}

// matchhere: search for re at beginning of text
int matchhere(char *re, char *text)
{
  if(re[0] == '\0')
    return 1;
  if(re[1] == '*')
    return matchstar(re[0], re+2, text);
  if(re[0] == '$' && re[1] == '\0')
    return *text == '\0';
  if(*text!='\0' && (re[0]=='.' || re[0]==*text))
    return matchhere(re+1, text+1);
  return 0;
}

// matchstar: search for c*re at beginning of text
int matchstar(int c, char *re, char *text)
{
  do{  // a * matches zero or more instances
    if(matchhere(re, text))
      return 1;
  }while(*text!='\0' && (*text++==c || c=='.'));
  return 0;
}

void find_helper(char *path, char *target){
    char buf[512],*p;
    int fd;
    struct dirent de;
    struct stat st;
    if((fd = open(path, 0)) < 0){
        fprintf(2, "find: cannot open %s\n", path);
        exit(1);
    }

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

    switch(st.type){
        case T_FILE:
            // fprintf(2,"Usage: find <path> <target>\n");
            if(match(target, path)){
                printf("%s\n", path);
            }
            break;
        case T_DIR:
            if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
                /* path长度 + '/' + DIRSIZE + '\0' 大于 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(st.type == T_FILE && strcmp(de.name, target) == 0){
                    printf("%s\n", buf);
                }
                if(st.type == T_DIR && strcmp(de.name, ".") != 0 && strcmp(de.name, "..") != 0){
                    find_helper(buf, target);
                }
            }
            break;
    }
}

int main(int argc, char *argv[]){
    if(argc != 3){
        fprintf(2, "Usage: find <path> <target>\n");
        exit(1);
    }
    find_helper(argv[1], argv[2]);
    exit(0);
}

Makefile中添加

    $U/_find\

xargs

xargs命令行工具:wangchujiang.com/linux-comma…

为了实现xargs需要对输入作处理,分行并按空格划分每一个指令的参数集合,代码如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/param.h"
#include "user/user.h"
void copy(char **p1, char *p2)
{
    *p1 = malloc(strlen(p2) + 1);
    strcpy(*p1, p2);
}

// i - 起始下标
int readLine(char **pars, int i)
{
    int d = 1024;
    char buf[d];
    int j = 0;
    // 读取一行
    while (read(0, buf + j, 1))
    {
        /* 遇到换行符 */
        if (buf[j] == '\n')
        {
            buf[j] = 0;
            break;
        }
        j++;
        if (j == d)
        {
            fprintf(2, "Parameters too long in one line\n");
            exit(1);
        }
    }

    if (j == 0)
    {
        /* 没有读取到内容 */
        return -1;
    }

    /* 以空格划分 */
    int k = 0;
    while (k < j)
    {
        if (i > MAXARG)
        {
            fprintf(2, "Too many parameters\n");
            exit(1);
        }
        while ((k < j) && (buf[k] == ' '))
        {
            /* 忽略 如'  abc   abc'中的多个空格 */
            k++;
        }
        int l = k; /* 起始位置 */
        while ((k < j) && (buf[k] != ' '))
        {
            /* 保留字符串 */
            k++;
        }
        buf[k++] = 0;
        copy(&pars[i], buf + l);
        i++;
    }

    return i;
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        fprintf(2, "usage: xargs command [args]\n");
        exit(1);
    }
    else
    {
        int i;
        char *pars[MAXARG]; /* 参数字符串数组 */
        for (i = 1; i < argc; i++)
        {
            copy(&pars[i - 1], argv[i]);
        }
        int end;
        while ((end = readLine(pars, argc - 1)) != -1)
        {
            pars[end] = 0;
            if (fork() == 0)
            {
                exec(pars[0], pars);
                exit(1);
            }
            else
            {
                wait(0);
            }
        }
    }
    exit(0);
}

Makefile中添加

    $U/_xargs\

Optional challenge exercises(可选的额外练习)

这里有2个,uptime 和 resolve find issues,目前只做了前一个

uptime

非常简单:

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

int main(int argc, char *argv[]){
    printf("%d\n",uptime());
    exit(0);
}

Makefile中添加

    $U/_uptime\

resolve find issues

为find解决一个直接匹配文件名的小问题,这里抄一下grep.c的match函数给find用,已包含在上方的find中。

resolve shell issues TODO


基本完成所有实验,使用 make grade 验证

Score: 99/100