MIT6.S081 - Lab1: Xv6 and Unix utilities

这篇具有很好参考价值的文章主要介绍了MIT6.S081 - Lab1: Xv6 and Unix utilities。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Part1:sleep

实验要求与提示

  1. 可以参考 user/echo.c, user/grep.cuser/rm.c 文件
  2. 如果用户忘记传递参数,sleep 应该打印一条错误消息
  3. 命令行参数传递时为字符串,可以使用 atoi 函数将字符串转为数字
  4. 使用系统调用 sleep,有关实现 sleep 系统调用的内核代码参考 kernel/sysproc.c(查找 sys_sleep),关于可以从用户程序调用的 sleep 的 C 定义,参阅 user/user.h,以及 user/usys.S 表示从用户跳转到内核休眠的汇编代码
  5. 确保 main 调用 exit() 以退出程序
  6. Makefile 中将 sleep 程序条件到 UPROGS 中,这样可以使得 make qemu 能够编译程序,并在 xv6 shell 中运行

遇到的问题

问题一

  • 问题:运行 ./grade-lab-util sleep 显示错误 /usr/bin/env: ‘python’: No such file or directory ,可能是没装 python2 或者装的是 python3
  • 解决:grade-lab-util 文件第一行的 !/usr/bin/env python 改为 !/usr/bin/env python3

问题二

  • 问题:make qemu 后无法退出
  • 解决:输入 ctrl+a 后抬起按键,然后再输入 x

最终代码

#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 has not input parameters!\n");  //将错误信息写入到标准错误2
        exit(1);  // 非正常运行导致退出程序
    }
    sleep(atoi(argv[1]));   // 使用sleep系统调用,使用atoi将输入的字符串转为数字
    exit(0);    // 正常退出,注意这里没用return
}

注意要将 sleep 添加到 Makefile 的 UPROGS 中

  • 可以使用 ./grade-lab-util sleep 来进行打分,使用 make grade 可以给整个实验打分

实验思考

  1. 实现 sleep 比较容易,但是要掌握 sleepexitatoi 等的使用
  2. exit 和 return 的不同点:
  • exit(0):正常运行程序并退出程序

  • exit(1):非正常运行导致退出程序

  • return():返回函数,若在主函数中,则会退出函数并返回一值

    • return 返回函数值,是关键字;exit 是一个函数
    • return 是语言级别的,由 C 语言提供,它表示了调用堆栈的返回;而 exit 是系统调用级别的,是由操作系统提供的(或者函数库中给出的),它表示了一个进程的结束
    • return函数的退出(返回);exit进程的退出
    • return 用于结束一个函数的执行,将函数的执行信息传出个其他调用函数使用;exit 函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给 OS,这个状态标识了应用程序的一些运行信息,这个信息和操作系统有关,一般是 0 为正常退出,非 0 为非正常退出
    • 非主函数中调用 returnexit 效果很明显,但是在 main 函数中调用 returnexit 的现象就很模糊,多数情况下现象都是一致的

Part2:pingpong

实验要求与提示

  1. 调用一对管道(每个方向一个管道)在两个进程间"ping-pong"传递一个字节。父进程向子进程发送一个字节,子进程输出 <pid>: received ping,其中 <pid> 是它的进程 ID,然后子进程将字节写入管道,随后退出,父进程从子进程读取字节,打印 <pid>: received pong,随后退出
  2. 使用 pipe 创建一个管道;使用 fork 创建子进程;使用 read 从管道中读数据,使用 write 将数据写入到管道;使用 getpid 查找进程的 ID
  3. xv6 上的用户程序中可供使用的库函数可以在 user/user.h 中查看,它们的源代码(除了用于系统调用)在 user/ulib.cuser/printf.cuser/umalloc.c

遇到的问题

问题一

  • 问题:VScode 中怎么调试用户程序

  • 配置:首先应该将 launch.json 中的 "stopAtEntry": 改为 true

  • 调试步骤:

    1. 点调试按键开启调试,此时会停在 kernerl/main.c 的入口处
    2. 在调试控制台输入 -exec file ./user/_filenamefilename 为需要调试的文件名称
    3. 在终端输入 filename ,点击继续开始调试的按键,然后就可以进入文件调试了
    4. 如果需要对该文件进行多次调试,直接在终端重新输入 filename 就行

    注意:第一次调试某文件时,不要先设置断点,有的地方设置断点可能会导致进入不了该文件,等第一次调试之后再将断点打在能变为红色的地方文章来源地址https://www.toymoban.com/news/detail-853485.html

问题二

  • 问题:python 用多了,C 语言中关于字符串、指针的用法就有点模糊了,程序错误都是因为这里
  • 解决:韦东山有个视频是关于指针的,然后再找一个数组的视频或文档看一看

最终代码

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

int main(int argc, char *argv[]){
    int p1[2];
    int p2[2];
    int pid;
    char recv1[64];
    char recv2[64];
    pipe(p1);
    pipe(p2);
    pid = fork();
    if(pid == 0){     // 子进程
        close(p1[1]);   // 关闭写通道
        read(p1[0], recv1, sizeof("ping"));  // 等待父进程将数据写入通道
        printf("%d: received %s\n", getpid(), recv1);
        close(p1[0]);
        close(p2[0]);
        write(p2[1], "pong", sizeof("pong"));
        close(p2[1]);
        exit(0);
    }else{            // 父进程
        close(p1[0]);   // 关闭写通道
        write(p1[1], "ping", sizeof("ping"));  // 写入通道
        close(p1[1]);
        close(p2[1]);
        read(p2[0], recv2, sizeof("pong"));
        printf("%d: received %s\n", getpid(), recv2);
        close(p2[1]);   
    }
    exit(0);
}

实验思考

  1. 关于通道读写的过程一定要知道在什么情况下会发生什么,在不使用读端或者写端的时候一定要关闭,不然可能会造成自己被自己阻塞的现象
  2. 这个实验实现起来比较简单,但是能深挖的逻辑关系有很多,之后需要再进行复习,理清之间的关系

Part3: primes

实验要求与提示

  1. 使用 pipe 和 fork 来设置管道,首先将数字 2 到 35 输入管道。对于每个素数将安排创建一个进程,该进程通过一个管道从其左侧邻居读取数据,并通过另一个管道向其右侧邻居写入数据。由于 xv6 的文件描述符和进程数量有限,第一个进程可以在 35 时停止
  2. 要小心关闭进程不需要的文件描述符,否则程序将在第一个进程达到 35 之前耗尽 xv6 的资源
  3. 一旦第一个进程达到 35,它应该等到整个管道终止,包括所有的子进程、孙子进程等等。因此,主质数进程应该只在所有输出都打印出来之后退出,并且在所有其他质数进程都退出之后退出
  4. 当管道的写端关闭时,read 返回零
  5. 最简单的方法是直接将 32 位(4 字节)整数写入管道,而不是使用格式化的 ASCII I/O
  6. 仅在需要时在管道中创建进程

最终代码

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

#define WRITE 1
#define READ 0
void primeprocess(int p[]){
    int first_num;
    close(p[WRITE]);
    if(read(p[READ], &first_num, sizeof(first_num)) == 0){  // 递归终止条件,读不到数据
        close(p[READ]);
        exit(0);   
    }
    printf("prime %d\n", first_num);  // 第一个进入管道的肯定是素数
    int p_child[2];
    pipe(p_child);                  // 创建下一个pipe
    int pid = fork();
    if(pid == 0){                   // 子进程
        primeprocess(p_child);      // 递归函数
    }else{                          // 父进程
        int num; 
        close(p_child[READ]);      
        while(read(p[READ], &num, sizeof(num)) != 0){
            if(num % first_num != 0){
                write(p_child[WRITE], &num, sizeof(num));
            }
        }
        close(p[READ]);
        close(p_child[WRITE]);
        wait(0);   // 需要等待子进程退出才能退出
    }
    exit(0);   // 子进程结束
}
int main(int argc, char *argv[]){
    int p[2];
    pipe(p);
    int pid = fork();
    if(pid == 0){    // 子进程
        primeprocess(p);
    }else{
        close(p[READ]);
        for(int i = 2; i < 36; i++){
            write(p[WRITE], &i, sizeof(i));  // 注意这里是将i的地址给write函数
        }
        close(p[WRITE]);
        wait(0);
    }
    exit(0);
}

实验思考

  1. 这道题关键在于理解问题所表达的意思,用递归的方法主要是因为父进程需等待子进程退出,不过递归的思路比较简单
  2. 注意 write(p[WRITE], &i, sizeof(i)) 中是传递的 i 的地址

Part4: find

实验要求与提示

  1. 查看 user/ls.c 了解如何读取目录
  2. 使用递归查找子目录,但除去"."和".."
  3. 对文件系统的更改在 qemu 运行期间持续存在;要获得一个干净的文件系统,请运行 make clean,然后运行 qemu
  4. 需要使用 C 字符串,注意比较字符串不能像 python 一样直接 ==,而是应该用 strcmp()

遇到的问题

问题一

  • 问题:不太熟悉 find 函数的使用,不知道它后面都带能带哪些函数
  • 解决:这个实验仅仅是实现了 find 函数的部分功能,它的语法为 find [路径] [匹配条件] [动作] ,之后可以再尝试实现它里面更多的功能

最终代码

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

char* fmtname(char *path)
{
  char *p;
  // 查找末尾斜杠后的第一个字符
  for(p=path+strlen(path); p >= path && *p != '/'; p--);
  p++;
  return p;
}

void find(char *path, char *target)
{
  char buf[512], *p;
  int fd;
  struct dirent de;  // 记录文件前缀
  struct stat st;    // inode

  if((fd = open(path, O_RDONLY)) < 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), target) == 0)
        printf("%s\n", path);
      break;

    case T_DIR:    // 目录
      if(strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)){
        printf("ls: path too long\n");
        break;
      }
      strcpy(buf, path);    // 复制path到buf里
      p = buf+strlen(buf);  // 将p指向buf的末尾 
      *p++ = '/';           // 将buf的末尾添加/,从a/b变为a/b/
      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);  // 合并文件为a/b/de.name
        p[DIRSIZ] = 0;   // 结束字符串
        if(stat(buf, &st) < 0){
          printf("find: cannot stat %s\n", buf);
          continue;
        }
        find(buf, target);   // 递归,从开始路径一直往深处查找文件
      }
      break;
    }
  close(fd);
}

int main(int argc, char *argv[])
{
  if(argc != 3){
    printf("Usage: find <dirName> <fileName>\n");
    exit(1);
  }
  find(argv[1], argv[2]);
  exit(0);
}

实验思考

  1. read(fd, &de, sizeof(de)) 是读取文件的方法,其中 struct dirent de 用来记录文件前缀,它的结构体如下:
struct dirent {
  ushort inum;
  char name[DIRSIZ];
};
  1. 这道题在 user/ls.c 的基础上进行修改,但要注意在文件判断时,要排除 "."".." 的情况,它们不能进入递归

Part5: xargs

实验要求与提示

  1. 使用 forkexec 对每一行输入调用命令。在父进程中使用 wait 来等待子进程完成命令
  2. 要读取单独的输入行,每次读取一个字符,直到出现换行符 '\n'
  3. kernel/param.h 声明 MAXARG,如果需要声明 argv 数组,这可能很有用。

最终代码

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
#include "kernel/fcntl.h"
#include "kernel/param.h"
#define MAXBUF 1024
int main(int argc, char *argv[]){
    char *xargs_argv[MAXARG];   // 字符串数组
    char buf[MAXBUF];           // 字符数组
    int i;
    if(argc < 2){
        printf("Usage: xargs <command>\n");
        exit(1);
    }

    for(i = 0; i < argc; i++){
        xargs_argv[i - 1] = argv[i];  // argv里面为管道|后面的输入,字符串数组
    }
    while(1){
        int index = 0;   // buf写入字节顺序
        int buf_index = 0;  // buf遇到' '或'\n'的首地址
        int xargs_index = argc - 1;
        int re;    // read返回值
        char ch;   // 读到的一个字节
        while(1){
            re = read(0, &ch, sizeof(ch));   // 读取shell标准输入的一个字节
            if(re == 0){
                exit(0);      // 表示没有读到字节,结束程序(这里是程序正常结束的唯一出口)
            }
            if(ch == ' ' || ch == '\n'){
                buf[index++] = '\0';
                xargs_argv[xargs_index++] = &buf[buf_index];   //将buf当前的字符串传给xargs_argv
                buf_index = index;       // 更新buf当前命令首地址
                if(ch == '\n')break;     // 跳出循环,执行一行命令
            }else{
                buf[index++] = ch;
            }
        }
        xargs_argv[xargs_index] = (char *)0;   // 结束一行命令
        int pid = fork();
        if(pid == 0){    // 子程序
            exec(xargs_argv[0], xargs_argv);
        }else{
            wait((int *) 0);  //等待子程序执行完毕
        }
    }
    exit(0);   
}

实验思考

  1. 这道题主要是要理解 xargs 的用法以及灵活使用指针和数组,其中字符串数组和字符数组的用法要区分清楚
  2. 可以用 '\0' 来标记字符串的结束
  3. argv 的字符串只包括了管道最后一个输入,这里是整个代码的关键

到了这里,关于MIT6.S081 - Lab1: Xv6 and Unix utilities的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MIT6.S081 - Lab2: system calls

    step1:系统调用声明 user/user.h :系统调用函数(如 int fork(void) ) step2:ecall 进入内核态 user/usys.S (该文件由 user/usys.pl 生成,后续添加函数可以在这里添加):执行如下命令 将系统调用的编号(在 kernel/syscall.h 中定义)写入 a7 寄存器 从 ecall 进入中断处理函数 step3:保存数据并

    2024年04月23日
    浏览(34)
  • MIT 6.S081 Operating System/Fall 2020 macOS搭建risc-v与xv6开发调试环境

    电脑型号:Apple M2 Pro 2023 操作系统:macOS Ventura 13.4 所以我的电脑是arm64架构的M2芯片 执行安装脚本 /bin/zsh -c \\\"$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)\\\" 镜像选哪个都无所谓,我选择的是阿里巴巴 查看安装是否成功 brew --version 执行brew的安装脚本 这步需要先安装

    2024年02月08日
    浏览(42)
  • 6.s081/6.1810(Fall 2022)Lab5: Copy-on-Write Fork for xv6

    本来往年这里还有个Lazy Allocation的,今年不知道为啥直接给跳过去了。. 环境搭建 Lab1: Utilities Lab2: System calls Lab3: Page tables Lab4: Traps Lab5: Copy-on-Write Fork for xv6 官网链接 xv6手册链接,这个挺重要的,建议做lab之前最好读一读。 xv6手册中文版,这是几位先辈们的辛勤奉献来的呀!

    2024年02月14日
    浏览(26)
  • MIT6.S081 - Lecture1: Introduction and Examples

    理解操作系统的设计和实现 通过 XV6 操作系统动手实验,可以扩展或改进操作系统 Abstraction: 对硬件进行抽象 Multiplex: 在多个应用程序之间共用硬件资源 Isolation: 隔离性,程序出现故障时,不同程序之间不能相互干扰 Sharing: 实现共享,如数据交互或协同完成任务 Securi

    2024年04月15日
    浏览(40)
  • MIT6.S081 - Lecture3: OS Organization and System Calls

    使用操作系统的主要原因是为了实现 CPU 多进程分时复用以及内存隔离 如果没有操作系统,应用程序会直接与硬件进行交互,这时应用程序会直接使用 CPU,比如假设只有一个 CPU 核,一个应用程序在这个 CPU 核上运行,但是同时其他程序也需要运行,因为没有操作系统来帮助

    2024年04月22日
    浏览(28)
  • MIT6.S081学习笔记--lec 1

    abstract H/W 抽象化硬件 multiplex 多路复用 isolation 隔离性 sharing 共享(进程通信,数据共享) security / access control 安全性/权限控制 performance 性能/内核开销 range of applications 多应用场景 操作系统应该提供的功能:1. 多进程支持 2. 进程间隔离 3. 受控制的进程间通信 xv6 :一种在本

    2024年02月16日
    浏览(29)
  • 1670_MIT 6.828 xv6中增加系统调用的实现与分析

             全部学习汇总: GreyZhang/g_unix: some basic learning about unix operating system. (github.com)          操作系统的任务调度切换,在xv6中其实是基于中断的方式来进行触发的。          在ttap处理的部分,调用了一个trap处理的C语言接口。          也就是上面的接口。

    2023年04月11日
    浏览(26)
  • mit 6.824 lab1分析

    略 map阶段每个worker应该把中间文件分成nReduce份,nReduce是reduce任务的数量 worker完成reduce任务后生成文件名 mr-out-X mr-out-X 文件每行应该是 \\\"%v %v\\\" 格式,参考 main/mrsequential.go worker处理完map任务,应该把生成的中间文件放到当前目录中,便于worker执行reduce任务时读取中间文件 当所

    2023年04月10日
    浏览(40)
  • mit6.828 - lab5笔记(上)

    unix的文件系统相关知识 unix将可用的磁盘空间划分为两种主要类型的区域: inode区域 和 数据区域 。 unix为每个文件分配一个inode,其中保存文件的 关键元数据 ,如文件的stat属性和指向文件数据块的指针。 数据区域中的空间会被分成大小相同的数据块(就像内存管理中的分

    2024年02月02日
    浏览(23)
  • MIT 6.S081 Lab Three

    本文为 MIT 6.S081 2020 操作系统 实验三解析。 MIT 6.S081课程前置基础参考: 基于RISC-V搭建操作系统系列 在本实验中,您将探索页表并对其进行修改,以简化将数据从用户空间复制到内核空间的函数。 开始编码之前,请阅读xv6手册的第3章和相关文件: * kernel/memlayout.h* ,它捕获了

    2024年02月09日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包