实验环境说明

关于pintos

Pintos操作系统可以直接运行在常规的采用80x86 CPU 的IBM兼容PC机上。但是为了实验和调试方便,本组实验以在Linux系统上通过Bochs仿真器运行Pintos为例介绍。

image-20221221162141149

Bochs是一个系统仿真器,能够仿真80x86 CPU及其外设,从而所有能够在80x86 CPU上运行的操作系统和软件都可以不加修改地在Bochs上运行。 Bochs主要用于操作系统开发,当模拟操作系统崩溃,它不崩溃主机操作系统,所以可以调试仿真操作系统。

QEMU(quick emulator)是一款通用且免费的可执行硬件虚拟化的(hardware virtualization)开源仿真器。其与Bochs,PearPC类似,但配合KVM拥有高速,跨平台的特性。

pintos代码结构

开源的操作系统Pintos系统源码都位于src目录下

image-20221221164118520

  • threads/:内核线程操作相关的源文件。线程相关的实验将主要在这一目录下开展。
  • userprog/:用户进程和系统调用相关的源文件。系统调用中的实验将主要在这一目录下开展。
  • vm/:该目录基本为空。虚拟内存相关的实验将主要在这一目录下开展。
  • filesys/:文件系统相关源文件。从第四章开始的实验基本都需要涉及这一目录,文件系统的实验将主要在这一目录下开展。
  • devices/:输入输出设备接口相关的源文件,主要包括键盘、时钟、磁盘等设备的接口。除了时钟原语实验外,本教程的其它实验都不需要修改该目录下的代码。
  • lib/:该目录实现了标准C库中的常用函数和数据类型定义。该目录中的源文件将可同时编译进Pintos内核代码和(需要在Pintos上运行的)用户程序代码中,这需要引用该目录中的头文件。对该目录中头文件的引用可通过在代码中增加指示字#include <..>的方式进行。
    • lib/kernel/:该目录包含的是仅供Pintos内核使用的C库部分,包括常用的数据类型,如位图(bitmap)、双向链表(doubly linked list)、散列表(hash table)等。
    • lib/user/:该目录包含的是仅供Pintos用户程序使用的C库部分,如封装系统调用的包裹函数等。
  • tests/:本实践项目所涉及实验的测试程序。这些测试程序用来检验你所完成的代码是否实现了所要求的内核设计功能。
  • examples/:示例用户程序,在使用前需要将这些程序编译,即在该文件夹下运行make命令,而后将编译后生成的文件拷贝到Bochs虚拟的磁盘上加载运行,具体加载方法在系统调用相关实验中给出了介绍。
  • misc/和utils/:在不同的开发环境下可能需要的一些辅助程序。其中一些调试相关的程序如backtrace能够方便实验的开发调试。

pintos编译与运行

在对thread文件夹下输入make后,产生新的子目录build

image-20221221164147347

  • Makefile:这是pintos/src/Makefile.build文件的一个拷贝。该文件包含了make命令运行所需要的(关于编译Pintos内核的)相关信息。make clean命令会重新创建该文件,因此,关于编译信息的永久性修改最好写入pintos/src/Makefile.build文件。
  • kernel.o:整个内核的目标文件,是所有内核源文件编译连接后形成的,包含调试信息,可以用GDB或backtrace来调试。
  • kernel.bin:内核的内存映像,是运行Pintos内核时加载到内存中的内容。
  • loader.bin:内核boot loader的内存映像。boot loader用于将操作系统内核从磁盘读入内存,并跳转到操作系内核代码上去执行。该文件长度为512字节,是PC BIOS要求的长度。
  • os.dsk:内核的磁盘映像,实际由loader.bin和kernel.bin组成。仿真器将该文件视为“虚拟磁盘”。
  • 其它子目录:包含目标文件(.o)和依赖文件(.d),都是编译器产生的。其中依赖文件告诉make命令当某些源文件或头文件被更改后,哪些源文件需要被重新编译。

pintos时钟原语的改进

任务描述

源代码devices/time.c中有一个timer_sleep()函数

image-20221222184534423

该函数的功能是让调用它的线程“睡眠”ticks个时钟中断,当这段时间过去后,将该线程将被放入就绪队列。此函数可用于实现实时功能,例如按照规定的时间间隔发送报文等。

但是采用的是“忙等”技术,即通过不停地循环调用thread_yield()并查询是否已经达到等待时间。

编程要求

采用阻塞唤醒机制重新设计time_sleep()函数

1、阻塞:自定义一个阻塞队列(如何定义,放到哪里),将因调用timer_sleep()阻塞的线程加入该队列中(如何加入),并将其设计为按照阻塞时间从大到小的有序队列(为何这样做,怎么做)

2、唤醒:在每个时钟中断到来时(什么时候来),检查自定义阻塞队列中的线程,将各线程的需阻塞时间减1,并将需阻塞时间为0的唤醒,加入就绪队列(怎么检测,如何唤醒)

测试说明

修改代码后,在src/threads目录下执行make check命令,以下的测试结果应为pass:alarm-single、alarm-multiple、alarm-simultaneous、alarm-zero、alarm-negative。

原函数分析

image-20221222184534423

timer_sleep()函数第一句int64_t start = timr_ticks();调用了timer_ticks()函数,返回自开机以来的时刻数。

image-20221223164229559

timer_sleep()函数第二句ASSERT (intr_get_level () == INTR_ON);使用ASSERT断言,判断intr_get_level()函数得到的当前中断状态是不是等于INTR_ON。

ASSERT断言如果表达式的值为假,assert()宏就会调用_assert函数在标准错误流中打印一条错误信息,并调用abort()(abort()函数的原型在stdlib.h头文件中)函数终止程序。