Status
Done

实验目的

  1. Nachos开发环境的安装测试(不含Linux系统本身及虚拟机软件的安装测试)。
  1. Nachos实验代码框架(源码目录)的基本分析。
  1. Nachos Makefile的基本分析。
4.  硬件机制模拟部分的实现原理分析,包括中断、时钟、CPU指令执行。
注意:实验1没有代码的编写、演示验收及提交,因此分数完全来自于以上4部分实验内容在实验报告中的撰写质量情况。
 

实验步骤与内容:

开发工具安装过程;

由于使用的虚拟机是32位的ubuntu,参考《32位Ubuntu安装Nachos》进行安装
1.nautilus-open-terminal 插件安装
在Ubuntu中,打开终端可以通过Ctrl+Alt+T来打开,但其打开的是~下的,如果进入指定的目录,便需要通过cd命令来进行切换,为简化操作,安装插件,使得可以通过鼠标右键来在当前目录下打开终端
使用命令sudo apt install nautilus-open-terminal 安装插件
nautilus -q 使插件生效
之后便可以在任意目录下便通过鼠标右键来打开路径为当前目录下的终端
notion image
2.安装g++
由于安装的Ubuntu 14只带有gcc,而无g++,在线安装g++
使用命令sudo apt install g++
3.安装用于MIPS的交叉编译器
将压缩包 gcc-2.8.1-mips.tar.gz 复制到 ~ (Home,用户主目录)
cd /usr/local
sudo tar -xzvf ~/gcc-2.8.1-mips.tar.gz
notion image
这样就安装好了用于MIPS的交叉编译器
 

Nachos安装过程

1.安装Nachos 3.4
cd ~
mkdir oscp
cd oscp
将压缩包 nachos-3.4-ualr-2022.tar.gz 复制到 ~/oscp
tar -xzvf nachos-3.4-ualr-2022.tar.gz
notion image
Nachos安装完毕
2. 测试Nachos threads
cd ~/oscp/nachos-3.4-ualr-2022/code/threads
make clean
make
./nachos
notion image
3. 生成实用程序coff2noff及coff2flat
cd ../bin
make clean
make
生成了 coff 转 noff 及 coff 转 flat 的MIPS可执行文件转换工具
因为Nachos安装包nachos-3.4-ualr-2022.tar.gz中已包含编译好的这两个工具(Linux i386版),也可不make,只要确认coff2noff及coff2flat有执行权限即可
notion image
使用ls -l 列出文件详情,可以看到cof2noff和coff2flat的权限均包含‘x’,即拥有执行权限
如果不含执行权限,可以使用chmod 777 coff2flat 等命令将文件权限修改为777
4.Make Nachos for User Program
cd ../userprog
make clean
make
在code/userprog下生成了能加载用户进程的nachos
notion image
5.Make User(MIPS) Test Programs
cd ../test
make clean
make
生成了用户MIPS程序halt,matmult,shell。注意在进一步实现Nachos内核的相关功能(比如虚拟内存)前,需要内存大的Nachos用户程序无法正常运行
../userprog/nachos -x halt.noff
notion image
 
 
 

Nachos实验代码框架(源码目录)的基本分析

Nachos 体系结构
notion image
Nachos是建立在一个软件模拟的虚拟机之上的,模拟了MIPS R2/3000的指令集、主存、中断系统、网络以及磁盘系统等操作系统所必须的硬件系统。许多现代操作系统大多是先在用软件模拟的硬件上建立并调试,最后才在真正的硬件上运行。
为什么用软件模拟硬件?
用软件模拟硬件的可靠性比真实硬件高得多,不会因为硬件故障而导致系统出错,便于调试。虚拟机可以在运行时报告详尽的出错信息,更重要的是采用虚拟机使Nachos的移植变得非常容易,在不同机器上移植Nachos,只需对虚拟机部分作移植即可。
为何采用R2/3000指令集?
采用R2/3000指令集的原因是该指令集为RISC指令集,指令数目比较少。Nachos虚拟机模拟了其中的63条指令。由于该指令集是一个比较常用的指令集,许多现有的编译器如gcc/g++能够直接将C或C++源程序编译成该指令集的目标代码,这样不必编写编译器,读者就可以直接用C/C++语言编写应用程序,使得在Nachos上开发大型的应用程序也成为可能。
 
网络
Nachos的虚拟机使得网络的实现相当简单。与MINIX不同,Nachos只是一个在宿主机上运行的一个进程。在同一个宿主机上可以运行多个Nachos进程,各个进程可以相互通讯,作为一个全互连网络的一个节点;进程之间通过Socket进行通讯,模拟了一个全互连网络。
 
源码目录分析
 
code/ 下不同文件夹下的Makefile,调用了三种C++/C编译器
Makefile in Folder
Compiler编译器
machine, threads, userprog, filesys, network, demo0, demo1
Host g++
bin
Host gcc
test
MIPS cross compiler gcc
 

Nachos Makefile的基本分析

Makefile

一个大型工程中的源文件有很多,按类型、功能、模块分别放在若干个目录中。在调试过程中,如果简单用gcc等编译命令,程序的编译将是一件非常繁琐的任务。
makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大地提高了软件开发的效率。
make工具解读文本makefile,来控制编译器的具体执行。
 
makefile文件里的主要内容,是关于:
(1)target:目标文件,即 make 要建立的目标文件
(2)dependencies:依赖文件,通常为要编译的源文件或要连接的浮动目标代码文件
(3)command:从目标依赖体创建目标体的命令(command)列表,通常为编译或连接命令
【注意】 ① 在Makefile文件中,命令前面必须是Tab ② 一行写不完可用\换行,\后不能有空格
 
GNU Make 的主要工作是读进一个文本文件 makefile,并解释makefile中指令
make 会检查磁盘上的文件,如果目标文件的时间戳(该文件被改动的时间)比它的任何一个依赖文件旧的话, make 就执行相应的命令,以便更新目标文件。
在默认的方式下,输入make命令:
make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  • 如果找到,它会找文件中的第一个目标文件,在上面的例子中,他会找到“myprog”这个文件,并把这个文件作为最终的目标文件。
  • 如果myprog文件不存在,或是myprog所依赖的后面的[.o]文件的文件修改时间要比myprog这个文件新,那么它就会执行后面所定义的命令来生成myprog这个文件。
 

Nachos系统中的Makefile分析

整体架构如下:
code中有两个为其子目录所公用的 Makefile文件,Makefile.common和Makefile.dep
notion image
Makefile.common文件里包含一个include文件Makefile.dep
notion image
 
/lab文件夹下有两个文件,Makefile和Makefile.local
notion image
Makefile文件里包含两个include文件一个是makefile.local另一个是makefile.common
notion image
 
Makefile.dep
Makefile.dep 文件属于硬件环境依赖部分,根据当前系统的不同架构适配不同的编译指令。
1.文件开头定义各种编译器
其中CPP定义的是C++的预处理器。其他则是编译器
 
2.用uname指令获取系统类型,将uname命令输出的结果存储到uname变量中
 
3.定义交叉编译相关的参数,使之能够调用gcc交叉编译,在当前架构下编译出与nachos模拟机器相匹配的mips架构执行文件。同时,将编译产物置于相应系统架构名称的目录下。
 
4.其他配置
此处为让nachos可以在64位机子上运行的相关配置
 
 
Makefile.common
1.Makefile.common 首先括入 Makefile.dep,然后用 vpath 定义各类文件搜索路径。
在一些大的工程中,有大量的源文件,通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
vpath是makefile中的特殊变量,如果没有这个变量,make只会在当前路径寻找相关文件,定义后,当前路径找不到就到指定路径下找。
 
2.定义编译开关宏 CFLAGS、目标文件规则宏 ofile
 
3.最终目标
 
 
4.对于目标文件目录下的每一个.o文件,都依赖于和它同名的.cc、.c、.s文件。总体是一个for循环的结构,遍历目录下的所有.o文件。-c让编译器只编译、不连接。$<代表第一个依赖项。
 
5.可执行文件的产生依赖于.o文件,产生.o文件需要依赖于.d文件中描述的其依赖的.h文件
为了自动化地生成.o文件,先求.s文件(汇编文件),.c文件(C文件),.cc文件(C++源文件)的集合。以上代码提取每种文件的文件名,删去扩展名,求出所有.d文件(依赖文件)。依赖文件里的信息包括.o依赖哪些.h文件。最后include即为将所有.d文件全部引入。
 
6.gcc编译器编译时有能力自动找当前编译的c、cc、s文件依赖哪些.h文件(通过-MM参数),而不需要手动输入
我们include的是.d文件,好处是有了上面的构建依赖关系的过程,这些文件可以在编译时自动生成,一旦修改了.cc等文件,再次make就能自动更新相应的.d文件。以上的代码就是用作自动生成.d文件,自动产生依赖关系,并在产生相应的.o文件时被使用。
 
7.最后定义了两项make指令,分别是清理make生成的目标文件与临时文件
find $(arch_dir)命令寻找所有architecture目录下的文件,通过管道输出,在其中搜索字符串为版本控制的文件,这些文件保留不删除。
 
 
Makefile
可见项目将每个lab的Makefile核心写在Makefile.local中,另外再引入父目录中的公共Makefile文件,有助于项目结构的清晰化管理,避免重复的配置文件。
 
 
Makefile.local
1.配置声明汇编文件需要链接的部分,在相应架构的构建产物中,于depends中生成对应的.d文件,于objects中生成.o文件
 
2.声明需要与该lab一同编译的源码文件。在lab的main文件中通常可以看到include了一些.h文件,这些头文件与如上的.cc文件一一对应。当编写lab代码是若需要使用到这些依赖中的类或函数,应该将其对应的头文件引入。
 
3.声明需要包含的头文件所在路径,以便使用gcc进行编译链接时能够查找到这些文件
其中-I-这个参数防止gcc默认在.cc的同一个目录下include进.h文件,而是强制编译器根据-I-之后定义的路径顺序依次搜索.h文件。这样当我们将一个需要修改的类(如scheduler.cc和scheduler.h)拷贝到相应的lab目录下时,就不需要拷贝其他的文件。这样避免了make操作优先在Makefile的当前目录下搜索.h文件,而是严格按照我们定义的路径来搜索。
I-目前被作为gcc不建议使用的功能,但这对我们的实验是有用的。
INCPATH指明在寻找头文件时先在lab2目录下的include文件中找,若找不到再到后面的目录下找。这样如果需要修改某个文件只需要把它从主目录复制到相应的lab目录底下修改即可,并能够被程序优先使用,而不需要更改全局的各项文件。这样就可以在各个lab目录下构建一个「Modified Nachos」程序。
 
 
gcc(GNU Compiler Collection)
gcc是GNU推出的功能强大、性能优越的多平台编译器,是GNU的代表作品之一
gcc编译器能将C、C++语言源程序、汇编程序和目标程序编译、链接成可执行文件
与一般的编译器相比,gcc编译产生的代码的执行效率平均要高20%~30%
gcc通过后缀来区别输入文件的类别:
  • .c为后缀的文件,C语言源代码文件
  • .a为后缀的文件,是由目标文件构成的函数库文件
  • .C,.cc或.cpp 为后缀的文件,是C++源代码文件
  • .i 为后缀的文件: 是已经预处理过的C源代码文件
  • .ii为后缀的文件: 是已经预处理过的C++源代码文件
  • .h为后缀的文件,是程序所包含的头文件
  • .o为后缀的文件,是编译后的目标文件
  • .s为后缀的文件: 是汇编语言源代码文件
  • .S为后缀的文件: 是经过预编译的汇编语言源代码文件
 
gcc的执行过程 使用gcc由C语言源代码文件生成可执行文件的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶
预处理 - Preprocessing
编译 - Compilation
汇编 - Assembly
链接 - Linking
notion image
 
 
 
 

Nachos硬件机制模拟部分的实现原理分析(中断、时钟、CPU指令执行等)

在nachos程序的启动前,首先将创建nachos machine。在此过程中进行硬件初始化,涵盖中断控制、时钟、MIPS处理器、控制台及文件系统等部分。其关键代码位于machine目录下;若启动一个线程,还需调用threads目录system.h中的Initialize函数。
 
nachos的内核从threads/main.cc目录开始执行,从main函数启动。
将参数传入initialize方法中,initialize方法在threads/system.cc文件中,接收参数,设置变量和flags;并构建内核必须的组件,包括第一个运行线程的TCB等。
nachos是一个Linux的用户进程,在其中创建了若干个用户线程,因此main函数对应的线程在nachos启动就已经存在(不存在在nachos中第一次上CPU的这件事,而是由Linux内核完成),不过我们也需要为其分配一个TCB,作为一个线程。因为它也会下CPU,将处理机让给后续创建的其他线程运行。main可以先于其他线程退出,也可以最后退出,如果其先退出,那么其他创建的线程还会继续运行,作为nachos的用户线程。
 
 

中断 interrupt

中断类定义在machine/interrupt,文件为interrupt.cc,interrupt.h,主要作用是模拟底层的中断机制。
该中断机制模拟了 Nachos 系统需要处理的所有的中断,包括时钟中断、磁盘中断、终端读/终端写中断以及网络接收/网络发送中断。
中断的发生总是有一定的时间。比如当向硬盘发出读请求,硬盘处理请求完毕后会发生中断;在请求和处理完毕之间需要经过一定的时间。所以在该模块中,模拟了时钟的前进:
在执行用户态指令和重新打开中断时,均会调用OneTIck方法,OneTick方法将系统态和用户态的时间分开进行处理,这是因为用户态的时间计算是根据用户指令为单位的;而在系统态,没有办法进行指令的计算,所以将系统态的一次中断调用或其它需要进行时间计算的单位设置为一个固定值,假设为一条用户指令执行时间的10倍。
当系统中没有就绪进程时,系统处于Idle状态。这种状态可能是系统中所有的进程都在等待各自的某种操作完成。也就是说,系统将在未来某个时间发生中断,到中断发生的时候中断处理程序将进行中断处理。在系统模拟中,有一个中断等待队列,专门存放将来发生的中断。在这种情况下,可以将系统时间直接跳到中断等待队列第一项所对应的时间,以免不必要的等待。
 
Nachos线程运行有三种状态:
  • Idle状态
    • 系统CPU处于空闲状态,没有就绪线程可以运行。如果中断等待队列中有需要处理的除了时钟中断以外的中断,说明系统还没有结束,将时钟调整到发生中断的时间,进行中断处理;否则认为系统结束所有的工作,退出。
  • 系统态
    • Nachos执行系统程序。Nachos虽然模拟了虚拟机的内存,但是Nachos系统程序本身的运行不是在该模拟内存中,而是利用宿主机的存储资源。这是Nachos操作系统同真正操作系统的重要区别。
  • 用户态
    • 系统执行用户程序。当执行用户程序时,每条指令占用空间是Nachos的模拟内存。
 
Nachos需要处理的中断种类有:
TimerInt:
时钟中断
DiskInt:
磁盘(读/写)中断
ConsoleWriteInt:
终端写中断
ConsoleReadInt:
终端读终端
NetworkSentInt:
网络发送中断
NetworkRecvInt:
网络接收中断
 
1.【interrupt.h】首先定义枚举类型
 
2.【interrupt.h】PendingInterrupt类定义了一个中断等待队列中需要处理的中断,构造函数的参数包括中断处理函数、函数的参数、中断发生的事件和中断类型,,将在interrupt.cc中实现
 
3.【interrupt.h】Interrupt类的定义
 
4.【Interrupt.cc】实现了interrupt.h中定义的方法,此处将挑选重点代码进行分析
OneTick方法会让时钟前进一个单位
Schedule方法会将一个中断插入等待处理中断队列
CheckIfDue函数测试当前等待中断队列中是否要有中断发生,并根据不同情况作出不同处理
如果该函数被OneTick()调用,advanceClock值为false,程序先检查中断执行的时间when和当前的系统时间,如果尚未到达,则将其再次加入到中断队列;反之,则对中断进行处理。如果被idle()调用,advanceClock值为true,则前进到下一个中断的时间,对中断进行处理。
 

时钟 Timer

时钟定义在machine/timer,文件为timer.cc,timer.h,主要作用是模拟时钟中断.
Nachos虚拟机可以如同实际的硬件一样,每隔一定的时间会发生一次时钟中断。这是一个可选项,目前Nachos还没有充分发挥时钟中断的作用,只有在Nachos指定线程随机切换时,(Nachos -rs参数)启动时钟中断,在每次的时钟中断处理的最后,加入了线程的切换。
Nachos利用其模拟的中断机制来模拟时钟中断。时钟中断间隔由TimerTicks宏决定(100倍Tick的时间)。
 
1.【timer.h】Timer类的定义
 
2.【timer.cc】timer.h中方法的实现
在Timer初始化时,传入中断处理的具体函数、函数调用参数及是否随机的控制变量
当时钟中断时刻到来时,调用TimerHandler函数,其调用TimerExpired方法,该方法将新的时钟中断插入到等待处理中断队列中,然后再调用真正的时钟中断处理函数。这样Nachos就可以定时的收到时钟中断。
TimeOfextInterrupt()方法的作用是计算下一次时钟中断发生的时机,如果需要时钟中断发生的时机是随机的,可以在Nachos命令行中设置 –rs 选项。这样,Nachos的线程切换的时机将会是随机的。但是此时时钟中断则不能作为系统计时的标准了。
 

终端设备 console

该模块的作用是模拟实现终端的输入和输出。包括两个部分,即键盘的输入和显示输出。终端输入输出的模拟是异步的,也就是说当发出终端的输入输出请求后系统即返回,需要等待中断发生后才是真正完成了整个过程。
 
1.【console.h】Console类的定义
Nachos的终端模拟借助了两个文件,即在生成函数中的readFile和writeFile。这两个文件分别模拟键盘输入和屏幕显示。当readFile为NULL时,Nachos以标准输入作为终端输入;当writeFile为NULL时,Nachos以标准输出作为终端输出。
 
2.【console.cc】两个内部函数ConsoleReadPoll以及ConsoleWriteDone的作用同Timer模拟中的TimerHandler内部函数。
 
3.【console.cc】实现console.h中定义的方法
系统的终端操作有严格的工作顺序,对读终端来说:CheckCharAvail -> GetChar -> CheckCharAvail -> GetChar ->...
系统通过定期的读终端中断来判断终端是否有内容供读取,如果有则读出;如果没有,下一次读终端中断继续判断。读出的内容将一直保留到GetChar将其读走。
对写终端来说:PutChar -> WriteDone -> PutChar -> WriteDone -> ...
系统发出一个写终端命令PutChar,模拟系统将直接向终端输出文件写入要写的内容,但是对Nachos来说,整个写的过程并没有结束,只有当写终端中断来到后整个写过程才算结束。
 

磁盘设备 disk

磁盘设备模拟了一个物理磁盘。Nachos用宿主机中的一个文件来模拟一个单面物理磁盘,该磁盘由道组成,每个道由扇区组成,而每个扇区的大小是固定的。和实际的物理磁盘一样,Nachos以扇区为物理读取/写入的最小单位,每个扇区有唯一的扇区地址,具体的计算方法是:
track * SectorsPerTrack + offset
该物理磁盘是一个异步的物理磁盘,同终端设备和网络设备一样,当系统发出读磁盘的请求,立即返回,只有具体的磁盘终端到来的时候,整个过程才算结束。
 
1.【disk.h】Disk类的定义
 
2.【disk.cc】Disk类的实现
Nachos对物理磁盘的模拟和对网络、终端等的模拟和所采用的手段类似
 

CPU指令执行

Nachos 对内存、寄存器以及CPU的模拟
1.【machine.h】模拟机的机器指令由操作代码和操作数组成的,其类定义和实现如下所示:
 
2.【machine.h】定义Machine类的目的是为了执行用户程序。如同许多其它系统一样,用户程序不直接使用内存的物理地址,而是使用自己的逻辑地址,在用户程序逻辑地址和实际物理地址之间,就需要一次转换,系统提供了两种转换方法的接口:线性页面地址转换方法TLB页面地址转换方法,Nachos选择使用先行页面地址转换。
 
3.【translate.h】地址转换表定义
 
4.【translate.cc】由于Nachos可以在多种平台上运行,各种运行平台的数据表达方式不完全一样,有的是高位在前,有的是高位在后。为了解决跨平台的问题,特地给出了四个函数作数据转换,它们是WordToHost、ShortToHost、WordToMachine和ShortToMachine。
 
5.【translate.cc】提供方法实现用户程序的内存管理、地址转换以及异常处理
  • RaiseException 方法:当系统遇到异常(如访问错误、页错误等)时,调用该方法。它根据错误类型(which)和出错地址(badVAddr),通过 ExceptionHandler 处理异常。
  • ReadMem 方法:从用户程序的逻辑地址 addr 中读取指定大小的数据,并将数据存储在 value 指向的空间中。该方法依赖 Translate 方法来进行虚拟地址到物理地址的转换。
  • WriteMem 方法:将指定内容写入用户程序的逻辑地址 addr。它将 value 中的数据按照指定大小 size 写入内存,调用 Translate 方法进行地址转换。
  • Translate 方法:将用户程序的虚拟地址 virtAddr 转换为物理地址 physAddr,并检查内存对齐和权限等问题。该方法会验证地址是否对齐,检查页表或TLB中的表项是否有效,并处理页错误、只读异常等情况。
  • Run 方法:模拟执行一个用户程序,逐条取指并执行,模拟用户程序在 CPU 中的运行过程。执行过程中,系统会进入用户模式,取出指令并进行解码和执行。每执行一条指令,系统时钟前进一单位。
 
 
参考文献:
 
 
 

结论分析与体会

通过本次实验,我深入了解了Nachos的开发环境、代码框架和硬件机制的实现原理。
1. Nachos开发环境的安装与测试
在安装Nachos开发环境的过程中,我成功配置了所需的开发工具链,包括编译器、调试工具和运行时库。通过测试,确保了Nachos能够在虚拟机环境中顺利运行。这一过程不仅帮助我理解了虚拟机与主机系统之间的交互,也使我掌握了如何在一个虚拟化环境中进行开发和调试。
安装和配置开发环境是任何开发项目的基础,尤其是在操作系统层面的开发中,准确的环境设置对于后续实验的顺利进行至关重要。虚拟机的使用不仅提供了一个隔离的测试环境,也为实验提供了更多的灵活性。
2. Nachos实验代码框架的基本分析
通过分析Nachos的源码目录结构,我了解了Nachos中各个模块的功能划分及其相互关系。Nachos的源代码组织清晰,每个模块的职责分明。这些模块的划分和设计思想为操作系统的学习提供了很好的参考。
良好的代码结构和模块化设计是大型系统开发的核心,特别是在操作系统开发中,模块化设计能够让不同部分的功能互不干扰,便于扩展和调试。通过分析这些代码框架,我学到了如何高效组织和管理复杂系统的源码。
3. Nachos Makefile的基本分析
通过对Nachos的Makefile的分析,我对如何使用Make工具进行自动化构建有了更深刻的理解。Makefile定义了如何编译源代码、生成目标文件以及如何链接成最终可执行文件。分析Makefile中的依赖关系和构建规则,让我熟悉了构建过程中的每个环节,帮助我更好地理解了编译过程。
Makefile是管理项目构建的关键工具,能够有效地自动化编译和链接过程,尤其是在跨平台开发和调试时显得尤为重要。在多模块开发中,Makefile可以帮助快速定位问题和自动化处理构建流程,大大提高了开发效率。
4. 硬件机制模拟部分的实现原理分析
在硬件机制模拟部分,我深入研究了中断处理、时钟管理以及CPU指令执行的模拟原理。通过实现虚拟地址到物理地址的转换、页表管理以及TLB(Translation Lookaside Buffer)等机制,我对操作系统中的内存管理和硬件抽象有了更为直观的理解。尤其是在模拟中断、时钟和CPU指令执行时,Nachos模拟了操作系统的基本调度机制、进程管理和异常处理机制。
通过模拟硬件机制,我对操作系统如何与硬件进行交互、如何管理内存和处理器资源有了更为深入的理解。在实现和分析这些机制时,我不仅学到了如何处理页错误(PageFault)等异常情况,还更加清晰地理解了操作系统内核的基本结构和工作原理。
 
Loading...