Lab1: Xv6 and Unix utilities

实现代码

1、搭建Lab环境

1.1、安装工具包

使用的是之前跑在 VirtualBox 上的 Ubuntu 虚拟机 版本 20.04.5 TLS

然后按照官网上的指令下载对应的 工具包

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu

完成之后可以测试一下  

然后到lab页 clone相应的代码

最后执行 make qemu,出现  

即为搭建成功

1.2、gdb调试过程

1、打开两个cmd窗口

2、其中一个输入:make qemu-gdb

3、另一个输入:gdb-multiarch kernel/kernel

4、可以使用gdb命令进行调试了

2、Utilities实现

2.1、sleep

这个函数比较简单,使用系统调用 sleep

int main(int argc, char *argv[]){
    if(argc < 2){
        fprintf(2, "too few arguments...");
        exit(1);
    }
    sleep(atoi(argv[1]));
    exit(0);
}

2.2、pingpong

这个函数的实现需要重点理解pipe函数(xv6-book的Chapter One)

感性的理解,pipe就是创建了一个管道,使得两个进程之间可以通信(互相读取到对方写的数据)

实际上,pipe在传入的数组p里存放一个read file descriptor(p[0])和write file descriptor(p[1])

就像管道的一头一尾,从"头部"写入数据,可以从"尾部"读取数据

而由于fork创建的子进程也会带着这个数组p,从而可以实现父子进程的通信

(具体细节参见xv6-book的相应部分)

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

    char * parent_msg = "p";
    char * child_msg  = "c";
    char buffer[2];

    pipe(p);
    if(fork() == 0){
        printf("%d: received ping\n", getpid());
        close(p[0]);
        write(p[1], child_msg, 1);
        close(p[1]);
        exit(0);
    } else {
        write(p[1], parent_msg, 1);
        close(p[1]);
        wait(0);
        read(p[0], buffer, 1);
        close(p[0]);
        printf("%d: received pong\n", getpid());
        exit(0);
    }
}

2.3、primes

primes函数是借助pipeline实现了一个素数筛。

需要好好理解下面这幅图和上面的pipe

思想:在每个进程中,选取一个素数N(就是通过管道读到的第一个数),然后把所有读到的数中不能被整除N的数继续通过管道写进下一个进程中,而把可以整除的数drop掉。

void func(int * p){
    int p1[2];
    p1[0] = p[0]; p1[1] = p[1];

    pipe(p); // create the new pipeline connect the it and the it's child

    char num[1];
    close(p1[1]); // close the old write
    if( read(p1[0], num, 1) == 1 ){
        int prime = num[0]; // the first number is the prime
        printf("prime %d\n", prime);

        if(fork() == 0){
            func(p); // recursion
        } else {
            close(p[0]); // close the read
            while ( read(p1[0], num, 1) == 1 ){
                int n = num[0];
                if(n % prime != 0){
                    write(p[1], num, 1);
                }
            }
            close(p1[0]);
            close(p[1]);
            wait(0);
        }
    } else { // no data avaliable
        close(p[0]);
        close(p[1]);
        exit(0);
    }
}

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

    int p[2];
    pipe(p);

    if(fork() == 0){
      func(p);
    } else {
        close(p[0]);
        char num[1];
        for (int i = 2; i <= 35; i++){
            num[0] = i;
            write(p[1], num, 1);
        }
        close(p[1]);
        wait(0);
    }
    exit(0);
}

2.4、find

find函数的实现需要先去学习一下ls函数的实现,主要是学习如何读取目录的,读懂了之后难度不是很大,需要注意一些细节。

    // 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. 


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

    // put the dir_name into the buf
    // buf : dir_name/
    strcpy(buf, dir);
    p = buf + strlen(buf);
    *p++ = '/';

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

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

    if(st.type != T_DIR){
        fprintf(2, "find: %s is not a dir\n", dir);
        close(fd);
        return;
    }
    
    // read the every file or dir in the `dir` sequentially
    while( read(fd, &de, sizeof(de)) == sizeof(de)){
        if(de.inum == 0) continue;

        char *name = de.name;
        if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue; // not consider the . and ..

        memmove(p, name, DIRSIZ);
        p[DIRSIZ] = 0;

        if(stat(buf, &st) < 0){
            printf("find: cannot stat %s\n", name);
            continue;
        }

        if(st.type == T_DIR){
            find(buf, file);
        } else if(strcmp(name, file) == 0) {
            printf("%s\n", buf);
        }
    }
    close(fd);
}

// find <dir_name> <file_name>
// find all the <file_name> in the <dir_name>
int
main(int argc, char* argv[]){
    if(argc < 3){
        fprintf(1, "the arguments is too few...\n");
        exit(1);
    }

    find(argv[1], argv[2]);
    exit(0);
}

2.5、xargs

首先,需要知道这个命令是干什么的。

Run COMMAND with arguments INITIAL-ARGS and more arguments read from input.

总的来说就是用来跑其他命令的,而参数从输入中读取

从实验手册上的要求和例子来看,实现的是一个只带-n参数且参数值只为1的xargs命令

这里的-n的意思是执行命令最大能够从输入读取的参数个数,如果-n 1就意味着只能从输入读取一个参数执行命令,所以就意味着会执行多次命令(需要把读取的参数用完)

$ echo hello too | xargs echo bye
bye hello too
$ echo "1\n2" | xargs -n 1 echo line
line 1
line 2

 理解了xargs的作用和实现的细节,就可以开始写代码了

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

int seq_num = MAXARG;  // the max_args per cmd


void exec1(char **argv){
    if(fork() == 0){
        exec(argv[0], argv);
    } else {
        wait(0);
    }
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        fprintf(2, "too few arguments...");
        exit(1);
    }

    char buf[512]; // store the input from the previous cmd
    int read_n = 0;
    int read_total = 0;

    // attention: if exits '\n', just a read can not read all
    while( (read_n = read(0, buf + read_total, 512)) > 0 ){
        read_total += read_n; // stat the number of input
    }
    int len = read_total;

    // switch all the '\n' to ' '
    for(int i = 0; i < len; i ++){
        if(buf[i] == '\n') buf[i] = ' ';
    }

    int cmd_ptr = 1; // the ptr to cmd
    bool is_set_n = false;

    // set the max args
    if(strcmp(argv[1], "-n") == 0){
        is_set_n = true;
        cmd_ptr = 3;
    }

    char* cmd_argv[MAXARG]; // the cmd argv
    // put the arg into the cmd_argv
    int idx = 0;
    for (int i = cmd_ptr; i < argc; i++) {
        char *arg = (char *)malloc(strlen(argv[i])+1); // attention: must use malloc, put the data in heap
        // if use char arg[MAXARG] will cause the error
        strcpy(arg, argv[i]);
        cmd_argv[idx] = arg;
        idx ++;
    }

    if(is_set_n){
        int index = 0;
        char arg[MAXARG];
        memset(arg, 0, MAXARG); // clear the space
        for(int i = 0; i < len; i++){
            if(buf[i] == ' ') { // can spilt out a arg

                arg[index++] = '\0';
                cmd_argv[idx++] = arg;

                // run
                exec1(cmd_argv);

                index = 0;
                memset(arg, 0, MAXARG); // clear the space

                idx --;
                continue;
            }
            arg[index ++] = buf[i];
        }
    } else {
        int prev = 0;
        // should spilt the args with ' '
        // and put them into cmd_argv sequentially
        for(int i = 0; i < len; i ++){
            if(buf[i] == ' '){
                char* add_arg = (char *)malloc(i - prev);
                memcpy(add_arg, buf+prev, i - prev);
                prev = i+1; // attention
                cmd_argv[idx++] = add_arg;
            }
        }
        exec1(cmd_argv);
    }

    exit(0);
}

记录一个小问题

发生在将argv里的参数拷贝到cmd_argv的过程中

for(int i = 0; i < len; i++){
 char agr[32]; // 每次分配到的地址都是一样的!!!
 cmd_argv[idx ++] = strcpy(arg, argv[i]);
}

数组空间是分配在栈中的,而且一次迭代之后空间失效,再次分配时就会分配相同的地址空间,导致cmd_argv中的指针值都是一样的,然后strcpy就是在不断地改变那块地址中的内容。

### 回答1:xv6是一个基于Unix的操作系统,它是一个教学用途的操作系统,旨在教授操作系统的基本概念和实现。它是在MIT的x86架构上开发的,包括了Unix的一些基本功能,如进程管理、文件系统、内存管理等。xv6的源代码是公开的,可以用于学习和研究。 Unix utilitiesUnix操作系统中的一些基本工具,如ls、cd、cp、mv、rm等。这些工具可以帮助用户管理文件和目录,执行各种操作。这些工具的实现是基于Unix的系统调用,可以通过编写C程序来调用这些系统调用实现相应的功能。这些工具是Unix操作系统的基础,也是其他操作系统的参考。 ### 回答2: lab: xv6 and unix utilities 实验是一项旨在帮助学生深入理解操作系统和 Unix 工具使用的实验。该实验分为两个部分,第一部分教授学生如何构建和运行 xv6 操作系统;第二部分则重点教授 Unix 工具的使用。 在 xv6 操作系统部分,学生将学习到操作系统内核的基本结构和实现原理。实验将引导学生理解内存管理、进程调度、系统调用等关键操作系统概念。此外,学生还将学习如何编写简单的 shell 以及如何通过修改 xv6 内核代码来实现新的系统调用和功能。 在 Unix 工具部分,学生将探索 Unix 系统中广泛使用的常见工具。这些工具包括 vi 编辑器、grep、awk、sed 等。实验将介绍这些工具的基本使用方法以及它们在处理文本和数据时的实际应用。这部分实验还将让学生深入了解 shell 和 shell 脚本的编写,帮助他们在 Unix 环境中轻松地编写脚本和自动化任务。 lab: xv6 and unix utilities 实验对计算机科学专业的学生具有重要意义。通过完成这个实验,学生将建立起对操作系统和 Unix 工具的深入理解,为他们成为一名优秀的软件工程师奠定坚实的基础。同时,这个实验还将为学生提供实践经验,让他们能够将所学知识应用到真实的软件开发和运维中。 ### 回答3: Lab: xv6 and Unix Utilities是一个计算机科学领域的实验,旨在让学生深入了解Unix操作系统以及操作系统本身的自我管理机制。在这个实验中,学生需要从零开始构建一个类似于Unix的操作系统,在这个操作系统中,学生需要设计一些基本命令,例如ls,cat,grep等等,并且将它们与系统的底层API结合起来,以实现各种功能。此外,学生还需要了解和探索xv6这个开发工具,它是一个轻量级基于Unix的操作系统实现,具有一定的可移植性和简洁性,因此,它可以作为一个基础框架来实现一个完整的Unix操作系统。 这个实验的目标是让学生了解Unix的基本命令结构和API,以及操作系统内部的一些基本机制,例如进程管理,文件系统交互以及进程通信等等。此外,通过实现这些命令,学生还可以学到一些基本的C语言编程技能,例如文件操作,字符串处理以及进程管理等等。还可以学习到如何使用Git等版本控制工具,以及如何进行调试和测试代码的技巧。 在整个实验过程中,学生需要有较强的自我管理能力和综合运用能力,因为在实现这些命令的同时,他们还需要和其他团队成员进行交流和合作,以及不断改进和完善他们的代码。总之,这个实验是一个非常有趣且富有挑战性的计算机科学课程,通过完成这个实验,学生可以更好地了解操作系统的构造和运作机制,以及如何设计和开发高效的系统级应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值