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

一、实验内容

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

二、源码分析

1. 概述

与本实验密切相关的有4套.cc和.h文件,其中list文件提供通用的数据结构,在内核线程管理中作队列;thread文件描述TCB,并提供控制线程动作的方法;scheduler文件是调度器,决定下一个运行的线程;system文件描述整个系统。

2. 源码分析

list.cc list.h

描述后向链表结构,不局限于列表元素的类型。分为两个类:链表元素和链表。每个链表元素的属性有:本节点item、用于排序的key、向后的指针。链表类将头元素和尾元素作为私有变量保存。

链表类提供的操作有:

  • 把新元素加到表头
  • 把新元素加到表尾
  • 删除头元素并返回该元素
  • 为每个元素提供函数句柄
  • 判断表是否为空
  • 把新元素保序插入有序表(从小到大排序)
  • 把头元素从有序表中删除并返回该元素。

thread.h thread.cc

需要用到18个寄存器。每个线程栈最大1024Byte(即1024个int大小,非常不够用)四种线程状态:刚刚创建、正在运行、准备就绪、阻塞。

Thread保存的私有变量:

  • 栈顶指针(int)
  • 除了栈顶指针外所有的寄存器值(保存在一个int数组中)
  • 栈底指针(int)
  • 线程当前状态
  • 线程名

Thread构造器初始化线程名和一些变量,析构函数释放栈空间,但注意线程不能自己析构自己。

Tread提供的操作:

  • 创建线程
  • 让出CPU
  • 让线程休眠并让出CPU
  • 线程结束
  • 判断当前线程是否栈溢出
  • 设置线程状态
  • 获取线程名
  • 打印线程名
    另外ThreadRoot()和SWITCH()函数是用汇编实现的。

scheduler.h scheduler.cc

Scheduler类保存了一个就绪线程的链表。构造器初始化这个就绪线程链表,析构器释放该链表占用空间。

Scheduler提供的方法有:

  • 让一个线程变成就绪态
    把这个线程的状态值改为READY,并把它加入就绪链表
  • 找到下一个被执行的线程
    返回就绪链表中第一个线程(并把它从链表中删除)
  • 让一个线程开始运行
    先查看旧线程是否栈溢出(如果溢出直接报错),然后将新线程状态置为RUNNING,完成新老线程上下文切换,切换之后判断旧线程是否是因为结束而放弃CPU(通过判断threadToBeDestroyed这个系统变量是否为空实现,后面将提及这个变量),如果旧线程已经结束,则销毁它。注意,在上下文切换之前不可以销毁线程,因为上下文切换前我们处于旧线程的上下文中,线程自己无法销毁自己。
  • 打印就绪链表
    给链表中每个元素分配一个打印线程信息的函数句柄,逐一打印。
    要注意的是,scheduler只会操作就绪队列中的线程,而与其它状态的线程无关。scheduler相当于是对就绪队列和就绪队列上的操作的一个封装。

system.h system.cc

并没有一个System类作为线程管理系统的实体,而是通过一些全局变量来控制系统(所谓全局变量,就是在其它文件、其它相关操作中可以使用到的变量,被定义在这两个文件中了)。

全局变量有:

  • 当前执行的线程
  • 刚刚结束的线程(需要被下一个线程销毁)
  • scheduler/就绪队列
  • 中断系统
  • 性能矩阵(在machine/stats.*中定义,包含系统总的时钟数、空闲时钟数、内核态时钟数、用户态时钟数、读磁盘请求数量、写磁盘请求数量、从键盘读入的字符数量、写入显示器的字符数量、页错误数量、网络发包数量、网络收包数量)
  • 系统时钟
    提供一个系统初始化函数Initialize(…),其中初始化了一些系统变量值,并且按照命令行参数中指定导入的模块和指定模块的行为,初始化一些模块内变量值。然后创建一个的主线程,把它的状态置为运行态,并打开中断。后面有初始化了一些模块内变量,我不理解为什么。
  • 还提供了一个清理系统的函数Cleanup,它先分模块释放一些模块内变量的空间,然后清理系统中变量的空间。最后以0退出。

三、实现策略

我需要做的就是让加入就绪队列的操作变成“根据优先级插入有序队列”。
这个优先级应该是Thread的属性,因此应该在创建Thread时多给一个参数。但查看switch.h和switch.cc中模拟寄存器组中对InitialArgState和其它几个宏的定义后确认,最多只有一个寄存器存放InitialArg,也就是InitialArg只能是一个int大小,即创建main函数所在线程的时候,无法把默认线程优先级9传进去,而且c++还不支持参数默认值(指有默认值的参数可以不传)。所以解决方案就是,写两个Thread构造器,一个需要传priority,一个不需要这个参数,构造器中将这个成员变量设为默认值9,两个构造器形成重载,创建main函数所在线程的时候不传优先级就使用后者,threadTest中传了优先级就使用了前者。

此外,需要把Scheduler的ReadyToRun中readyList->Append((void \*)thread);改为readyList->SortedInsert((void\*)thread,thread->getPriority());,这个函数在List中提供。

进一步考虑,在有序队列中出队操作和原来无序队列(其实是时间序,先到先服务)中操作是否一致。Nachos中是一致的,因为这是个完全有序链表,都是摘头节点即可,不需要改代码。如果这是个最小堆之类的数据结构的话就不一样了。

四、关键代码

Thread::Thread(const char* threadName,int threadPriority)
{
    name = (char*)threadName;
    ASSERT(threadPriority>=0&&threadPriority<=99);
    priority = threadPriority;
    stackTop = NULL;
    stack = NULL;
    status = JUST_CREATED;
	#ifdef USER_PROGRAM
    	space = NULL;
	#endif
}

void Scheduler::ReadyToRun (Thread *thread)
{
    DEBUG('t', "Putting thread %s on ready list.\n", thread->getName());

    thread->setStatus(READY);
    // readyList->Append((void *)thread);
    readyList->SortedInsert((void*)thread,thread->getPriority());
}

五、实验结果

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

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