nachos源码分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
计算机科学与技术学院2009-2010 学年第一学期《操作系统》课程设计
题目: Nachos线程模块分析
班级: 070341 B 学号: 070341221 姓名:阮琳琳
教师:杨志娴
成绩:
1. 题目分析
本次课程设计中,我将遵循课本上进程部分的章节组织来分析Nachos中线程模块。我想这样会使分析的思路更加清晰,系统性和理论性更强。
分析目的:
通过阅读nachos代码,了解一个最基本的操作系统是如何工作运转起来的。结合书本上的知识,理解nachos中的源码,并使在书本上学到的知识得到巩固。以使我对操作一同这门课有更深入的理解。
Nachos相关知识概述
一、Nachos的线程管理
Nachos广泛采用线程的概念,是多线程操作系统。线程是Nachos处理机调度的单位,在Nachos中线程分成两类,一类是系统线程。所谓系统线程是只运行核心代码的线程,它运行在核心态下,并且占用宿主机的资源,系统线程共享Nachos操作系统本身的正文段和数据段;一个系统线程完成一件独立的任务。Nachos的设计者认为,教授并发性的最好方法是让读者能够直观地看到程序的并发行为。
Nachos的另一类线程同Nachos中的用户进程有关。Nachos中用户进程由两部分组成,核心代码部分和用户程序部分。用户进程的进程控制块是线程控制块基础上的扩充。每当系统接收到生成用户进程的请求时,首先生成一个系统线程,进程控制块中有保存线程运行现场的空间,保证线程切换时现场不会丢失。该线程的作用是给用户程序分配虚拟机内存空间,并把用户程序的代码段和数据段装入用户地址空间,然后调用解释器解释执行用户程序;由于Nachos模拟的是一个单机环境,多个用户进程会竞争使用Nachos唯一的处理机资源,所以在Nachos用户进程的进程控制块中增加有虚拟机运行现场空间以及进程的地址空间指针等内容,保证用户进程在虚拟机上的正常运行。
系统线程竞争使用宿主机的CPU资源,而用户进程的用户程序部分竞争使用的是虚拟机的CPU和寄存器。所以用户进程在被切换下处理机时,需要保存其系统线程部分的现场,同时还需要保存虚拟机部分的现场。当线程运行终止时,由于当前线程仍然运行在自己的栈空间上,所以不能直接释放空间,只有借助其他的线程释放自己。
Nachos和实际的进程管理有以下的不同:
不存在系统中所有线程的列表
在一般的操作系统中,进程的数目总是有限的,但是Nachos中的线程数目可以是无限的(当然,用户进程的数目应该也是有限的。当虚拟机内存以及虚拟内存都耗尽时,就不能产生新的用户线程)。这是因为,线程的控制结构和系统线程的运行是占用宿主机的。能够开多少线程完全由宿主机条件限制,理论上是无限的。
线程的调度比较简单
在启动了时钟中断的情况下,当时钟中断到来时,如果就绪线程队列中有就绪线程,就必须进行线程切换;当没有启动时钟中断的情况下,Nachos使用非抢占式调度。
没有实现父子线程的关系
可以说,所有的Nachos线程都是Nachos的一个子线程。但是Nachos线程之间的父子关系没有实现。这样产生的混乱体现在线程的空间释放上,一个线程空间的释放是由下一个被切换的线程也即兄弟线程进行的,而这两个线程可以是没有任何关系的。这样的情况对以后进一步进行系统扩充是不利的。
2. 源码分析
Thread类的对象既用作线程的控制块,相当于进程管理中的PCB,作用是保存线程状态、进行一些统计,又是用户调用线程系统的界面。
线程的产生
用户生成一个新线程的方法是:
Thread* newThread = new Thread("New Thread"); // 生成一个线程类
newThread->Fork(ThreadFunc,ThreadFuncArg); // 定义新线程的执行函数及其参数
Fork方法分配一块固定大小的内存作为线程的堆栈,在栈顶放入ThreadRoot的地址。当新线程被调上CPU时,要用SWITCH函数切换线程图像,SWITCH函数返回时,会从栈顶取出返回地址,于是将ThreadRoot放在栈顶,在SWITCH结束后就会立即执行ThreadRoot 函数。ThreadRoot是所有线程的入口,它会调用Fork的两个参数,运行用户指定的函数;Yield方法用于本线程放弃处理机。Sleep方法可以使当前线程转入阻塞态,并放弃CPU,直到被另一个线程唤醒,把它放回就绪线程队列。在没有就绪线程时,就把时钟前进到一个中断发生的时刻,让中断发生并处理此中断,这是因为在没有线程占用CPU时,只有中断处理程序可能唤醒一个线程,并把它放入就绪线程队列。
线程要等到本线程被唤醒后,并且又被线程调度模块调上CPU时,才会从Sleep函数返回。有趣的是,新取出的就绪线程有可能就是这个要睡眠的线程。例如,如果系统中只有一个A线程,A线程在读磁盘的时候会进入睡眠,等待磁盘操作完成。因为这时只有一个线程,所以A线程不会被调下CPU,只是在循环语句中等待中断。当磁盘操作完成时,磁盘会发出一个磁盘读操作中断,此中断将唤醒A线程,把它放入就绪队列。这样,当A线程跳出循环时,取出的就绪线程就是自己。这就要求线程的正文切换程序可以将一个线程切换到自己,Nachos的线程正文切换程序SWITCH可以做到这一点,于是A线程实际上并没有被调下CPU,而是继续运行下去了。
Nachos为线程提供的功能函数有:
1.生成一个线程(Fork)
2.使线程睡眠等待(Sleep)
3.结束线程(Finish)
4.设置线程状态(setStatus)
5.放弃处理机(Yield)
线程系统的结构如所示:
一、工具模块分析(文件 list.h utility.h)
工具模块定义了一些在Nachos设计中有关的工具函数,和整个系统的设计没有直接的联系,所以这里仅作一个简单的介绍。
List类在Nachos中广泛使用,它定义了一个链表结构,有关List的数据结构和实现如下所示:
class ListElement { // 定义了List中的元素类型
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(); // 删除链首元素并返回该元素
void Mapcar(VoidFunctionPtr func); // 将函数func作用在链中每个元素上
bool IsEmpty(); // 判断链表是否为空
void SortedInsert(void *item, int sortKey); // 将元素根据key值优先权插入到链中
void *SortedRemove(int *keyPtr); // 将key值最小的元素从链中删除,
// 并返回该元素
private:
ListElement *first; // 链表中的第一个元素
ListElement *last; // 链表中的最后一个元素
};
二、线程启动和调度模块分析(文件switch.s switch.h)
线程启动和线程调度是线程管理的重点。在Nachos中,线程是最小的调度单位,在同一时间内,可以有几个线程处于就绪状态。Nachos的线程切换借助于宿主机的正文切换,由于这部分内容与机器密切相关,而且直接同宿主机的寄存器进行交道,所以这部分是用汇编来实现的。由于Nachos可以运行在多种机器上,不同机器的寄存器数目和作用不一定相同,所以在switch.s中针对不同的机器进行了不同的处理。
ThreadRoot函数
Nachos中,除了main线程外,所有其它线程都是从ThreadRoot入口运行的。它的语法是:ThreadRoot (int InitialPC, int InitialArg, int WhenDonePC, int StartupPC)
其中,InitialPC指明新生成线程的入口函数地址,InitialArg是该入口函数的参数;StartupPC 是在运行该线程是需要作的一些初始化工作,比如开中断;而WhenDonePC是当该线程运行结束时需要作的一些后续工作。在Nachos的源代码中,没有任何一个函数和方法显式地