[MIT 6.S081] Lab1: Xv6 and Unix utilities

[MIT 6.S081] Lab1: Xv6 and Unix utilities

Boot xv6 (easy)

步骤

  1. 获取实验用的 xv6 源码并切换到 util 分支
$ git clone git://g.csail.mit.edu/xv6-labs-2020
$ cd xv6-labs-2020
$ git checkout util

[MIT 6.S081] Lab1: Xv6 and Unix utilities
2. 构建并运行 xv6

$ make qemu

[MIT 6.S081] Lab1: Xv6 and Unix utilities
3. 在 xv6 中使用命令测试
使用 ls 列出文件:

$ ls

[MIT 6.S081] Lab1: Xv6 and Unix utilities
使用 Ctrl+p 查看运行进程(xv6 无 ps 命令)
[MIT 6.S081] Lab1: Xv6 and Unix utilities
使用 Ctrl+a x 退出 QWMU.
[MIT 6.S081] Lab1: Xv6 and Unix utilities

  • 注: 先按 Ctrl+a, 再按 x键.

sleep (easy)

步骤

  1. 编写 sleep.c
    此处sleep考虑了输入参数不为数字的情况. 此外, 对于sleep后多个参数的情况也进行了考虑, 但是发现在 QEMU 中执行是失败的, 测试是通过的, 可以推测此处无需考虑多个参数的情况.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc, char *argv[]) {
	int ticks;
	// 需要1个参数
	if(argc != 2) {
		fprintf(2, "Usage: sleep number\n");
		exit(1);
	}
	ticks = atoi(argv[1]);
	// 判断
	if(ticks == 0 && strcmp(argv[1],"0") != 0){
		fprintf(2, "sleep: invalid time interval '%s'\n", argv[1]);
		exit(2);
	}
	sleep(ticks);
	exit(0);
}
  1. 修改 Makefile 文件中的 UPROGS 部分:
    [MIT 6.S081] Lab1: Xv6 and Unix utilities

测试

[MIT 6.S081] Lab1: Xv6 and Unix utilities

pingpong (easy)

步骤

  1. 编写代码
    该代码主要是管道 pipe 的应用, 注意管道是单工的, 因此父子进程相互通信需要创建两个管道.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main() {
    // fd1用于父进程向子进程通信, fd2反之
	int fd1[2], fd2[2];
	char buf[1];
	pipe(fd1);
	pipe(fd2);
	if(fork() == 0){
		close(fd1[1]);
		read(fd1[0],buf,1);
		close(fd1[0]);
		printf("%d: received ping\n",getpid());
		
		close(fd2[0]);
		write(fd2[1],buf,1);
		close(fd2[1]);
	} else {
		close(fd1[0]);
		write(fd1[1],"a",1);
		close(fd1[1]);
		
		close(fd2[1]);
		read(fd2[0],buf,1);
		close(fd2[0]);
		printf("%d: received pong\n", getpid());
	}
	exit(0);
}
  1. 修改 Makefile 文件中的 UPROGS 部分:
    [MIT 6.S081] Lab1: Xv6 and Unix utilities

测试

[MIT 6.S081] Lab1: Xv6 and Unix utilities
[MIT 6.S081] Lab1: Xv6 and Unix utilities

primes (M/hard)

步骤

  1. 编写代码
    [MIT 6.S081] Lab1: Xv6 and Unix utilities
    质数的求解方法本质上就是经典的"筛法", 此实验中通过多个进程来达到并行处理. 进程相当于上图的芯片, 而用于进程间通信的管道就模拟导线进行数字传递. 下面算法中, 个人使用了递归来达到构建迭代的子进程的目的.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int subProcess(int *oldFd) {
    // 关闭原管道写端
    close(oldFd[1]);
    int fd[2];
    int prime;
    int num;
    // 若能从原管道读到数据
    if (read(oldFd[0], &prime, 4)) {
        // 第一个数据为质数,进行输出
        printf("prime %d\n", prime);
        // 创建管道和子进程
        pipe(fd);
        if (fork() == 0) {    //子进程
            // 递归调用
            subProcess(fd);
        } else {    // 父进程
            // 关闭新管道读端
            close(fd[0]);
            // 从原管道进行读取
            while (read(oldFd[0], &num, 4)) {
                // 不能被记录的质数整除则写入新管道
                if (num % prime != 0) {
                    write(fd[1], &num, 4);
                }
            }
            // 此时父进程的原管道关闭, 则关闭原管道的读端
            close(oldFd[0]);
            // 关闭新管道的写端
            close(fd[1]);
            // 等待子进程结束
            wait((int *) 0);
        }
    } else {    // 此时说明原管道已关闭,第一个数字都读不出
        // 不创建子进程直接关闭原管道读端
        close(oldFd[0]);
    }
    exit(0);
}

int main() {
    int i;
    int fd[2];
    pipe(fd);
    if (fork() == 0) {
        subProcess(fd);
    } else {    // 父进程
        close(fd[0]);
        // 遍历 2~35 写入管道写端
        for (i = 2; i <= 35; ++i) {
            write(fd[1], &i, 4);
        }
        // 写完关闭管道写端并等待子进程结束
        close(fd[1]);
        wait((int *) 0);
    }
    exit(0);
}
  1. 修改 Makefile 文件中的 UPROGS 部分

测试

[MIT 6.S081] Lab1: Xv6 and Unix utilities
[MIT 6.S081] Lab1: Xv6 and Unix utilities

find (moderate)

步骤

  1. 编写代码
    代码思路主要参考 user/ls.c, 需要使用 struct direntstruct stat 两个和文档相关的结构体. 此处对于文件夹同样是利用递归来进行查找.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *dir, char *file) {
    char buf[512] = {0}, *p;
    int fd;
    struct dirent de;
    struct stat st;
    // 打开目录
    if ((fd = open(dir, 0)) < 0) {
        fprintf(2, "find: cannot open %s\n", dir);
        return;
    }
    // 判断路径长度
    if (strlen(dir) + 1 + DIRSIZ + 1 > sizeof(buf)) {
        fprintf(2, "find: path too long\n");
        close(fd);
        return;
    }

    strcpy(buf, dir);
    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;
        // 获取当前文档的状态
        if (stat(buf, &st) < 0) {
            fprintf(2, "find: cannot stat %s\n", buf);
            continue;
        }
        // 是目录则递归遍历
        if (st.type == T_DIR) {
            find(buf, file);
        } else if (strcmp(de.name, file) == 0) {    //是文件则进行比较, 若与查找一致则输出
            printf("%s\n", buf);
        }
    }
    close(fd);
    return;
}

int main(int argc, char *argv[]) {
    struct stat st;
    
    if (argc != 3) {
        fprintf(2, "Usage: find dir file\n");
        exit(1);
    }
    // 获取查找目录的状态并判断是否为目录
    if (stat(argv[1], &st) < 0) {
        fprintf(2, "find: cannot stat %s\n", argv[1]);
        exit(2);
    }
    if (st.type != T_DIR) {
        fprintf(2, "find: '%s' is not a directory\n", argv[1]);
        exit(3);
    }
    find(argv[1], argv[2]);
    exit(0);
}
  1. 修改 Makefile 文件中的 UPROGS 部分

测试

[MIT 6.S081] Lab1: Xv6 and Unix utilities
[MIT 6.S081] Lab1: Xv6 and Unix utilities

xargs (moderate)

步骤

  1. 编写代码
    该部分实验耗时较长, 重点之一在于 xargs 指令的用法, 详见 命令教程 - 阮一峰的网络日志, 其中读取输入是通过标准输入 stdin 进行的, 在代码上体现为 read() 函数.
    另一方面是参数的解析, 下面代码考虑了 xargs 后面无参数时默认以 echo 作为指令. 此外, 另一个比较困难的地方在于"空格",“回车”,以及"输入结束"三种情况的处理. 网上有些代码没有考虑输入结束, 也就是 read() 返回 0 的情况.
    具体说来, 本题个人主要想到有以下两个方法:
  • 第 1 种方法是对"空格",“回车”,以及"输入结束"三种情况都进行处理. 也就是在读取输入时会根据空格将参数进行划分.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXLEN 32

int main(int argc, char *argv[]) {
    // 执行命令
    char *path = "echo";
    // 存放参数的数组和指针
    char buf[MAXLEN * MAXARG] = {0},*p;
    // 参数的指针数组
    char *params[MAXARG];
    // xargs后命令所带的参数个数
    int oriParamCnt = 0;
    // 参数序号
    int paramIdx;
    // 参数长度
    int paramLen;
    int i;
    // 临时记录读取字符
    char ch;
    // read读取长度, 用于判断是否输入结束
    int res;

    // 参数数量大于1
    if (argc > 1) {
        // 提取指令
        path = argv[1];
        // 设置参数, 注意也需要带上指令的参数
        for (i = 1; i < argc; ++i) {
            params[oriParamCnt++] = argv[i];
        }
    } else {    //参数唯一,即只有xargs
        // 即指令为echo
        params[oriParamCnt++] = path;
    }
    
    // 后续参数起始序号
    paramIdx = oriParamCnt;
    p = buf;
    paramLen = 0;
    while (1) {
        res = read(0, p, 1);
        ch = *p;
        
        // 若读取的为一般字符
        if (res != 0 && ch != ' ' && ch != '\n') {
            ++paramLen;
            ++p;
            continue;
        }
        // 未读取成功, 或者读取的是空格或回车
        // 计算参数起始位置
        params[paramIdx++] = p - paramLen;
        // 参数长度置0
        paramLen = 0;
        // 设置字符结束符
        *p = 0;
        ++p;
        // 若读取的参数超过上限
        if (paramIdx == MAXARG && ch == ' ') {
            // 一直读到回车即下个命令为止
            while ((res = read(0, &ch, 1)) != 0) {
                if (ch == '\n') {
                    break;
                }
            }
        }
        // 若读取的不为空格, 即 res==0||ch=='\n'
        if (ch != ' ') {
            // 创建子进程执行命令
            if (fork() == 0) {
                exec(path, params);
                exit(0);
            } else {
                // 父进程等待子进程
                wait((int *) 0);
                // 重置参数序号和指针
                paramIdx = oriParamCnt;
                p = buf;
            }
        }
        // 若输入读取完毕则退出
        if (res == 0) {
            break;
        }
    }
    exit(0);
}
  • 第 2 种方法仅对"回车"以及"输入结束"两种种情况都进行处理. 也就是在读取输入时不根据空格将参数进行划分, 直接作为 1 个参数, 实际测试证明确实是可行的, 空格应该会被 exec() 执行的命令进行解析. 代码参考了 MIT 6.S081 2020 LAB1记录 - 知乎 中的 xargs 代码.
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"

#define MAXLEN 32

int main(int argc, char *argv[]) {
    char *path = "echo";
    char buf[MAXLEN * MAXARG] = {0}, *p;
    char *params[MAXARG];
    int paramIdx = 0;
    int i;
    int len;

    // 读取原有参数, 与上一种方法相同
    if (argc > 1) {
        if (argc + 1 > MAXARG) {
            fprintf(2, "xargs: too many ars\n");
            exit(1);
        }
        path = argv[1];
        for (i = 1; i < argc; ++i) {
            params[paramIdx++] = argv[i];
        }
    } else {
        params[paramIdx++] = path;
    }
    
    p = buf;
    while (1) {
        // 读取字符知道结束或遇到回车
        while (1) {
            len = read(0, p, 1);
            if (len == 0 || *p == '\n') {
                break;
            }
            ++p;
        }
        *p = 0;
        // 直接将其作为一个参数
        params[paramIdx] = buf;
        // 创建子进程执行
        if (fork() == 0) {
            exec(path, params);
            exit(0);
        } else {
            wait((int *) 0);
            p=buf;
        }
        if (len == 0) {
            break;
        }
    }
    exit(0);
}
  1. 修改 Makefile 文件中的 UPROGS 部分

测试

[MIT 6.S081] Lab1: Xv6 and Unix utilities
[MIT 6.S081] Lab1: Xv6 and Unix utilities

上一篇:股票自选股基本函数大全-7


下一篇:python连接数据库