山东大学软件学院操作系统课程设计(2021秋季,nachos)实验1

〇、前言

这几天翻到自己去年做的课程设计,感觉当时理解得还是挺深的(之前一直没觉得自己写得多认真,现在读起来觉得,嗯,虽然我只负责一半的实验,但给我98分真的不亏/doge),三篇半实验报告写了一万多字,都超过毕业设计论文的字数要求了,觉得不发出来有些可惜了。据我所知,今年(2022年秋季)操作系统课程设计要求和去年有所不同,可能nachos源码也不太一样了,但我觉得核心部分应该没有太大改动,希望我的源码分析仍然能帮到学弟学妹吧。如有理解错误的地方恳请大家批评指正。

写课程设计之前,我上过必修课《计算机组织与结构》和《操作系统》等,成绩都是班里第一,另外选修过《计算机体系结构》课程(用的是《深入理解计算机系统》这本书),并且自学过一些嵌入式系统设计。唉,当年那个对AI爱答不理、立志要冲体系结构的小姑娘,现在终于也向现实妥协,乖乖投入了机器学习的怀抱……谨以这几篇博客纪念当年对体系结构的热爱吧。

由于课程设计是我和同学共同完成,我只负责lab2、lab6、lab7,以及lab1的一部分,拿别人的劳动成果发博客不太厚道,所以这里只发上来我自己完成的部分。

一、实验内容

山东大学软件学院操作系统课程设计(2021秋季,nachos)实验1_第1张图片

二、Nachos源码分析

1. 中断

interrupt中定义了两个重要的类,PendingInterrupt的实例保存了准备发生的中断的信息,包括中断处理程序入口、交给中断处理程序的参数、该中断应该被处理的时间。

Interrupt是一个更大的概念,可以认为是整个中断系统。它包含了PendingInterrupt类型元素的有序列表,以及其它一些中断系统变量,比如是否允许中断、当前是否在运行中断处理程序、从中断返回时是否要切换上下文、机器当前所处的模式(空闲/内核态/用户态)。Interrupt中定义了很多操作,其中,构造函数中初始化了上述系统变量值,析构函数释放待发生中断的列表占用的空间(其它变量空间应该是自动释放的),转换、获取机器模态的操作,开闭中断,让系统空闲(当没有中断要执行的时候),停机,转换上下文,打印中断信息,产生一个中断(该操作由Schedule定义,感觉名不副实。内核不应该去调用这个函数,它应该只由硬件设备模拟器调用),和时钟滴答(OneTick函数,每次时钟滴答都会把timer+1,并且检查有没有要处理的中断,如果有,将全部处理完)。其中,像开闭中断、获取机器模态等操作是内部使用的,而Schedule、OneTick之类的供外界调用。

2. 时钟

时钟由文件timer.cc和timer.h实现,该模块的作用是模拟时钟中断。Nachos虚拟机可以如同实际的硬件一样,每隔一定的时间会发生一次时钟中断。Timer类模拟定时器。定时器每隔X个时钟周期就向CPU发一个时钟中断。它是时间片管理必不可少的硬件基础。它的实现方法是将一个即将发生的时钟中断放入中断队列,到了时钟中断应发生的时候,中断系统将处理这个中断,在中断处理的过程中又将下一个即将发生的时钟中断放入中断队列,这样每隔X个时钟周期,就有一个时钟中断发生。

Nachos利用其模拟的中断机制来模拟时钟中断。时钟中断间隔由TimerTicks宏决定(100倍Tick的时间)。在系统模拟时有一个缺陷,如果系统就绪进程不止一个的话,每次时钟中断都一定会发生进程的切换(ystem.cc中TimerInterruptHandler函数)。所以运行Nachos时,如果以同样的方式提交进程,系统的结果将是一样的。这不符合操作系统的运行不确定性的特性。所以在模拟时钟中断的时候,加入了一个随机因子,如果该因子设置的话,时钟中断发生的时机将在一定范围内是随机的。这样有些用户程序在同步方面的错误就比较容易发现。但是这样的时钟中断和真正操作系统中的时钟中断将有不同的含义。不能像真正的操作系统那样通过时钟中断来计算时间等等。是否需要随机时钟中断可以通过设置选项(-rs)来实现。

目前Nachos还没有充分发挥时钟中断的作用,只有在Nachos指定线程随机切换时,(Nachos -rs参数,线程管理部分Nachos主控模块分析)启动时钟中断,在每次的时钟中断处理的最后,加入了线程的切换。实际上,时钟中断在线程管理中的作用远不止这些,时钟中断还可以用作:
1)线程管理中的时间片轮转法的时钟控制,(线程管理系统中的实现实例中,对线程调度的改进部分)不一定每次时钟中断都会引起线程的切换,而是由该线程是否的时间片是否已经用完来决定。
2)分时系统线程优先级的计算(线程管理系统中的实现实例中,对线程调度的改进部分)
3)线程进入睡眠状态时的时间计算

可以通过时钟中断机制来实现sleep系统调用,在时钟中断处理程序中,每隔一定的时间对定时睡眠线程的时间进行一次评估,判断是否需要唤醒它们。

3. CPU指令执行

CPU指令执行由文件machine.cc和machine.h实现,Machine类用来模拟计算机主机。它提供的功能有:读写寄存器。读写主存、运行一条用户程序的汇编指令、运行用户程序、单步调试用户程序、显示主存和寄存器状态、将虚拟内存地址转换为物理内存地址、陷入 Nachos 内核等等。
Machine类实现方法是在宿主机上分配两块内存分别作为虚拟机的寄存器和物理内存。运行用户程序时,先将用户程序从 Nachos 文件系统中读出,写入模拟的物理内存中,然后调用指令模拟模块对每一条用户指令解释执行。将用户程序的读写内存要求,转变为对物理内存地址的读写。Machine类提供了单步调试用户程序的功能,执行一条指令后会自动停下来,让用户查看系统状态,不过这里的单步调试是汇编指令级的,需要对R2/3000指令比较熟悉。如果用户程序想使用操作系统提供的功能或者发出异常信号时,Machine调用系统异常陷入功能,进入Nachos的核心部分。

4.系统调用流程(以Halt()为例)

用户程序调用Halt()函数,该函数的声明在userprog/syscall.h文件中(这个文件中还声明了好几个系统调用,比如Fork()、Create()、Exit()等等,其中有的函数名(比如Fork)早在第二个实验——线程调度的时候就见过,但那些程序是本身就是属于内核的,内核Fork时的操作和用户调用Fork时的操作是不同的。目测对Fork的异常处理程序应该是调用Thread的Fork来完成工作的),注意到syscall.h没有对应的.cc文件,它的实现在test/start.s中,是一段汇编代码,但仅仅是”存根“,即它只负责把异常处理程序的编码(SC_Halt)放入r2号寄存器,然后执行系统调用指令syscall(这个指令的行为在mipssim.cc中指定,是RaiseException(SyscallException, 0),第一个参数是异常类型,第二个参数是发生异常的内存地址,由于这是个系统调用引发的异常,与寻址无关,所以第二个参数就填0。RaiseException函数在machine/machine.cc中定义,它先把发生异常的内存地址放入寄存器的BadVAddrReg项中(nachos中寄存器是用数组模拟的,BadVAddrReg的本质是一个数),然后调用DelayedLoad,然后把机器模态改为内核态,然后调用中断处理程序(ExceptionHandler(which);),然后把系统模态改回用户态,退出。重点是中断处理程序,ExceptionHandler(which)的定义在userprog/exception.cc中,它先从r2寄存器中获取系统调用的类型(这个动作是无论那种异常都会做的,但这个数值目前只在系统调用引发的异常中有用。不过我觉得r2寄存器应该是传参的作用,所以处理其它异常时可以把参数放在这个寄存器中),然后判断异常类型和系统调用类型。目前只能处理系统调用引发的异常,而且只能处理停机这一类。其它异常处理程序有待我们去开发。

最后最后,给大水老师疯狂打call!大水老师真的好耐心!

你可能感兴趣的:(操作系统课程设计,系统架构,开源)