6.S081-Lab Util report

Lab Util report

Boot Xv6 (easy)

没啥复杂的,按照教程来就可以了,基本没踩坑。

Sleep

描述:Implement the UNIX program sleep for xv6; your sleep should pause for a user-specified number of ticks. A tick is a notion of time defined by the xv6 kernel, namely the time between two interrupts from the timer chip. Your solution should be in the file user/sleep.c.

指导(可以称得上是保姆级了。。。):

  • Before you start coding, read Chapter 1 of the xv6 book.
    *Look at some of the other programs in user/ (e.g., user/echo.c, user/grep.c, and user/rm.c) to see how you can obtain the command-line arguments passed to a program.
  • If the user forgets to pass an argument, sleep should print an error message.
  • The command-line argument is passed as a string; you can convert it to an integer using atoi (see user/ulib.c).
  • Use the system call sleep.
  • See kernel/sysproc.c for the xv6 kernel code that implements the sleep system call (look for sys_sleep), user/user.h for the C definition of sleep callable from a user program, and user/usys.S for the assembler code that jumps from user code into the kernel for sleep.
  • Make sure main calls exit() in order to exit your program.
  • Add your sleep program to UPROGS in Makefile; once you’ve done that, make qemu will compile your program and you’ll be able to run it from the xv6 shell.
  • Look at Kernighan and Ritchie’s book The C programming language (second edition) (K&R) to learn about C.

按照教程即可。代码如下:

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

int main(int argc, char *argv[])
{
    int t;
	if (argc <= 1 || argc > 2) {
		fprintf(2, "usage: sleep \n");
        exit(1);
	}
    t = atoi(argv[1]);
    sleep(t);
	exit(0);
}

实现sleep命令没啥好讲的,实际上好玩的是sleep syscall的实现,即如何从用户态陷入内核空间,进而完成sleep命令的。

首先在user.h中声明了sleep这个函数,在usys.S中看到,首先.global sleep在全局符号表中注册函数名sleep,之后调用sleep这个系统调用时就会执行以下这段riscv汇编代码:

.global sleep
sleep:
 li a7, SYS_sleep
 ecall
 ret

这段汇编的意思也很明确,就是把SYS_sleep的值放入a7寄存器中,记录系统调用的类型,之后的ecall指令产生一个异常,此时陷入内核态,然后查看kernel/syscall.c可知SYS_sleepsys_sleep存在映射关系,syscall函数可以根据SYS_值的不同执行不同的sys_函数(kernel/syscall.c:140)。

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]();
  } else {
    printf("%d %s: unknown sys call %d\n",
            p->pid, p->name, num);
    p->trapframe->a0 = -1;
  }
}

反正目前看kernel/syscall.c里的syscall实现,是通过中断实现的(trapframe),具体的实现会等到后面的实验再详细解释。

以上是一个基本的理解,更详细的了解估计在后面的课程中会涉及。

参考:

  • riscv特权等级介绍:riscv特权等级
  • riscv指令手册:riscv-spec

PingPong

描述:Write a program that uses UNIX system calls to ‘‘ping-pong’’ a byte between two processes over a pair of pipes, one for each direction. The parent should send a byte to the child; the child should print “: received ping”, where is its process ID, write the byte on the pipe to the parent, and exit; the parent should read the byte from the child, print “: received pong”, and exit. Your solution should be in the file user/pingpong.c.

指导:

  • Use pipe to create a pipe.
  • Use fork to create a child.
  • Use read to read from the pipe, and write to write to the pipe.
  • Use getpid to find the process ID of the calling process.
  • Add the program to UPROGS in Makefile.
  • User programs on xv6 have a limited set of library functions available to them. You can see the list in user/user.h; the source (other than for system calls) is in user/ulib.c, user/printf.c, and user/umalloc.c.

之前实现过unix的版本,直接把代码copy稍作改动即可。

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


int main()
{
    int p[2], q[2];
    pipe(p), pipe(q);
    if (fork() > 0) {
        close(p[0]);
        close(q[1]);
        char x[1];
        write(p[1], "0", 1);
        read(q[0], x, 1);
        printf("%d: received pong\n", getpid());
        close(p[1]);
        close(q[0]);
        wait(0);
        exit(0);
    } else {
        close(p[1]);
        close(q[0]);
        char x[1];
        read(p[0], x, 1);
        printf("%d: received ping\n", getpid());
        write(q[1], x, 1);
        close(p[0]);
        close(q[1]);
        exit(0);
    }
    return 0;
}

主要涉及管道的基本使用。难度不大。

Primes

描述:Write a concurrent version of prime sieve using pipes. This idea is due to Doug McIlroy, inventor of Unix pipes. The picture halfway down this page and the surrounding text explain how to do it. Your solution should be in the file user/primes.c.

指导:

  • Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
  • Once the first process reaches 35, it should wait until the entire pipeline terminates, including all children, grandchildren, &c. Thus the main primes process should only exit after all the output has been printed, and after all the other primes processes have exited.
  • Hint: read returns zero when the write-side of a pipe is closed.
  • It’s simplest to directly write 32-bit (4-byte) ints to the pipes, rather than using formatted ASCII I/O.
  • You should create the processes in the pipeline only as they are needed.
  • Add the program to UPROGS in Makefile.

首先需要了解两种素数筛法,一种埃氏筛,另一种是欧式筛,相关知识可以查阅基础数论书籍,这里不做赘述。

下面是描述的链接中给出的图,非常直观的解释了本题通过管道使用埃氏筛的思路:

在父进程中接收数据,输出第一个数据(一定是素数,埃氏筛中证明了这一点),并将这个数作为当前进程的一个公因子,之后在父进程中处理后续数据,如果可以被公因子整除,说明一定不是素数,因此drop掉,将满足条件的数据作为子进程的输入,直到输入的数据为空。

6.S081-Lab Util report_第1张图片
我们不难发现,这个过程是一个标准的递归调用过程。而且父进程中需要按顺序完成以下任务:

  1. 输出第一个素数——从输入数据的文件描述符(以下简称输入端)中读取即可;
  2. 检查输入端是否还有数据未处理:
    • 如果还有,那么在父进程中依次读入输入端数据并检查,同时fork一个子进程并建立数据传输管道,将管道的读出端文件描述符作为参数传递;
    • 否则直接exit
  3. 不要忘记在父进程中及时关闭管道的两端,否则父进程中的管道会一直等待输入,造成无法正常退出的情况。
  4. 等待子进程结束即可退出。

因此,经过以上的分析,相信写出代码并不困难,添加了相关注释的代码如下:

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

#define MIN_PRIME 2
#define MAX_NUM 35

/*
 * input data to child process
 * @param  in_fd	input file descriptor
 * @return  none
 */
void foo(int in_fd)
{
    int prime;
    int num;
    /*
     * first num must be prime
     */
    if (read(in_fd, &prime, sizeof(int)) <= 0)
        exit(0);
    printf("prime %d\n", prime);
    /* 
     * if no data on in_fd, exit normally.
     * Because: "You should create the processes 
     * in the pipeline only as they are needed."
     */
    if (read(in_fd, &num, sizeof(int)) <= 0)
        exit(0);
    int p[2];
    pipe(p);
    if (fork() > 0) {
        close(p[0]);
        do {
            if (num % prime) 
                write(p[1], &num, sizeof(int));
        } while (read(in_fd, &num, sizeof(int)) > 0);
    } else {
        close(p[1]);
        foo(p[0]);
    }
    close(p[0]);
    close(p[1]);
    wait(0);
    exit(0);
}


int main(int argc, char *argv[])
{
    int i;

    if (argc > 1) {
        fprintf(2, "usage: primes\n");
        exit(1);
    }
    int p[2];
    pipe(p);
    if (fork() > 0) {
        close(p[0]);
        for (i = MIN_PRIME ; i <= MAX_NUM ; i ++) {
            /*
             * directly write 32-bit (4-byte) ints 
             * to the pipes
             */
            write(p[1], &i, sizeof(int));
        }
    } else {
        close(p[1]);
        foo(p[0]);
    }
    close(p[0]);
    close(p[1]);
    wait(0);
    exit(0);
}

find

描述:Write a simple version of the UNIX find program: find all the files in a directory tree with a specific name. Your solution should be in the file user/find.c.

指导:

  • Look at user/ls.c to see how to read directories.
  • Use recursion to allow find to descend into sub-directories.
  • Don’t recurse into “.” and “…”.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.
  • You’ll need to use C strings. Have a look at K&R (the C book), for example Section 5.5.
  • Note that == does not compare strings like in Python. Use strcmp() instead.
  • Add the program to UPROGS in Makefile.

可以参考ls.c的代码,读懂了不难写出find.c。

代码如下,仅供参考:

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

// DIRSIZ: file and directory max name length


/*
 * get name from path (e.g. getname("abc/ab/a") = "a")
 * @param	path	file path
 * @return	pointer to name
 */
char*
getname(char *path)
{
    static char buf[DIRSIZ + 1];
    char *p;
    for (p = path + strlen(path) ; p >= path && *p != '/' ; p --);
    p ++;
    memmove(buf, p, strlen(p));
    // Note: buf is static, need to process \0
    *(buf + strlen(p)) = 0;
    return buf;
}

void
find(char *path, char *name)
{
    int fd;
    char buf[512], *p;
    struct stat st;
    struct dirent de;

    if((fd = open(path, 0)) < 0){
        fprintf(2, "ls: cannot open %s\n", path);
        return;
    }

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

    switch(st.type) {
    case T_FILE:
        if (strcmp(name, getname(path)) == 0) {
            printf("%s\n", path);
        }
        break;
    case T_DIR:
        if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf) {
            fprintf(2, "%s too long\n", path);
            break;
        }
        strcpy(buf, path);
        p = buf + strlen(buf);
        *p ++ = '/';
        while (read(fd, &de, sizeof(de)) == sizeof(de)) {
            if (de.inum == 0)
                continue;
            // skip "." and ".."
            if (strcmp(de.name, ".") == 0)
                continue;
            if (strcmp(de.name, "..") == 0)
                continue;
            memmove(p, de.name, DIRSIZ);
            p[DIRSIZ] = 0;
            find(buf, name);
        }
        break;
    }
    close(fd);
}

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

xargs

描述:Write a simple version of the UNIX xargs program: read lines from the standard input and run a command for each line, supplying the line as arguments to the command. Your solution should be in the file user/xargs.c.

指导:

  • Use fork and exec to invoke the command on each line of input. Use wait in the parent to wait for the child to complete the command.
  • To read individual lines of input, read a character at a time until a newline (’\n’) appears.
  • kernel/param.h declares MAXARG, which may be useful if you need to declare an argv array.
  • Add the program to UPROGS in Makefile.
  • Changes to the file system persist across runs of qemu; to get a clean file system run make clean and then make qemu.

难度在于理解xargs命令的作用,最常见的应用就是把一个命令的输出当做另一个命令的参数。。。理解了之后就很好写了,代码如下:

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

int
main(int argc, char *argv[])
{
    char buf[32], *p;
    char *my_argv[MAXARG + 1];
    int cnt = 0;
    if (argc < 2) {
        fprintf(2, "usage: xargs  [ ]");
        exit(1);
    }
    if (fork() == 0) {
        for (int i = 1 ; i < argc ; i ++) {
            my_argv[cnt] = (char*)malloc(32);
            memmove(my_argv[cnt ++], argv[i], sizeof(argv[i]));
        }
        char c;
        p = buf;
        while (read(0, &c, 1) == 1) {
            if (c == '\n') {
                *p = 0;
                my_argv[cnt] = (char*)malloc(32);
                memmove(my_argv[cnt ++], buf, sizeof(buf));
                memset(buf, 0, sizeof(buf));
                p = buf;
            } else {
                if (p < buf + sizeof(buf)) {
                    *p++ = c;
                } else {
                    fprintf(2, "arg too long (>32 bytes)\n");
                    exit(1);
                }
            }
        }
        my_argv[cnt] = (char*)malloc(32);
        my_argv[cnt] = 0;
        exec(my_argv[0], my_argv);
    }
    wait(0);
    exit(0);
}

Submit the lab

神奇的是可以提交!不过肯定没人评分就是了hhh

这次实验用了12小时左右,时间比较久,整体上对系统调用特别是管道,forkexec这些系统调用都比较熟悉了,难度适中。

Optional challenge exercises

Write an uptime program that prints the uptime in terms of ticks using the uptime system call. (easy)

待续

Support regular expressions in name matching for find. grep.c has some primitive support for regular expressions. (easy)

待续

The xv6 shell (user/sh.c) is just another user program and you can improve it. It is a minimal shell and lacks many features found in real shell. For example, modify the shell to not print a $ when processing shell commands from a file (moderate), modify the shell to support wait (easy), modify the shell to support lists of commands, separated by “;” (moderate), modify the shell to support sub-shells by implementing “(” and “)” (moderate), modify the shell to support tab completion (easy), modify the shell to keep a history of passed shell commands (moderate), or anything else you would like your shell to do. (If you are very ambitious, you may have to modify the kernel to support the kernel features you need; xv6 doesn’t support much.)

待续

你可能感兴趣的:(操作系统)