Lab 1 Util

Boot xv6

按照指导从仓库git clone代码,然后在运行

  1. make qemu

启动xv6。

sleep

按照提示,理解如何获取传给程序的命令行参数,调用sleep系统调用。

显然,输入的第二个命令行参数为sleep系统调用的参数,所以使用atoi()将字符串转化为数字,为暂停的时钟节拍。

#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 error...\n");
        exit(1);
    }
    sleep(atoi(argv[1]));
    exit(0);
}

pingpong

题意可知,使用管道来进行父进程与子进程之间的”ping-pong“。关于管道的知识点,除了xv6book的1.3外,还可以参考pipe()系统调用

父进程要向子进程发送一个字节,所以当fork()!=0时,进入父进程,先写入一个字节,通过管道,子进程可以读取该字节,并在管道中写入字节发送给父进程,然后退出。在父进程中,wait(0)等待子进程的退出,然后读取管道中子进程写入的字节。其中,close()函数用于关闭管道的读取端或者写入端。

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

int
main(int argc, char *argv[])
{
    int pid;
    int p[2];
    char buf[2];
    if(argc > 1)
    {
        fprintf(2, "Usage: pingpong error...\n");
        exit(1);
    }
    pipe(p);
    if(fork() == 0) //child
    {
        pid = getpid();
        if(read(p[0], buf, 1) != 1)
        {
            fprintf(2, "Failed to read in child\n");
            exit(1);
        }
        close(p[0]);
        printf("%d: received ping\n", pid);
        if(write(p[1], buf, 1) != 1)
        {
            fprintf(2, "Failed to write in child\n");
            exit(1);
        }
        close(p[1]);
    }
    else // parent
    {
        pid = getpid();
        if(write(p[1], buf, 1) != 1)
        {
            fprintf(2, "Failed to write in parent\n");
            exit(1);
        }
        close(p[1]);
        wait(0);
        if(read(p[0], buf, 1) != 1)
        {
            fprintf(2, "Failed to read in parent\n");
            exit(1);
        }
        printf("%d: received pong\n", pid);
        close(p[0]);
    }
    exit(0);
}

primes

本题需要用管道来编写一个素数筛的并发版本。下面的代码和图片是对于这个方法的解释,源于这篇文章。此外,这里有一篇关于素数筛法介绍的文章。

p = get a number from left neighbor
print p
loop:
    n = get a number from left neighbor
    if (p does not divide n)
        send n to right neighbor

Lab 1 Utilities - 图1

通过题意和提示,我们知道第一个进程会将数字2到35输入管道之中,然后在子进程中,会实现素数筛的过程,可以创建一个child_process函数作为子进程的操作。

根据上图和伪代码的解释,我们可以使用read在管道中读取左邻居的数,将这个数输出。由pipe()系统调用中我们可以知道,管道的行为类似于队列,为FIFO先进先出,所以第一个读取到的数为2,必为prime。由素数筛法可知,我们可以去掉所有被2整除的数,于是,读取的数如果被2整除就不写入孙子进程的管道。

周而复始,我们可以得到正确的答案,当写入管道关闭时,read返回零,循环结束。在这个过程中,及时close,防止系统资源不足。

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

void
child_process(int p[2])
{
    int n;
    int pp[2];
    int prime;
    close(p[1]);
    if(read(p[0], &prime, 4) != 4)
    {
        fprintf(2, "Failed to read prime\n");
        exit(1);
    }
    printf("prime %d\n", prime);
    if(read(p[0], &n, 4))
    {
        pipe(pp);
        if(fork() == 0)
        {
            child_process(pp);
        }
        else
        {
            do
            {
                if(n % prime) write(pp[1], &n, 4);
            }
            while(read(p[0], &n, 4));
            close(p[0]);
            close(pp[1]);
        }
    }
    wait(0);
    exit(0);
}

int
main(int argc, char *argv[])
{
    int p[2];
    if(argc > 1)
    {
        fprintf(2, "Usage: primes error...\n");
        exit(1);
    }
    pipe(p);
    if(fork() == 0) // child
    {
        child_process(p);
    }
    else // parent
    {
        close(p[0]);
        for(int i=2; i<=35; i++)
        {
            write(p[1], &i, 4);
        }
        close(p[1]);
    }
    wait(0);
    exit(0);
}

find

该题参照ls.c进行修改,得到相应的功能。

对于fmtname函数,我们看到ls中实现的功能有包括后面的一串空格,而在find中我们并不需要,于是删掉与生成一串空格相关的代码,直接返回p。

在find函数主体中,与ls函数主题框架相比变化不大,在switch中,如果st.type类型为文件,修改为如果输入参数得到的文件名与目标文件名对应,则输出。如果是文件夹,在文件夹中做与ls一样的处理,不一样的是,递归find,在子文件夹中寻找,注意,不要在”.”和”..”中递归,于是加入判定条件。

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

char*
fmtname(char *path)
{
    char *p;

  // Find first character after last slash.
    for(p=path+strlen(path); p >= path && *p != '/'; p--)
        ;
    p++;
    return p;
}

void find(char *path, char *file)
{
    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);
        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(fmtname(path), file) == 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)
                continue;
            memmove(p, de.name, DIRSIZ);
            p[DIRSIZ] = 0;

            if(stat(buf, &st) < 0){
                printf("find: cannot stat %s\n", buf);
                continue;
            }
            if(!strcmp(de.name, ".") || !strcmp(de.name, "..")) continue;
            find(buf, file);
        }
        break;
    }
    close(fd);
}

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

xargs

这道题涉及到的知识点比较多,做起来并不是很清晰,参考了几种思路,最后选择了比较好理解的一种。

首先,关于xargs指令,有几点要注意的。我们可以看到例子中的

$ echo hello too | xargs echo bye
bye hello too

展示了xargs的功能,在这里,涉及到一个管道符“|”的知识。

对于管道符

Command A | Command B

将命令A的输出作为命令B的输入。于是这里又涉及到了标准化输入和标准化输出的内容,而在题目描述中也提及了标准化输入。查阅xv6 Book,可以知道标准化输入的文件描述符(file descriptor)为0。

下面是三个核心问题:

  1. 怎么获取前一个命令的标准化输出(此命令的标准化输入)?
  2. 如何获取自己的命令行参数?
  3. 如何使用exec执行命令?

建立一个xargv指针数组,用于储存相应的命令行参数。接着,对标准化输入进行读取,并循环至文件结束。指针b在数组buf中进行操作,如果对应的数组元素为’\n’则fork,且要将该元素置为0。在子进程中,调用exec,在第一个命令行参数中,知道要执行的文件。在父进程中,等待子进程的完成。

补充:在make grade跑分时发现,将元素置为0时要注意要在fork之前。

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

int
main(int argc, char *argv[])
{
    char buf[MAXARG];
    char *xargv[MAXARG];
    char *b = buf;
    int n;
    int xargc = argc - 1;
    if(argc <= 1)
    {
        fprintf(2, "Usage:xargs error...\n");
        exit(1);
    }
    for(int i=1; i<argc; i++)
    {
        xargv[i-1] = argv[i];
    }
    while((n = read(0, b, 1)) > 0)
    {
        if(*b == '\n')
        {
            *b = 0;
            if(fork() == 0)
            {
                xargv[xargc] = buf;
                exec(xargv[0], xargv);
                exit(0);
            }
            else
            {
                wait(0);
            }
            b = buf;
        }
        else 
        {
            ++b;
        }
    }
    exit(0);
}