MIT6.S081操作系统实验——Xv6-and-Unix-utilities

前言

本实验需要编写一些用户程序,执行系统调用来达成目标。作为第一个实验,本次实验内容比较简单,主要内容是展示用户如何调用操作系统的接口,即system call,在下一个实验中将详细展示系统调用的工作流程。

本次实验的学习目标是:

  • 学习并理解用户使用shell执行命令时的具体过程

    • 命令的参数如何被进程使用
  • 对Unix的进程间通信方式之一pipe有基础了解

    • 为什么用户进程要使用OS提供的IPC进行通信?
  • 了解Unix中的file description,理解Unix中”一切皆文件“的理念

  • 初步了解make项目以及Makefile

实验原文:Lab: Xv6 and Unix utilities (mit.edu)

前置工作

进入工作文件夹下,输入以下命令

git checkout util #切换到util分支,对应本实验
git clean -xfd #清除无关文件(未追踪的、忽略的文件和文件夹)
git checkout -b solveutil #新建一个solveutil分支,来记录自己的编程过程

sleep (easy)

任务

写一个用户程序sleep,执行系统调用来实现休眠。

$ make qemu
...
init: starting sh
$ sleep 10
(nothing happens for a little while)
$

提示

  • 观察user/grep.c等文件来学习命令行参数如何传入程序。
  • sleep.c添加进Makefile中的UPROGS。(添加完后点击Makefile右上角图标重新加载make项目才能将sleep.c加入索引)
  • 在用户没有给出sleep的参数时打印错误信息。
  • 使用atoi函数将命令行参数由字符串转为整形。
  • 确保main函数调用了exit函数来退出程序。

源码

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

int
main(int argc,char* argv[]){
    if(argc != 2){
        fprintf(2, "Usage: sleep ticks\n");//stderr的文件描述符的值为2
        exit(1);
    }
    int ticks = atoi(argv[1]);
    sleep(ticks);
    exit(0);
}

pingpong(easy)

任务

写一个用户程序pingpong,实现两个进程间的数据传递。

 $ make qemu
 ...
 init: starting sh
 $ pingpong
 4: received ping
 3: received pong
 $

提示

  • 使用pipe建立管道。

关于管道的理解可以看这篇博文:Linux系统编程pipe()

  • 使用fork创建子进程。
  • 使用readwrite对管道进行读和写。
  • 使用getpid获取当前进程的pid。

源码

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

int
main() {
    int p1[2];//parent->child
    int p2[2];//child->parent
    pipe(p1);
    pipe(p2);
    if (fork() == 0) {
        //child
        char buffer[] = {'y'};
        read(p1[0],buffer,sizeof(buffer));
        fprintf(2,"%d: received ping\n",getpid());
        write(p2[1],buffer,sizeof(buffer));
        exit(0);
    }
    //parent
    char buffer[] = {'x'};
    write(p1[1],buffer,sizeof(buffer));
    wait(0);
    read(p2[0],buffer,sizeof(buffer));
    fprintf(2,"%d: received pong\n",getpid());
    exit(0);
}

primes(moderate/hard)

任务

使用管道写一个并发版本的素数筛来找出2~35中的素数。

相关链接:Bell Labs and CSP Threads (swtch.com)

概念图:

MIT6.S081操作系统实验——Xv6-and-Unix-utilities_第1张图片

$ make qemu
...
init: starting sh
$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
$

提示

  • 请牢记这个题目的要求是通过多线程来加速素数筛,在编写程序时要检查自己的程序是否满足并发,**一定不要在父进程写入所有数字后子进程才开始处理!**即使这样也能通过测试。

  • 请小心关闭进程不需要的文件描述符,否则程序将在第一个进程达到 35 之前耗尽xv6的资源。(及时关闭管道非常重要!!!

  • 当一个进程读取完所有数字后,应该等到所有他的子进程终止才能终止,从而避免产生僵尸进程。(善用ctrl+p

  • 当一个管道的写端被关闭时,对读端进行read会返回0。

  • 写入所有数字后再写入一个0来表示写入完毕,否则子进程无法知道父进程是否写入完毕。采取这种做法是因为执行了fork后父进程才关闭管道写端,子进程并不能感应到管道写端已经关闭了。

源码

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

int main() {
    int p1[2];
    pipe(p1);
    int i;
    for (i = 2; i <= 35; i++) {
        write(p1[1], &i, sizeof(i));
    }
    i = 0;
    write(p1[1], &i, sizeof(i));
    while (1) {
        int n, prime, p2[2];
        pipe(p2);
        read(p1[0], &prime, sizeof(prime));
        printf("prime %d\n", prime);
        if (read(p1[0], &n, sizeof(n)) && n) {
            if (fork() == 0) {
                //子进程会复制一遍父进程的两个管道,其中父进程用来与祖父进程交流的管道应该被关闭
                close(p1[0]);
                close(p1[1]);
                p1[0] = p2[0];
                p1[1] = p2[1];
                continue;
            }
            do {
                if (n % prime != 0)
                    write(p2[1], &n, sizeof(n));
            } while (read(p1[0], &n, sizeof(n)) && n);
            write(p2[1], &n, sizeof(n));
        }
        //关闭占用的管道
        close(p1[0]);
        close(p1[1]);
        close(p2[0]);
        close(p2[1]);
        break;
    }
    wait(0);
    exit(0);
}

find(moderate)

任务

写一个简单版本的UNIX find程序,ls path filename为寻找目录path下所有文件名为filename的文件并输出。

$ make qemu
...
init: starting sh
$ echo > b
$ mkdir a
$ echo > a/b
$ find . b
./b
./a/b
$ 

提示

  • 查看user/ls.c中的代码来学习如何读取文件夹。
  • 使用递归来实现遍历所有子目录。
  • 不要递归进入.(即当前目录)和..(即父目录)
  • 请使用strcmp函数而非==

源码

//find.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"

char *target;

/*读取path中的文件名*/
char *
filename(char *path) {
    char *p = path;
    while (*p)
        p++;
    while (*p != '/' && p != path)
        p--;
    return p == path ? p : ++p;
}

void
find(char *path) {
    int fd;
    struct stat st;
    struct dirent de;
    char buf[512], *p;
    if ((fd = open(path, O_RDONLY)) < 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;
    }

    switch (st.type) {
        case T_FILE:
            if (strcmp(filename(path), target) == 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 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)
                    continue;
                memmove(p, de.name, DIRSIZ);
                p[DIRSIZ] = 0;
                find(buf);
            }
            break;
    }
    close(fd);
}

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

xargs(moderate)

任务

写一个简单版本的UNIX xargs程序,从STDIN逐行读取,将每一行作为参数执行命令。比如echo hello too | xargs echo bye,要输出bye hello too,等价于echo bye hello too,建议看原文理解。

提示

  • 使用gets函数来读取STDIN。

  • 使用forkexec函数来执行子进程。

代码

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

int
main(int argc, char *argv[]) {
    char buf[64], *xargs[32];
    for (int i = 1; i < argc; i++)
        xargs[i - 1] = argv[i];
    while (1) {
        int x = argc - 1;
        gets(buf, 64);
        if (buf[0] == 0)
            break;
        xargs[x++] = buf;
        for (char *p = buf; *p; p++) {
            if (*p == ' ') {
                *p = 0;
                xargs[x++] = p + 1;
            } else if (*p == '\n') {
                *p = 0;
            }
        }
        if (fork() == 0) {
            exec(argv[1], xargs);
        }
    }
    wait(0);
    exit(0);
}

评测

输入make gradepython ./grade-lab-util来评测整个实验。

若想单独评测一个题目比如sleep可以使用python ./grade-lab-util sleep

评测整个实验前别忘了在Xv6根目录创建一个名为time.txt的文件,在里面输入一个整数表明自己在这个实验上花了多少个小时。

你可能感兴趣的:(MIT6.S081-2021,linux,risc-v,c语言)