1、Nachos线程通信和调度分析操作系统课程设计说明书 作者: 日期:36 个人收集整理 勿做商业用途操作系统课程设计说 明 书学 院: 信息科学与工程学院 一、 理解Nachos模拟的物理机的运行机制31. Sysdep模块分析(文件sysdep.cc sysdep。h)52.中断模块分析(文件interrupt。cc interrupt。h)103。 时钟中断模块分析(文件timer。cc timer。h)164。 终端设备模块分析(文件console。cc console.h)18二、 理解Nachos中线程运行机制191。 工具模块分析(文件list.cc list.h utility.
2、cc utility.h)202. 线程启动和调度模块分析(文件switch。s switch.h)213。 线程模块分析(文件thread.cc thread。h)224. 线程调度算法模块分析(文件scheduler.cc scheduler.h)265.Nachos主控模块分析(文件main.cc system.cc system.h)276. 同步机制模块分析(文件synch。cc synch.h)28三、 理解Nachos中支持用户进程的机制30一、用户程序空间(文件address。cc, address。h)35二、系统调用(文件exception。cc, syscall.h, s
3、tart.s)36一、 理解Nachos模拟的物理机的运行机制Machine类用来模拟计算机主机.它提供的功能有:读写寄存器.读写主存、运行一条用户程序的汇编指令、运行用户程序、单步调试用户程序、显示主存和寄存器状态、将虚拟内存地址转换为物理内存地址、陷入 Nachos 内核等等.Machine类实现方法是在宿主机上分配两块内存分别作为虚拟机的寄存器和物理内存.运行用户程序时,先将用户程序从 Nachos 文件系统中读出,写入模拟的物理内存中,然后调用指令模拟模块对每一条用户指令解释执行.将用户程序的读写内存要求,转变为对物理内存地址的读写.Machine类提供了单步调试用户程序的功能,执行一
4、条指令后会自动停下来,让用户查看系统状态,不过这里的单步调试是汇编指令级的,需要读者对R2/3000指令比较熟悉.如果用户程序想使用操作系统提供的功能或者发出异常信号时,Machine调用系统异常陷入功能,进入Nachos的核心部分。Interrupt类用来模拟硬件中断系统.在这个中断系统中,中断状态有开,关两种,中断类型有时钟中断、磁盘中断、控制台写中断、控制台读中断、网络发送中断以及网络接收中断。机器状态有用户态,核心态和空闲态。中断系统提供的功能有开/关中断,读/写机器状态,将一个即将发生中断放入中断队列,以及使机器时钟前进一步。在Interrupt类中有一个记录即将发生中断的队列,称为
5、中断等待队列。中断等待队列中每个等待处理的中断包含中断类型、中断处理程序的地址及参数、中断应当发生的时间等信息。一般是由硬件设备模拟程序把将要发生的中断放入中断队列。中断系统提供了一个模拟机器时钟,机器时钟在下列情况下前进:l 用户程序打开中断l 执行一条用户指令l 处理机没有进程正在运行机器时钟前进时,中断处理的过程如下图所示:Nachos中断处理时机NYNY进行正文切换中断处理程序要求进行正文切换?开中断取出队列中一个应当发生的中断,调用这个中断的处理程序去处理中断中断队列中有应当发生的中断?关中断中断系统成为整个Nachos虚拟机的基础,其它的模拟硬件设备都是建立在中断系统之上的。在此之
6、上,加上Machine类模拟的指令解释器,可以实现Nachos的线程管理、文件系统管理、虚拟内存、用户程序和网络管理等所有操作系统功能。下图展示了Nachos系统的整体结构。用 户 程 序线程管理网络协议文件系统虚拟内存终端设备时钟网络磁盘中 断 系 统指令解释和内存模拟Nachos系统的整体结构机器模拟的实现:1。 Sysdep模块分析(文件sysdep。cc sysdep。h)Nachos的运行环境可以是多种操作系统,由于每种操作系统所提供的系统调用或函数调用在形式和内容上可能有细微的差别.sysdep模块的作用是屏蔽掉这些差别。1。1 PoolFile 函数语法:bool PoolFil
7、e (int fd)参数:fd:文件描述符,也可以是一个套接字 (socket)功能:测试一个打开文件 fd 是否有内容可以读,如果有则返回TRUE,否则返回FALSE。当Nachos系统处于IDLE状态时,测试过程有一个延时,也就是在一定时间范围内如果有内容可读的话,同样返回TRUE。实现:通过 select 系统调用。返回:打开文件是否有内容供读取。1.2 OpenForWrite 函数语法:int OpenForWrite (char name)参数:name:文件名功能:为写操作打开一个文件。如果该文件不存在,产生该文件;如果该文件已经存在,则将该文件原有的内容删除.实现:通过 ope
8、n 系统调用。返回:打开的文件描述符。1。3 OpenForReadWrite 函数语法:int OpenForReadWrite (char *name, bool crashOnError)参数:name:文件名crashOnError:crash标志功能:为读写操作打开一个文件。当 crashOnError 标志设置而文件不能读写打开时,系统出错退出。实现:通过 open 系统调用.返回:打开的文件描述符。1.4 Read 函数语法:void Read (int fd, char buffer, int nBytes)参数:fd:打开文件描述符buffer:读取内容的缓冲区nBytes:
9、需要读取的字节数功能:从一个打开文件fd中读取nBytes的内容到buffer缓冲区.如果读取失败,系统退出。实现:通过 read系统调用。返回:空。1.5 ReadPartial 函数语法:int ReadPartial (int fd, char *buffer, int nBytes)参数:fd:打开文件描述符buffer:读取内容的缓冲区nBytes:需要读取的最大字节数 功能:从一个打开文件fd中读取nBytes的内容到buffer缓冲区. 实现:通过 read系统调用. 返回:实际读出的字节数。1。6 WriteFile 函数 语法:void WriteFile (int fd,
10、char buffer, int nBytes) 参数:fd:打开文件描述符buffer:需要写的内容所在的缓冲区nBytes:需要写的内容最大字节数 功能:将buffer缓冲区中的内容写nBytes到一个打开文件fd中。 实现:通过 write系统调用。 返回:空1.7 Lseek 函数 语法:void Lseek (int fd, int offset, int whence) 参数:fd:文件描述符offset:偏移量whence:指针移动的起始点 功能:移动一个打开文件的读写指针,含义同lseek系统调用;出错则退出系统。 实现:通过lseek 系统调用。 返回:空。1.8 Tell
11、函数 语法:int Tell (int fd) 参数:fd:文件描述符 功能:指出当前读写指针位置 实现:通过lseek 系统调用. 返回:返回当前指针位置.1。9 Close 函数 语法:void Close (int fd) 参数:fd:文件描述符 功能:关闭当前打开文件fd,如果出错则退出系统。 实现:通过close系统调用。 返回:空1.10 Unlink 函数 语法:bool Unlink (char *name) 参数:name:文件名 功能:删除文件。 实现:通过unlink系统调用. 返回:删除成功,返回TRUE;否则返回FALSE。1.11 OpenSocket 函数 语法:
12、int OpenSocket () 参数:无 功能:申请一个socket. 实现:通过socket系统调用.其中AF_UNIX参数说明使用UNIX内部协议。(Nachos是用SOCKET文件来模拟网络节点,所以采用UNIX内部协议。向该文件读写内容分别代表从该节点读取内容和向该网络节点发送内容)SOCK_DGRAM参数说明采用无连接定长数据包型的数据链路。 返回:申请到的socket ID。1。12 CloseSocket 函数 语法:void CloseSocket (int sockID) 参数:sockID:socket标识 功能:释放一个socket. 实现:通过close系统调用。
13、返回:无。1。13 Abort 函数 语法:void Abort () 参数:无 功能:退出系统 (非正常退出). 实现:通过abort系统调用。 返回:无.1。14 Exit 函数 语法:void Exit (int exitCode) 参数:exitCode:向系统的返回值 功能:退出系统. 实现:通过exit系统调用。 返回:空2.中断模块分析(文件interrupt.cc interrupt。h)中断模块的主要作用是模拟底层的中断机制.可以通过该模拟机制来启动和禁止中断 (SetLevel);该中断机制模拟了Nachos系统需要处理的所有的中断,包括时钟中断、磁盘中断、终端读/终端写中
14、断以及网络接收/网络发送中断.中断的发生总是有一定的时间。比如当向硬盘发出读请求,硬盘处理请求完毕后会发生中断;在请求和处理完毕之间需要经过一定的时间.所以在该模块中,模拟了时钟的前进.为了实现简单和便于统计各种活动所占用的时间起见,Nachos规定系统时间在以下三种情况下前进:执行用户态指令,时钟前进是显而易见的。我们认为,Nachos执行每条指令所需时间是固定的,为一个时钟单位(Tick)。一般系统态在进行中断处理程序时,需要关中断。但是中断处理程序本身也需要消耗时间,而在关闭中断到重新打开中断之间无法非常准确地计算时间,所以当中断重新打开的时候,加上一个中断处理所需时间的平均值。当系统中
15、没有就绪进程时,系统处于Idle状态。这种状态可能是系统中所有的进程都在等待各自的某种操作完成.也就是说,系统将在未来某个时间发生中断,到中断发生的时候中断处理程序将进行中断处理。在系统模拟中,有一个中断等待队列,专门存放将来发生的中断.在这种情况下,可以将系统时间直接跳到中断等待队列第一项所对应的时间,以免不必要的等待。当前面两种情况需要时钟前进时,调用OneTick方法。OneTick方法将系统态和用户态的时间分开进行处理,这是因为用户态的时间计算是根据用户指令为单位的;而在系统态,没有办法进行指令的计算,所以将系统态的一次中断调用或其它需要进行时间计算的单位设置为一个固定值,假设为一条用
16、户指令执行时间的10倍。虽然Nachos模拟了中断的发生,但是毕竟不能与实际硬件一样,中断发生的时机可以是任意的。比如当系统中没有就绪进程时,时钟直接跳到未处理中断队列的第一项的时间。这实际情况下,系统处于Idel状态到中断等待队列第一项发生时间之间,完全有可能有其它中断发生。由于中断发生的时机不是完全随机的,所以在Nachos系统中运行的程序,不正确的同步程序也可能正常运行,我们在此需要密切注意。Nachos线程运行有三种状态:Idle状态系统CPU处于空闲状态,没有就绪线程可以运行。如果中断等待队列中有需要处理的除了时钟中断以外的中断,说明系统还没有结束,将时钟调整到发生中断的时间,进行中
17、断处理;否则认为系统结束所有的工作,退出。系统态Nachos执行系统程序.Nachos虽然模拟了虚拟机的内存,但是Nachos系统程序本身的运行不是在该模拟内存中,而是利用宿主机的存储资源。这是Nachos操作系统同真正操作系统的重要区别。用户态系统执行用户程序。当执行用户程序时,每条指令占用空间是Nachos的模拟内存。中断等待队列是Nachos虚拟机最重要的数据结构之一,它记录了当前虚拟机可以预测的将在未来发生的所有中断。当系统进行了某种操作可能引起未来发生的中断时,如磁盘的写入、向网络写入数据等都会将中断插入到中断等待队列中;对于一些定期需要发生的中断,如时钟中断、终端读取中断等,系统会
18、在中断处理后将下一次要发生的中断插入到中断等待队列中.中断的插入过程是一个优先队列的插入过程,其优先级是中断发生的时间,也就是说,先发生的中断将优先得到处理。对中断等待队列的操作中断等待队列某些将会引起中断的操作系统定期发生的中断时钟前进判断有无中断发生当时钟前进或者系统处于Idle状态时,Nachos会判断中断等待队列中是否有要发生的中断,如果有中断需要发生,则将该中断从中断等待队列中删除,调用相应的中断处理程序进行处理。中断处理程序是在某种特定的中断发生时被调用,中断处理程序的作用包括可以在现有的模拟硬件的基础上建立更高层次的抽象。比如现有的模拟网络是有丢失帧的不安全网络,在中断处理程序中
19、可以加入请求重发机制来实现一个安全网络。2。1 PendingInterrupt类class PendingInterrupt public:PendingInterrupt (VoidFunctionPtr func, int param, int time, IntType kind);VoidFunctionPtr handler;/ 中断对应的中断处理程序int arg;/ 中断处理程序的参数int when;/ 中断发生的时机IntType type;/ 中断的类型,供调试用;这个类定义了一个中断等待队列中需要处理的中断。为了方便起见,所有类的数据和成员函数都设置为public的,不
20、需要其它的Get和Set等存取内部数据的函数。初始化函数就是为对应的参数赋值.2.2 Interrupt类class Interrupt public:/ 以下函数是Interrupt的对外接口Interrupt();/ 初始化中断模拟Interrupt();/ 终止中断模拟IntStatus SetLevel(IntStatus level);/ 开关中断,并且返回之前的状态void Enable();/ 开中断IntStatus getLevel() return level;/ 取回当前中断的开关状态 void Idle(); / 当进程就绪队列为空时,执行该函数void Halt();
21、 / 退出系统,并打印状态void YieldOnReturn();/ 设置中断结束后要进行进程切换的标志 MachineStatus getStatus() return status; / 返回系统当前的状态 void setStatus(MachineStatus st) status = st; / 设置系统当前的状态 void DumpState();/ 调试当前中断队列状态用 void Schedule(VoidFunctionPtr handler, int arg, int when, IntType type);/ 在中断等待队列中,增加一个等待中断 void OneTick
22、(); / 模拟时钟前进 private:/ 以下是内部数据和内部处理方法 IntStatus level;/ 中断的开关状态 List pending;/ 当前系统中等待中断队列bool inHandler;/ 是否正在进行中断处理标志 bool yieldOnReturn; / 中断处理后是否需要正文切换标志MachineStatus status;/ 当前虚拟机运行状态bool CheckIfDue(bool advanceClock);/ 检查当前时刻是否有要处理的中断void ChangeLevel(IntStatus old, IntStatus now);/ 改变当前中断的开关状
23、态,但不前进模拟时钟;其中,Schedule和 OneTick两个方法虽然标明是public的,但是除了虚拟机模拟部分以外的其它类方法是不能调用这两个方法的.将它们设置成public的原因是因为虚拟机模拟的其它类方法需要直接调用这两个方法。3。 时钟中断模块分析(文件timer。cc timer.h)该模块的作用是模拟时钟中断.Nachos虚拟机可以如同实际的硬件一样,每隔一定的时间会发生一次时钟中断。这是一个可选项,目前Nachos还没有充分发挥时钟中断的作用,只有在Nachos指定线程随机切换时,(Nachos rs参数,见线程管理部分Nachos主控模块分析)启动时钟中断,在每次的时钟中
24、断处理的最后,加入了线程的切换.实际上,时钟中断在线程管理中的作用远不止这些,时钟中断还可以用作:l 线程管理中的时间片轮转法的时钟控制,(详见线程管理系统中的实现实例中,对线程调度的改进部分)不一定每次时钟中断都会引起线程的切换,而是由该线程是否的时间片是否已经用完来决定。l 分时系统线程优先级的计算(详见线程管理系统中的实现实例中,对线程调度的改进部分)l 线程进入睡眠状态时的时间计算可以通过时钟中断机制来实现sleep系统调用,在时钟中断处理程序中,每隔一定的时间对定时睡眠线程的时间进行一次评估,判断是否需要唤醒它们。Nachos利用其模拟的中断机制来模拟时钟中断。时钟中断间隔由Time
25、rTicks宏决定(100倍Tick的时间)。在系统模拟时有一个缺陷,如果系统就绪进程不止一个的话,每次时钟中断都一定会发生进程的切换(见system。cc中TimerInterruptHandler函数).所以运行Nachos时,如果以同样的方式提交进程,系统的结果将是一样的.这不符合操作系统的运行不确定性的特性。所以在模拟时钟中断的时候,加入了一个随机因子,如果该因子设置的话,时钟中断发生的时机将在一定范围内是随机的.这样有些用户程序在同步方面的错误就比较容易发现。但是这样的时钟中断和真正操作系统中的时钟中断将有不同的含义。不能象真正的操作系统那样通过时钟中断来计算时间等等。是否需要随机时
26、钟中断可以通过设置选项(rs)来实现。Timer类的实现很简单,当生成出一个Timer类的实例时,就设计了一个模拟的时钟中断。这里考虑的问题是:怎样实现定期发生时钟中断?在Timer的初始化函数中,借用TimerHandler内部函数(见第1行).为什么不直接用初始化函数中的timerHandler参数作为中断处理函数呢?因为如果直接使用timerHandler作为时钟中断处理函数,第8行是将一个时钟中断插入等待处理中断队列,一旦中断时刻到来,立即进行中断处理,处理结束后并没有机会将下一个时钟中断插入到等待处理中断队列.TimerHandler内部函数正是处理这个问题。当时钟中断时刻到来时,调
27、用TimerHandler函数,其调用TimerExpired方法,该方法将新的时钟中断插入到等待处理中断队列中,然后再调用真正的时钟中断处理函数。这样Nachos就可以定时的收到时钟中断。那么为什么不将TimerExpired方法作为时钟中断在Timer的初始化函数中调用呢?这是由于C+语言不能直接引用一个类内部方法的指针,所以借用TimerHandler内部函数。这也是TimerExpired必须设计成public的方法的原因,因为它要被TimerHandler调用。这样的方法不仅仅在Timer模拟时钟中断中用到,所有需要定期发生的中断都可以采用这样的方法,如Nachos需要定期地检查是否
28、有终端的输入、网络是否有发给自己的报文等都是用这种方式实现。详见 network.cc 以及console。cc.TimeOfextInterrupt()方法的作用是计算下一次时钟中断发生的时机,如果需要时钟中断发生的时机是随机的,可以在Nachos命令行中设置 rs 选项.这样,Nachos的线程切换的时机将会是随机的。但是此时时钟中断则不能作为系统计时的标准了。二、 理解Nachos中线程运行机制Nachos中的系统线程和用户进程宿主机CPU和寄存器虚拟机CPU和寄存器用户程序系统线程用户程序系统线程用户进程用户程序系统线程系统线程系统线程系统线程Nachos为线程提供的功能函数有:1.
29、生成一个线程(Fork)2. 使线程睡眠等待(Sleep)3. 结束线程(Finish)4. 设置线程状态(setStatus)5. 放弃处理机(Yield)线程系统的结构如图所示:用户进程信号量条件变量锁Thread类模拟中断正文切换线程调度Nachos线程系统的结构线程管理系统中,有两个与机器相关的函数,正文切换过程依赖于具体的机器,这是因为系统线程切换是借助于宿主机的正文切换,正文切换过程中的寄存器保护,建立初始调用框架等操作对不同的处理机结构是不一样的。其中一个函数是ThreadRoot,它是所有线程运行的入口;另一个函数是SWITCH,它负责线程之间的切换。 Scheduler类用于
30、实现线程的调度.它维护一个就绪线程队列,当一个线程可以占用处理机时,就可以调用ReadyToRun方法把这个线程放入就绪线程队列,并把线程状态改成就绪态。FindNextToRun方法根据调度策略,取出下一个应运行的线程,并把这个线程从就绪线程队列中删除。如果就绪线程队列为空,则此函数返回空(NULL).现有的调度策略是先进先出策略(FIFO),Thread类的对象既用作线程的控制块,相当于进程管理中的PCB,作用是保存线程状态、进行一些统计,又是用户调用线程系统的界面。用户生成一个新线程的方法是:Thread* newThread = new Thread(”New Thread);/ 生成
31、一个线程类newThread-Fork(ThreadFunc,ThreadFuncArg);/ 定义新线程的执行函数及其参数 Fork方法分配一块固定大小的内存作为线程的堆栈,在栈顶放入ThreadRoot的地址。当新线程被调上CPU时,要用SWITCH函数切换线程图像,SWITCH函数返回时,会从栈顶取出返回地址,于是将ThreadRoot放在栈顶,在SWITCH结束后就会立即执行ThreadRoot函数。ThreadRoot是所有线程的入口,它会调用Fork的两个参数,运行用户指定的函数;Yield方法用于本线程放弃处理机。Sleep方法可以使当前线程转入阻塞态,并放弃CPU,直到被另一个
32、线程唤醒,把它放回就绪线程队列。在没有就绪线程时,就把时钟前进到一个中断发生的时刻,让中断发生并处理此中断,这是因为在没有线程占用CPU时,只有中断处理程序可能唤醒一个线程,并把它放入就绪线程队列。线程要等到本线程被唤醒后,并且又被线程调度模块调上CPU时,才会从Sleep函数返回。有趣的是,新取出的就绪线程有可能就是这个要睡眠的线程。例如,如果系统中只有一个A线程,A线程在读磁盘的时候会进入睡眠,等待磁盘操作完成。因为这时只有一个线程,所以A线程不会被调下CPU,只是在循环语句中等待中断。当磁盘操作完成时,磁盘会发出一个磁盘读操作中断,此中断将唤醒A线程,把它放入就绪队列.这样,当A线程跳出
33、循环时,取出的就绪线程就是自己。这就要求线程的正文切换程序可以将一个线程切换到自己, Nachos的线程正文切换程序SWITCH可以做到这一点,于是A线程实际上并没有被调下CPU,而是继续运行下去了。Nachos线程管理系统的初步实现1. 工具模块分析(文件list。cc list。h utility。cc utility.h)工具模块定义了一些在Nachos设计中有关的工具函数,和整个系统的设计没有直接的联系,所以这里仅作一个简单的介绍。List类在Nachos中广泛使用,它定义了一个链表结构,有关List的数据结构和实现如下所示:class ListElement /定义了List中的元素
34、类型 public: ListElement(void *itemPtr, int sortKey);/初始化方法 ListElement *next;/指向下一个元素的指针 int key; /对应于优先队列的键值 void *item; /实际有效的元素指针;其中,实际有效元素指针是(void )类型的,说明元素可以是任何类型.class List public:List();/初始化方法List();/析构方法void Prepend(void *item);/将新元素增加在链首void Append(void item); /将新元素增加在链尾void *Remove(); /删除链首
35、元素并返回该元素void Mapcar(VoidFunctionPtr func);/将函数func作用在链中每个元素上bool IsEmpty();/判断链表是否为空void SortedInsert(void *item, int sortKey);/将元素根据key值优先权插入到链中void *SortedRemove(int keyPtr); /将key值最小的元素从链中删除,/并返回该元素 private: ListElement first; /链表中的第一个元素 ListElement last;/链表中的最后一个元素;其它的工具函数如min和max以及一些同调试有关的函数,这里
36、就不再赘述。2. 线程启动和调度模块分析(文件switch.s switch.h)线程启动和线程调度是线程管理的重点。在Nachos中,线程是最小的调度单位,在同一时间内,可以有几个线程处于就绪状态。Nachos的线程切换借助于宿主机的正文切换,由于这部分内容与机器密切相关,而且直接同宿主机的寄存器进行交道,所以这部分是用汇编来实现的。由于Nachos可以运行在多种机器上,不同机器的寄存器数目和作用不一定相同,所以在switch。s中针对不同的机器进行了不同的处理.读者如果需要将Nachos移植到其它机器上,就需要修改这部分的内容。2.1 ThreadRoot函数Nachos中,除了main线
37、程外,所有其它线程都是从ThreadRoot入口运行的。它的语法是:ThreadRoot (int InitialPC, int InitialArg, int WhenDonePC, int StartupPC)其中,InitialPC指明新生成线程的入口函数地址,InitialArg是该入口函数的参数;StartupPC是在运行该线程是需要作的一些初始化工作,比如开中断;而WhenDonePC是当该线程运行结束时需要作的一些后续工作.在Nachos的源代码中,没有任何一个函数和方法显式地调用ThreadRoot函数,ThreadRoot函数只有在线程切换时才被调用到。一个线程在其初始化的最
38、后准备工作中调用StackAllocate方法(见本章3.4),该方法设置了几个寄存器的值(InterruptEnable函数指针,ThreadFinish函数指针以及该线程需要运行函数的函数指针和运行函数的参数) ,该线程第一次被切换上处理机运行时调用的就是ThreadRoot函数。其工作过程是:1. 调用 StartupPC 函数;2. 调用 InitialPC 函数;3. 调用 WhenDonePC 函数;这里我们可以看到,由ThreadRoot入口可以转而运行线程所需要运行的函数,从而达到生成线程的目的。2.2 SWITCH函数Nachos中系统线程的切换是借助宿主机的正文切换。SWI
39、TCH函数就是完成线程切换的功能。SWITCH的语法是这样的:void SWITCH (Thread *t1, Thread t2);其中t1是原运行线程指针,t2是需要切换到的线程指针。线程切换的三步曲是:1. 保存原运行线程的状态2. 恢复新运行线程的状态3. 在新运行线程的栈空间上运行新线程3。 线程模块分析(文件thread。cc thread。h)Thread类实现了操作系统的线程控制块,同操作系统课程中进程程管理中的PCB (Process Control Block) 有相似之处。Thread线程控制类较PCB为简单的多,它没有线程标识 (pid)、实际用户标识 (uid)等和线
40、程操作不是非常有联系的部分,也没有将PCB分成proc结构和user结构。这是因为一个Nachos线程是在宿主机上运行的。无论是系统线程和用户进程,Thread线程控制类的实例都生成在宿主机而不是生成在虚拟机上.所以不存在实际操作系统中proc结构常驻内存,而user结构可以存放在盘交换区上的情况,将原有的两个结构合并是Nachos作的一种简化。Nachos对线程的另一个简化是每个线程栈段的大小是固定的,为4096-5个字 (word),而且是不能动态扩展的.所以Nachos中的系统线程中不能使用很大的栈空间,比如:void foo () int buff10000; 。.可能会不能正常执行,
41、如果需要使用很大空间,可以在Nachos的运行堆中申请:void foo () int *buf = new int10000; 。.。如果系统线程需要使用的栈空间大于规定栈空间的大小,可以修改StackSize宏定义。Thread类的定义和实现如下所示:class Thread private:int* stackTop; /当前堆栈指针int machineStateMachineStateSize; /宿主机的运行寄存器 public:Thread(char debugName);/初始化线程Thread(); /析构方法void Fork(VoidFunctionPtr func, i
42、nt arg); /生成一个新线程,执行func(arg)void Yield(); /切换到其它线程运行void Sleep(); /线程进入睡眠状态void Finish(); /线程结束时调用void CheckOverflow(); /测试线程栈段是否溢出void setStatus(ThreadStatus st);/设置线程状态char* getName() return (name); /取得线程名(调试用)void Print() printf(%s, , name); /打印当前线程名(调试用) private:int stack;/线程的栈底指针ThreadStatus s
43、tatus;/当前线程状态char name;/线程名(调试用)void StackAllocate(VoidFunctionPtr func, int arg);/申请线程的栈空间#ifdef USER_PROGRAMint userRegistersNumTotalRegs;/虚拟机的寄存器组 public:void SaveUserState();/线程切换时保存虚拟机寄存器void RestoreUserState();/线程切换时恢复虚拟机寄存器组AddrSpace space;/线程运行的用户程序#endif线程状态有四种,状态之间的转换如图3。6所示:Nachos线程的状态转换运行结束等待的事件发生等待某事件发生初始化结束处理机调度运行被迫放弃处理机运行态阻塞态初启态就绪态初启态用户进程在线程切换的时候,除了需要保存宿主机的状态外,必须还要保存虚拟机的寄存器状态。UserRegisters数组变量和SaveUserState(), RestoreUserState()方