当前位置: 代码迷 >> 综合 >> MIT 6.S081 Lab1: Xv6 and Unix utilities
  详细解决方案

MIT 6.S081 Lab1: Xv6 and Unix utilities

热度:49   发布时间:2023-12-15 17:05:29.0

写在前面

这周开始做 MIT 的 6.S801 操作系统实验,补一下本科时的基础,希望能一直做下去,二哥监督!

虽然2021年的实验已经出来了,但我还是选择做2020年的,实验地址在这里。主要考虑到自己太菜了,20年的网上有一些代码可供参考,有时候实在不会做硬看也不一定能做出来,能够读懂别人的也算收获,哈哈。

刚做完实验一,地址在 Lab: Xv6 and Unix utilities, 在这里做一下记录。

实验内容

实验环境(Boot xv6)

这次实验主要使用 MIT 提供的代码,将 xv6 系统在 qemu 上跑起来。git checkout util 是将分支切换到 util 上。

我在自己电脑上刷了一个 Ubuntu 20.04 的系统,然后按照实验指导书的过程一点点敲命令,就好了。中间需要安装一些必备的软件包和一些依赖,提示缺啥就安装啥就成,环境没啥大问题,不需要折腾太久。感觉折腾装环境啥的没什么意义,不如快点开始实验。

在安装好环境后就可以开始实验了。这部分的实验主要是写五个系统调用的命令,分别是 sleep, pingpong, primes, findxargs。建议先看一下xv6 book的第一章,主要看一下 1.1-1.3

任务一(sleep)

这是最简单的一个任务,直接调用提供的 sleep 方法即可,纯属为了好上手。头文件参考其他的 user 目录下的 .c 文件即可。

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

在写完 .c 代码后,需要在 Makefile 文件中的 UPROGS 字段中添加 $U/_[xxx]\,然后才能使用 ./grade-lab-util 进行测试。

任务二(pingpong)

使用 pipe()fork() 实现父进程发送一个字符,子进程成功接收该字符后打印 received ping,再向父进程发送一个字符,父进程成功接收后打印 received pong

  • 文件描述符

这里需要懂得什么是文件描述符。在 Linux 操作系统中,一切皆文件,内核通过文件描述符访问文件。每个进程都会默认打开3个文件描述符,即0、1、2。其中0代表标准输入流(stdin)、1代表标准输出流(stdout)、2代表标准错误流(stderr)。可以使用 >>> 的方式重定向。

  • 管道

管道是操作系统进程间通信的一种方式,可以使用 pipe() 函数进行创建。有在管道创建后,就有两个文件描述符,一个是负责读,一个是负责写。两个描述符对应内核中的同一块内存,管道的主要特点:

  1. 数据在管道中以字节流的形式传输,由于管道是半双工的,管道中的数据只能单向流动。

  2. 管道只能用于具有亲缘关系的进程通信,父子进程或者兄弟进程。

  3. fork() 后,父进程和子进程拥有相同的管道描述符,通常为了安全起见,我们关闭一个进程的读描述符和另一个进程的写描述符,这样就可以让数据更安全地传输。

下面是代码实现:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"int main(int argc, char *argv[]) {
    int p1[2], p2[2];char buffer[] = {
    'l'};int len = sizeof(buffer);pipe(p1);pipe(p2);if (fork() == 0) {
    close(p1[1]);close(p2[0]);if (read(p1[0], buffer, len) != len) {
    printf("child read error!\n");exit(1);}printf("%d: received ping\n", getpid());if (write(p2[1], buffer, len) != len) {
    printf("child write error\n");exit(1);}exit(0);} else {
    close(p1[0]);close(p2[1]);if (write(p1[1], buffer, len) != len) {
    printf("parent write error!\n");exit(1);}if (read(p2[0], buffer, len) != len) {
    printf("parent read error!\n");exit(1);}printf("%d: received pong\n");exit(0);}exit(0);
}

注:这里需要使用两个管道,我尝试改成一个,并没有成功…不太懂如何使用一个管道,感觉还是使用两个管道更加安全些。

任务三(primes)

任务是输出区间内的质数 [2, 35],如果一般的方法,直接挨个遍历计算就行,但由于这里需要充分使用 pipefork 方法,因此需要一些不一样的方法。

下面使用的方法是埃拉托斯特尼素数筛,简称筛法。简单地说就是,每次得到一个素数时,在所有小于 n 的数中,删除它的倍数,然后不断迭代,剩下的就全是素数了。

实现思想是,只要还没获取到所有的素数,便不断遍历/递归。每次递归时:

  1. 先在父进程中创建一个子进程。
  2. 利用子进程将剩下的所有数全都写到管道中。
  3. 在父进程中,将数不断读出来,管道中第一个数一定是素数,然后删除它的倍数(如果不是它的倍数,就继续更新数组,同时记录数组中目前已有的数字数量)。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"void printPrime(int *input, int count)
{
    if (count == 0) {
    return;}int p[2], i = 0, prime = *input;pipe(p);char buff[4];printf("prime %d\n", prime);if (fork() == 0) {
    close(p[0]);for (; i < count; i++) {
    write(p[1], (char *)(input + i), 4);}close(p[1]);exit(0);} else {
    close(p[1]);count = 0;while (read(p[0], buff, 4) != 0) {
    int temp = *((int *)buff);if (temp % prime) {
    *input++ = temp;count++;}}printPrime(input - count, count);close(p[0]);wait(0);}
}int main(int argc, char *argv[]) {
    int input[34], i = 0;for (; i < 34; i++) {
    input[i] = i + 2;}printPrime(input, 34);exit(0);
}

无论是使用迭代的方式还是递归的方法,大致思想都是相同的。这道题我想了好久,。。

任务四(find)

在路径中查找特定文件名的文件。

基本直接复制 user/ls.c 文件内容。有几点需要注意的:

  1. 在获取文件名的函数(fmtname )中,需要设置 buf[strlen(p)] = 0; 否则比较函数 strcmp 会失效。
  2. memmove()memcpy() 的改进版,实现的功能相同,但更安全,具体分析见这篇博客,大致是说,一个是从头部开始复制,一个是从尾部开始复制。
  3. 结构体 statdirent 分别存储文件的信息和目录的信息。没有很理解,留着以后再看。
  4. 不太懂 de.inum 是干嘛的,有的代码还把它等于 1 的情况省略了,不懂为啥。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"char* fmtname(char *path) {
    static char buf[DIRSIZ + 1];char *p;for (p = path + strlen(path); p >= path && *p != '/';p--);p++;if (strlen(p) >= DIRSIZ) return p;memmove(buf, p, strlen(p));memset(buf+strlen(p), ' ', DIRSIZ-strlen(p));buf[strlen(p)] = 0;return buf;
}void find(char *path, char *fileName) {
    char buf[512], *p;int fd;struct stat st;struct dirent de;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;}//printf("%s %s\n",path, fmtname(path));switch(st.type) {
    case T_FILE://printf("%s\n", fmtname(path));if (strcmp(fmtname(path), fileName) == 0) {
    printf("%s\n", path);}break;case T_DIR://printf("%s %s\n", path, fmtname(path));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 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) {
    continue;}memmove(p, de.name, DIRSIZ);p[DIRSIZ] = 0;find(buf, fileName);}break;}close(fd);
}int main(int argc, char *argv[]) {
    if (argc < 3) {
    printf("error\n");exit(1);}find(argv[1], argv[2]);exit(0);
}

任务五(xargs)

没咋用过这个命令,还是先查的它是啥功能。主要作用是读取标准输出中的信息,然后将其作为标准输入,拼接到下一个命令后面来执行。神奇!

注意的一点是,利用 fork() 让命令在子进程中执行后,函数并没有结束,还需要继续读取,因此一些变量需要继续设置新的值。

#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"int main(int argc, char *argv[]) {
    int i, count = 0, k, m = 0;char* lineSplit[MAXARG], *p;char block[32], buf[32];p = buf;for (i = 1; i < argc; i++) {
    lineSplit[count++] = argv[i];}while ((k = read(0, block, sizeof(block))) > 0) {
    for (i = 0; i < k; i++) {
    if (block[i] == '\n') {
    buf[m] = 0;lineSplit[count++] = p;lineSplit[count] = 0;m = 0;p = buf;count = argc - 1;if (fork() == 0) {
    exec(argv[1], lineSplit);}wait(0);} else if (block[i] == ' ') {
    buf[m++] = 0;lineSplit[count++] = p;p = &buf[m];} else {
    buf[m++] = block[i];}}}exit(0);
}

命令中的 | 表示管道(我一直以为这是与或非的或,。。惭愧)。

总结

这个实验算是熟悉一下 xv6 的环境和操作吧,确实感觉学到了很多东西,大赞!

代码地址在 github,文章同步在知乎。

参考文章

  1. Mit6.S081-实验1-Xv6 and Unix utilities
  2. 操作系统实验Mit6.S801笔记 Lab1: Util
  3. MIT-6.s081-OS lab util Unix utilities
  4. 【MIT-6.S081-2020】Lab1 Util
  相关解决方案