MIT 6.S081 Lab One

news2024/12/29 11:03:53

MIT 6.S081 Lab One

  • 引言
  • sleep(难度:Easy)
    • 解析
    • Lab代码实现
  • pingpong(难度:Easy)
    • Lab代码实习
  • 小结


引言

本文为 MIT 6.S081 2020 操作系统 实验一解析。

MIT 6.S081课程前置基础参考: 基于RISC-V搭建操作系统系列


sleep(难度:Easy)

任务:

  • 实现xv6的UNIX程序sleep:您的sleep应该暂停到用户指定的计时数。

一个滴答(tick)是由xv6内核定义的时间概念,即来自定时器芯片的两个中断之间的时间。您的解决方案应该在文件user/sleep.c中

Tips:

  • 在你开始编码之前,请阅读《book-riscv-rev1》的第一章
  • 看看其他的一些程序(如: /user/echo.c, /user/grep.c, /user/rm.c)查看如何获取传递给程序的命令行参数
  • 如果用户忘记传递参数,sleep应该打印一条错误信息
  • 命令行参数作为字符串传递; 您可以使用atoi将其转换为数字(详见/user/ulib.c
  • 使用系统调用sleep
  • 请参阅kernel/sysproc.c以获取实现sleep系统调用的xv6内核代码(查找sys_sleep),user/user.h提供了sleep的声明以便其他程序调用,用汇编程序编写的user/usys.S可以帮助sleep从用户区跳转到内核区。
  • 确保main函数调用exit()以退出程序。
  • 将你的sleep程序添加到Makefile中的UPROGS中;完成之后,make qemu将编译您的程序,并且您可以从xv6的shell运行它。
  • 看看Kernighan和Ritchie编著的《C程序设计语言》(第二版)来了解C语言。

运行效果:

  • 从xv6 shell运行程序:
$ make qemu
...
init: starting sh
$ sleep 10
(nothing happens for a little while)
$
  • 如果程序在如上所示运行时暂停,则解决方案是正确的。运行make grade看看你是否真的通过了睡眠测试。
  • 请注意,make grade运行所有测试,包括下面作业的测试。如果要对一项作业运行成绩测试,请键入(不要启动XV6,在外部终端下使用):
$ ./grade-lab-util sleep
  • 这将运行与sleep匹配的成绩测试。或者,您可以键入:
$ make GRADEFLAGS=sleep grade
  • 效果是一样的。

解析

  • /user/echo.c函数代码如下:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

//argc是命令行参数个数
int main(int argc, char *argv[]){
  int i;
  // 依次处理每个命令行参数
  for(i = 1; i < argc; i++){
    // 默认情况下,文件描述符0对应标注输入,文件描述符1对应标准输出
    //文件描述符2对应标准错误
    write(1, argv[i], strlen(argv[i]));
    //每输出一个参数,就拼接一个换行符,如果是最后一个参数了,那么拼接一个" "
    if(i + 1 < argc){
      write(1, " ", 1);
    } else {
      write(1, "\n", 1);
    }
  }
  exit(0);
}
  • 字符串转整数的atoi函数代码如下(/user/echo.c):
int atoi(const char *s){
  int n=0;
  while('0' <= *s && *s <= '9')
    //每次处理一个字符,n每次乘10进一位,然后*s-'0'计算出当前字符代表数字几
    n = n*10 + *s++ - '0';
  return n;
}
  • user/user.h中的sleep声明
int sleep(int);
  • user/usys.S中编写的关于sleep函数的汇编实现—通过ecall指令完成系统调用
.global sleep
sleep:
 li a7, SYS_sleep
 ecall
 ret
  • syscall.h头文件中,列举出了所有支持的系统调用号
// System call numbers
#define SYS_fork    1
#define SYS_exit    2
#define SYS_wait    3
#define SYS_pipe    4
#define SYS_read    5
#define SYS_kill    6
#define SYS_exec    7
#define SYS_fstat   8
#define SYS_chdir   9
#define SYS_dup    10
#define SYS_getpid 11
#define SYS_sbrk   12
#define SYS_sleep  13
#define SYS_uptime 14
#define SYS_open   15
#define SYS_write  16
#define SYS_mknod  17
#define SYS_unlink 18
#define SYS_link   19
#define SYS_mkdir  20
#define SYS_close  21
  • kernel/sysproc.c中的sys_sleep系统调用函数代码如下:
uint64
sys_sleep(void)
{
  int n;
  uint ticks0;
  //从当前任务上下文中获取a0寄存器的值
  //a0寄存器作为系统调用参数寄存器,存放sleep(int)中int参数值
  if(argint(0, &n) < 0)
    return -1;
  //加锁
  acquire(&tickslock);
  //时钟中断每发生一次,ticks数加一 -- 此处是获取当前ticks数
  //ticks0保存进入睡眠的ticks数
  ticks0 = ticks;
  //进入sleep状态
  //每次都唤醒时,检查自身的sleep time是否到期,到期就停止sleep 
  while(ticks - ticks0 < n){
    //如果进程被杀掉了,直接释放锁,然后返回-1
    if(myproc()->killed){
      release(&tickslock);
      return -1;
    }
    //睡眠
    sleep(&ticks, &tickslock);
  }
  //释放锁
  release(&tickslock);
  return 0;
}
  • kernel/proc.c中的sleep函数代码如下:
// Atomically release lock and sleep on chan.
// Reacquires lock when awakened.
void
sleep(void *chan, struct spinlock *lk)
{
  //获取当前任务上下文 
  struct proc *p = myproc();
  
  // Must acquire p->lock in order to
  // change p->state and then call sched.
  // Once we hold p->lock, we can be
  // guaranteed that we won't miss any wakeup
  // (wakeup locks p->lock),
  // so it's okay to release lk.
  //获取当前任务锁
  //释放tickslock锁
  if(lk != &p->lock){  //DOC: sleeplock0
    acquire(&p->lock);  //DOC: sleeplock1
    release(lk);
  }

  // Go to sleep.
  //任务状态设置为SLEEPING状态,并且当前线程睡眠在ticks计数器上
  p->chan = chan;
  p->state = SLEEPING;
  //执行任务调度
  sched();
  
  // Tidy up.
  p->chan = 0;

  // Reacquire original lock.
  //释放任务锁,获取tickslock锁
  if(lk != &p->lock){
    release(&p->lock);
    acquire(lk);
  }
}

获取当前任务的lock,是为了改变当前任务状态时的并发安全性

  • kernel/trap.c中的clockintr函数会在发生时钟中断时被调用
void
clockintr()
{
  //获取tickslock
  acquire(&tickslock);
  //记录当前时钟中断发生次数
  ticks++;
  //唤醒所有睡眠在ticks计数器上的任务
  wakeup(&ticks);
  //释放锁
  release(&tickslock);
}
  • kernel/proc.c中的wakeup函数代码如下:
// Wake up all processes sleeping on chan.
// Must be called without any p->lock.
void
wakeup(void *chan)
{
  //遍历任务列表
  struct proc *p;
  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    //唤醒所有睡眠在ticks计数器上的任务
    if(p->state == SLEEPING && p->chan == chan) {
      //设置对应任务状态为RUNNING,该任务会在之后的任务调度过程中被调度执行
      //然后执行时,检查自身sleep time是否到期,如果没有到期,则继续sleep
      //然后再次唤醒,再次检查,循环往复...
      p->state = RUNNABLE;
    }
    release(&p->lock);
  }
}

xv6中的sleep函数本质就是软件定时器的实现,但是其思路并不是在每次时钟中断发生时,唤醒所有到期的定时任务,而是直接唤醒所有睡眠的任务,让其自身去检查是否睡够了,如果没睡够,那么就继续接着睡。

这种实现方式的坏处就是定时任务的定时属性不够精准,而且唤醒了还未睡够的任务,造成资源浪费。

  • 在kernel/start.c的timerinit定时器中断初始化方法中我们可以看到,时钟中断的触发间隔大约为1毫秒,也就是说ticks大约是每毫秒累加一次,即: 我们sleep函数的参数单位也是毫秒
    在这里插入图片描述

Lab代码实现

经过上面的分析后,我们已经知道了sleep函数背后的原理,下面开始编写本lab的代码:

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

int main(int argc, char const *argv[])
{
  //参数错误--第一个参数默认为当前程序名
  if (argc != 2) { 
    fprintf(2, "usage: sleep <time>\n");
    exit(1);
  }
  printf("sleep time=%s\n",argv[1]);
  int ticks = atoi(argv[1]);
  sleep(ticks);
  printf("(nothing happens for a little while)\n");
  exit(0);
}

在这里插入图片描述
执行测试:

  • make clean
  • make qemu
    在这里插入图片描述

pingpong(难度:Easy)

任务:

  • 编写一个使用UNIX系统调用的程序来在两个进程之间“ping-pong”一个字节,请使用两个管道,每个方向一个。
  • 父进程应该向子进程发送一个字节;
  • 子进程应该打印“<pid>: received ping”,其中<pid>是进程ID,并在管道中写入字节发送给父进程,然后退出;
  • 父级应该读取从子进程而来的字节,打印“<pid>: received pong”,然后退出。
  • 您的解决方案应该在文件user/pingpong.c中。

提示:

  • 使用pipe来创造管道
  • 使用fork创建子进程
  • 使用read从管道中读取数据,并且使用write向管道中写入数据
  • 使用getpid获取调用进程的pid
  • 将程序加入到Makefile的UPROGS
  • xv6上的用户程序有一组有限的可用库函数。您可以在user/user.h中看到可调用的程序列表;源代码(系统调用除外)位于user/ulib.c、user/printf.c和user/umalloc.c中。

运行程序应得到下面的输出:

$ make qemu
...
init: starting sh
$ pingpong
4: received ping
3: received pong
$

如果您的程序在两个进程之间交换一个字节并产生如上所示的输出,那么您的解决方案是正确的。


Lab代码实习

使用两个管道进行父子进程通信,需要注意的是如果管道的写端没有close,那么管道中数据为空时对管道的读取将会阻塞。因此对于不需要的管道描述符,要尽可能早的关闭。

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

#define RD 0 //pipe的read端
#define WR 1 //pipe的write端

int main(int argc, char const *argv[]) {
    char buf = 'P'; //用于传送的字节

    int fd_c2p[2]; //子进程->父进程
    int fd_p2c[2]; //父进程->子进程
    pipe(fd_c2p);
    pipe(fd_p2c);

    int pid = fork();
    int exit_status = 0;
   
    if (pid < 0) {
        fprintf(2, "fork() error!\n");
        close(fd_c2p[RD]);
        close(fd_c2p[WR]);
        close(fd_p2c[RD]);
        close(fd_p2c[WR]);
        exit(1);
    } else if (pid == 0) { //子进程
        close(fd_p2c[WR]);
        close(fd_c2p[RD]);
        
        if (read(fd_p2c[RD], &buf, sizeof(char)) != sizeof(char)) {
            fprintf(2, "child read() error!\n");
            exit_status = 1; //标记出错
        } else {
            fprintf(1, "%d: received ping\n", getpid());
        }

        if (write(fd_c2p[WR], &buf, sizeof(char)) != sizeof(char)) {
            fprintf(2, "child write() error!\n");
            exit_status = 1;
        }

        close(fd_p2c[RD]);
        close(fd_c2p[WR]);

        exit(exit_status);
    } else { //父进程
        close(fd_p2c[RD]);
        close(fd_c2p[WR]);

        if (write(fd_p2c[WR], &buf, sizeof(char)) != sizeof(char)) {
            fprintf(2, "parent write() error!\n");
            exit_status = 1;
        }

        if (read(fd_c2p[RD], &buf, sizeof(char)) != sizeof(char)) {
            fprintf(2, "parent read() error!\n");
            exit_status = 1; //标记出错
        } else {
            fprintf(1, "%d: received pong\n", getpid());
        }

        close(fd_p2c[WR]);
        close(fd_c2p[RD]);

        exit(exit_status);
    }
}

在这里插入图片描述

  • 测试

在这里插入图片描述


小结

实验一后续还有一些实验内容,留作后续慢慢补充

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/630478.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

English Learning - L3 作业打卡 Lesson5 Day34 2023.6.7 周三

English Learning - L3 作业打卡 Lesson5 Day34 2023.6.7 周三 引言&#x1f349;句1: The woman reading the romantic novel could be a lawyer.成分划分弱读连读爆破语调 &#x1f349;句2: She just wants a light read to take her mind off work.成分划分弱读连读爆破语调…

STM32读取MQ2烟雾浓度数据判断烟雾是否超标

【1】MQ2传感器是什么&#xff1f; MQ2传感器是一种可探测多种气体的传感器&#xff0c;常用于监测烟雾、液化气、丙酮、乙醇、甲醛、天然气等有害气体。MQ2传感器基于半导体敏感元件&#xff0c;通过检测气体中有害物质的浓度变化来实现气体检测。 MQ2传感器具有以下特点&a…

Python的self作用,以及__init__,__new__

本章来探讨一下Python类的self作用&#xff0c;以及__init__,__new__。 为什么是探讨&#xff0c;不是学习&#xff0c;因为菜&#x1f40e;&#xff1b; 先看个例子&#xff1a; class Example:def animal(self):self.dog "大黄"def Dog(self):print(self.dog)if _…

【前端 - CSS】第 10 课 - CSS 引入方式

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、CSS 引入方式 2.1、内部样式表 2.2、外部样式表 2.3、行内样式 3、总结 1、缘起 要想在 HTML 代码中写入 CSS 代…

English Learning - L3 作业打卡 Lesson5 Day33 2023.6.6 周二

English Learning - L3 作业打卡 Lesson5 Day33 2023.6.6 周二 引言&#x1f349;句1: And theres a student reading an English textbook.成分划分弱读连读语调 &#x1f349;句2: What do their choices say about them?成分划分连读爆破语调 &#x1f349;句3: Do you jud…

nodejs+vue+Express论坛网站34t91

本论坛网站有管理员&#xff0c;用户&#xff0c;普通管理员。管理员功能有个人中心&#xff0c;用户管理&#xff0c;普通管理员管理&#xff0c;论坛类别管理&#xff0c;交流论坛管理&#xff0c;系统管理等。用户功能有个人中心&#xff0c;交流论坛管理&#xff0c;我的收…

华为OD机试真题 JavaScript 实现【水仙花数】【2022Q4 100分】

一、题目描述 所谓水仙花数&#xff0c;是指一个n位的正整数&#xff0c;其各位数字的n次方和等于该数本身。 例如153是水仙花数&#xff0c;153是一个3位数&#xff0c;并且153 1^3 5^3 3^3。 二、输入描述 第一行输入一个整数n&#xff0c;表示一个n位的正整数。n在3到…

单容水箱建模(自衡单容水箱+无自衡单容水箱)

自衡单容水箱Simulink建模和PLC源代码请参看下面文章链接: 单容双容水箱建模(simulink仿真+PLC代码)_RXXW_Dor的博客-CSDN博客PLC通过伯努利方程近似计算水箱流量详细内容请参看下面的文章博客PLC通过伯努利方程近似计算水箱流量(FC)_怎么用伯努利方程求某水位流量_RXXW_Dor的…

基础汇编语言编程

目录 什么是汇编语言&#xff1f; 工程搭建 新建工程 环境设置 测试是否成功 正式学习汇编语言 数据处理指令 填充&#xff0c;加&#xff0c;减&#xff0c;乘 思考&#xff1a;我们可以看到R0寄存器可以存放8位十六进制数&#xff0c;那么0x12345678能不能用mov存入&am…

java SSM 摄影作品网站myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM 摄影作品网站系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主要采…

会用Python做副业的人,有多豪横!

前两天一个朋友找到我吐槽&#xff0c;说工资一发交完房租水电&#xff0c;啥也不剩&#xff0c;搞不懂朋友圈里那些天天吃喝玩乐的同龄人钱都是哪来的&#xff1f; 确实如此&#xff0c;刚毕业的大学生工资起薪都很低&#xff0c;在高消费、高租金的城市&#xff0c;别说存钱…

浏览器跨域请求

跨域是浏览器的一种同源策略&#xff0c;所以该概念只存在于通过浏览器访问服务里。 如果缺少了同源策略&#xff0c;则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的&#xff0c;浏览器只是针对同源策略的一种实现 请求的url地址,必须与浏览器上的…

Vue3 ElementPlus Dialog封装 (二:使用provide inject)

引言 上一章Vue3 ElementPlus Dialog封装 &#xff08;一&#xff1a;使用props emit&#xff09; propemit实现的方法用于父子组件比较方便, 跨多层次组件比较麻烦 vue3 中 还提供了provide和inject方法供组件间通信(参考官网图片), 本篇使用该方法实现上章效果 实现原理 参考…

K8S之Ingress 对外暴露应用(十四)

• Ingress为弥补NodePort不足而生 • Pod与Ingress的关系 • Ingress Controller • Ingress 规则配置 一&#xff0c;Ingress为弥补NodePort不足而生 NodePort存在的不足&#xff1a; • 一个端口只能一个服务使用&#xff0c;端口需提前规划 • 只支持4层负载均衡二&#x…

九耶丨阁瑞钛伦特-大型计算机硬件组成(二)

TSO&#xff1a; TSO是Time Sharing Option的缩写&#xff0c;用户可以通过TSO命令和系统进行交互式工作。TSO命令直接使用并不方便&#xff0c;所以IBM又在TSO下开发了程序产品ISPF/PDF&#xff08;Interactive System Productivity/Program Development Facility)其中ISPF支…

VAPS XT4.2 与 VS2013 安装

VAPS XT4.2 与 VS2013 安装 安装顺序 安装 VS2013安装 License tool安装 VAPS XT4.2VS2013 的安装 安装 VS2013,安装内容全选,安装路径选择D盘;VS2013 激活,网上找密钥,比如:【BWG7X-J98B3-W34RT-33B3R-JVYW9】;电脑重启;Win+R,sysdm.cpl,打开环境变量,查看VS相关的…

Linux SSH命令实战教程,提升你的服务器管理基本功!

前言 大家好&#xff0c;又见面了&#xff0c;我是沐风晓月&#xff0c;本文是专栏【linux基本功-基础命令实战】的第62篇文章。 专栏地址&#xff1a;[linux基本功-基础命令专栏] &#xff0c; 此专栏是沐风晓月对Linux常用命令的汇总&#xff0c;希望能够加深自己的印象&am…

java生成、识别条形码和二维码

一、概述 使用 zxing 开源库 Zxing主要是Google出品的&#xff0c;用于识别一维码和二维码的第三方库主要类:BitMatrix 位图矩阵MultiFormatWriter 位图编写器MatrixToImageWriter 写入图片 可以生成、识别条形码和二维码 内置三种尺寸&#xff1a;enum Size {SMALL, MIDDLE, …

【习题】习题 2 - 编写程序求当前机器的字节序

欢迎来到博主 Apeiron 的博客&#xff0c;祝您旅程愉快 &#xff01; 时止则止&#xff0c;时行则行。动静不失其时&#xff0c;其道光明。 目录 1、缘起 2、字节序影响 3、示例代码 4、总结 1、缘起 在计算机中&#xff0c;字节序&#xff08;Byte Order&#xff09;指定了…

一文带你彻底掌握Java 中的Stream流(详细)

本文目录 学习目标中间操作Filter(过滤)Map(转换)Sorted(排序)Distinct(去重)Limit(限制)Skip(跳过)Peek(展示) 终止操作forEach(循环)Collect(收集)Count(计数)Reduce(聚合)AnyMatch(任意匹配)AllMatch(全部匹配)NoneMatch(无匹配) 使用Stream流的优缺点&#xff1a;优点&…