MIT-6.S081 | Lab1: Unix utilities(2021)

431 阅读3分钟

lab1

调试准备

​ 我使用Docker的容器作为开发环境,课表是用的2021年的,然后为了方便使用VScode进行写代码以及调试。

配置

首先在xv6-labs-2021目录下创建.vscode文件夹,在这个文件夹下新建launch.json、tasks.json,内容分别如下:

// launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "xv6debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/kernel/kernel",
            "stopAtEntry": true,
            "cwd": "${workspaceFolder}",
            "miDebuggerServerAddress": "127.0.0.1:25000", //见.gdbinit 中 target remote xxxx:xx
            "miDebuggerPath": "/usr/bin/gdb-multiarch", // which gdb-multiarch
            "MIMode": "gdb",
            "preLaunchTask": "xv6build"
        }
    ]
}

// tasks.json
{
    "version": "2.0.0",
    "options": { //指定make qemu的执行位置
        "cwd": "${workspaceFolder}"
    },
    "tasks": [
        {
            "label": "xv6build",
            "type": "shell",
            "isBackground": true,
            "command": "make qemu-gdb",
            "problemMatcher": [
                {
                    "pattern": [
                        {
                            "regexp": ".",
                            "file": 1,
                            "location": 2,
                            "message": 3
                        }
                    ],
                    "background": {
                        "beginsPattern": ".*Now run 'gdb' in another window.",
                        "endsPattern": "."
                    }
                }
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

​ 我在这个过程中出现了

Unexpected GDB output from command "-target-select remote 127.0.0.1:25000". Remote communication error. Target disconnected.: Connection reset by peer

的错误,然后去.gdbinit注释到target remote那行,但是因为make clean后会重新生成,所以直接去.gdbini.tmpl-riscv中注释。

set confirm off
set architecture riscv:rv64
# target remote 127.0.0.1:25000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes

​ 这样就可以正常按F5在vscode中进行调试了。

开始lab

​ C语言中的int main(int argc, char* argv[]) 的含义

  • argc是命令参数个数。
  • argv是字符指针,argv[0]代表可执行程序,argv[1]是第一个参数,类推。

sleep

​ 根据提示,参考echo.c,grep.c,rm.c,使用atoi把参数转为int。

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

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

之后再makefile中找到UPROGS添加一行*$U*/_sleep\,在终端输入make qemu

$sleep
usage: sleep <tick count>
$ sleep 10  //停顿1s

​ 测试还可以直接用./grade--lab-util sleep(或其他作业名)

pingpong

​ 这里要提前了解pipe/fork的用法。

进程间的管道通信

​ 大致意思就是fd[0]是读管道,fd[1]是写管道。

​ fork被调用一次,却能够返回两次,它可能有三种不同的返回值:

  • 在父进程中,fork返回新创建子进程的进程ID;
  • 在子进程中,fork返回0;
  • 如果出现错误,fork返回一个负值;
#include "kernel/fd_types.h"
#include "kernel/types.h"
#include "user/user.h"

int main()
{
    int pfd[2];
    int cfd[2];
    pipe(pfd);
    pipe(cfd);
    char buf[1];
    if(fork() == 0)
    {
        close(pfd[1]); //close write
        close(cfd[0]); //close read
        read(pfd[0], buf, 1);
        printf("%d: received p%sng\n", getpid(), buf);
        write(cfd[1], "o", 1);
        close(cfd[1]);
    }
    else if(fork() > 0)
    {
        close(pfd[0]); 
        close(cfd[1]); 
        write(pfd[1], "i", 1);
        close(pfd[1]);
        read(cfd[0], buf, 1);
        printf("%d: received p%sng\n", getpid(), buf);
    }
    exit(0);
}
$ pingpong
6: received ping
5: received pong

prime

​ 父线程将 2 ~ 35 数字输入pipefork的子进程如同一个递归操作,将第一个数字打印,并将其余不能被这个数字整除的输入到pipe,不断重复操作,直至最后一个数字。 最后打印出来的结果将都会是素数。这篇文章 解释了这个模型。需要注意的是xv6的fd有限,需要注意的是范围为0~15,所以需要将不用的fd close。

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

void filter(int out, int input)
{
    close(input);
    int div;
    // 1. 输出第一个
    if (read(out, &div, sizeof(int)) <= 0)
        exit(0);
    fprintf(2, "prime %d \n", div);

    int p[2];
    pipe(p);

    

    // 2. 过滤,符合要求的发送到新的pipe
    int num;
    while (read(out, &num, sizeof(int)))
    {
        if (num % div != 0)
        {
            write(p[1], &num, sizeof(int));
        }
    }
    close(out);
    // 3. 让子进程继续重复操作
    if (fork())
    {
        filter(p[0], p[1]);
        return;
    }
    wait(0);
    exit(0);
}

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

    if (fork())
    {
        filter(p[0], p[1]);
    }
    else
    {
        for (int i = 2; i <= 35; i++)
        {
            write(p[1], &i, sizeof(i));
        }
        wait(0);
        exit(0);
    }
    return 0;
}

find

​ 主体思路,参考ls.c,需要注意的是范围为0~15,所以需要将不用的fd close。

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

char* fmtname(char *path) // 从路径中获取文件名
{
    char *p;
    for (p = path + strlen(path); p >= path && *p != '/'; p--); // 从后往前找到第一个'/'
    return ++p;
}

void find(char* path, char* name) // 递归查找
{
    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);
        close(fd);
        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), name) == 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;
            if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0)   // "." ".." 跳过
                continue;
            memmove(p, de.name, DIRSIZ);
            p[DIRSIZ] = 0;
            if(stat(buf, &st) < 0)
            {
                printf("find: cannot stat %s\n", buf);
                continue;
            }
            find(buf, name);
        }
        break;
    
    }
    close(fd);
}

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

xargs

xargs就是在输出前加上额外信息

> xargs echo bye
in:hello too
out:bye hello too
#include "kernel/types.h"
#include "user/user.h"

int main(int argc, char* argv[])
{
    char buf2[512];  // 读取的字符
    char buf[32][32];  
    char *pass[32];  

    for(int i=0;i<32;++i)  
    {
        pass[i] = buf[i]; 
    }
    int i;
    for(i=1;i<argc;++i)
    {
        strcpy(buf[i-1], argv[i]);  
    }
    int n;
    while ((n = read(0, buf2, sizeof(buf2))) > 0) // 从标准输入读取
    {
        int pos = argc - 1;  // 从第一个参数开始
        char *c = buf[pos];  // 指向第一个参数的首地址
        for (char *p = buf2; *p; p++)
        {
            if (*p == ' ' || *p == '\n') // 遇到空格或者换行符
            {
                *c = '\0';
                pos++;
                c = buf[pos];
            }
            else
                *c++ = *p;
        }
        *c = '\0';
        pos++;
        pass[pos] = 0;

        if (fork())
        {
            wait(0);
        }
        else
            exec(pass[0], pass);
    }

    if (n < 0)
    {
        printf("xargs: read error\n");
        exit(0);
    }

    exit(0);
}

创建time.txt 中随便加一个数字即可。

参考

Bell Labs and CSP Threads
fork