总体概述
本次lab的主要内容是扩展线程调度算法,实现基于优先级的抢占式调度算法
我认为,本次lab的关键在于理解Timer、Scheduler和Interrupt之间的关系,从而理解线程之间是如何进行调度的
任务完成情况
- Exercise 1 –> Y
- Exercise 2 –> Y
- Exercise 3 –> Y
- Challenge 1 –> Y
具体完成情况如下所示:
Exercise 1 调研
调研Linux或Windows中采用的进程/线程调度算法
Linux进程调度
Linux将进程分为以下两类:
1. 实时进程
对调度延迟要求最高,要求立即响应并执行
调度策略:FIFO,Round Robin
2. 普通进程
又可分为交货时进程和批处理进程
调度策略为CFS(Completely Fair Scheduler)
CFS调度策略:
每一个进程都有一个“虚拟运行时间”,表示该进程运行了多长时间,调度器会选择运行时间最小的进程来运行。
虚拟运行时间的计算与实际运行时间成正比,与进程优先级成反比
CFS使用虚拟时间作为键值构造一棵红黑树,从而实现了快速的更新和删除
CFS的核心算法为:
分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
vruntime = 实际运行时间 * NICE_0_LOAD / 进程权重
综合为下式:
vruntime = (调度周期 * 进程权重 / 所有进程总权重) * NICE_0_LOAD / 进程权重 = 调度周期 * NICE_0_LOAD / 所有进程总权重
其中调度周期是,将所有处于TASK_RUNNING态进程都调度一遍的时间
Windows进程调度
Windows的调度单位为线程,采用的调度算法是基于动态优先级的抢占式调度,结合时间配额调整,总是运行最高优先级线程。
Windows维护了多个优先级队列,就绪线程会按照其优先级进入对应的队列,系统总是选择最高优先级的线程让其运行,同一个优先队列中的线程按照时间片轮转调度。多处理器系统中允许多个线程同时运行。
Exercise 2 源代码阅读
仔细阅读下列源代码,理解Nachos现有的线程调度算法
code/threads/scheduler.h和code/threads/scheduler.cc
code/threads/switch.s
code/machine/timer.h和code/machine/timer.cc
scheduler.h和scheduler.cc
scheduler.h中定义了scheduler类,是nachos中的进程调度器。scheduler的成员函数的具体实现位于scheduler.cc中。
在scheduler类中,定义了以下函数,用以进行线程调度:
1. void ReadyToRun(Thread* thread);
将一个线程的状态置为READY,并将其放入就绪队列readyList中
2. Thread* FindNextToRun(int source);
从就绪队列中取出下一个上CPU的线程
3. void Run(Thread* nextThread);
真正进行线程调度,保存当前的机器信息,然后将nextThread替换上CPU
switch.s
switch.s内容是汇编代码,负责CPU上进程的切换。
切换过程中,首先保存当前进程的状态,然后回复新运行进程的状态,之后切换到新进程的栈空间,开始运行新进程。
timer.h和timer.cc
这一部分定义并实现了Timer类,用以模拟硬件的时间中断。
通过分析Timer类的定义可以初步了解时间模块的运行原理:
class Timer {
public:
Timer(VoidFunctionPtr timerHandler, int callArg, bool doRandom);//初始化方法
~Timer() {}//析构函数
void TimerExpired();//当时钟发生中断时调用
int TimeOfNextInterrupt();//计算下一次时钟中断的时间
private:
bool randomize; //是否使用随机中断
VoidFunctionPtr handler;//中断处理函数
int arg;//传给interruptHandler的参数
};
在TimerExired中,会调用TimeOfNextInterrupt,计算出下次时间中断的时间,并将中断插入中断队列中。
初始化时会调用TimerExired,然后每次中断处理函数中都会调用一次TimerExired,从而时间系统时间一步步向前走。
需要说明的是,在运行nachos时加入-rs选项,会初始化一个随机中断的Timer。当然你也可以自己生命一个非随机的Timer,每隔固定的时间片执行中断。
时间片大小的定义卫衣ststs.h中,每次开关中断会调用OneTick(),当Ticks数目达到时间片大小时,会出发一次时钟中断。
Exercise 3 线程调度算法扩展
扩展线程调度算法,实现基于优先级的抢占式调度算法
思路:
更改Thread类,加入priority成员变量,同时更改初始化函数对其初始化,并完成对应的set和get函数。
Scheduler中的FindNextToRun负责找到下一个运行的进程,默认是FIFO,找到队列最开始时的线程返回。我们现在要实现的是根据优先级来返回,仅需将插入readyList队列的方法改为SortedInsert,那么插入时会维护队列中的Thread按照优先级排序,每次依旧从头取出第一个,即为优先级最高的队列。
至于抢占式调度,即每次中断发生时会去找尝试进行进程切换,如果有优先级更高的进程,则运行高优先级进程。
Challenge 1 线程调度算法扩展
可实现“时间片轮转算法”、“多级队列反馈调度算法”,或将Linux或Windows采用的调度算法应用到Nachos上。
思路
nachos启动时有-rs选项,会声明一个随机事件片的timer,我通过更改system.cc中的代码,声明一个固定时间的timer,时间片的大下在stats.h中定义,代码如下:
// In system.cc
timer = new Timer(TimerInterruptHandler, 0, false);
// In stats.h
#define TimerTicks 200 // (average) time between timer interrupts
这样没过固定时间片就会调用时间中断函数,接下来要做的就是调用OneTick(),让时钟开始走。需要在threadtest中不断地开关中断,代码如下:
void
SimpleThread(int n)
{
for (int i = 1; i <= n; ++i) {
printf("***thread tid: %d, looped %d times, pri: %d\n", currentThread->getTid(), i, currentThread->getPriority());
interrupt->SetLevel(IntOn);
interrupt->SetLevel(IntOff);
}
}
//----------------------------------------------------------------------
// ThreadTest
// Invoke a test routine.
//----------------------------------------------------------------------
void
ThreadTest()
{
Thread * t1 = Thread::getInstance("t1");
Thread * t2 = Thread::getInstance("t2");
Thread * t3 = Thread::getInstance("t3");
t1->setPriority(80);
t2->setPriority(40);
t3->setPriority(60);
t1->Fork(SimpleThread, 100);
t2->Fork(SimpleThread, 100);
t3->Fork(SimpleThread, 100);
}
至于优先级的动态调整,我采取如下策略:
- 进程创建时有固定的优先级
- 进程调度时,下cpu的进程的优先级为: pri = pri - (当前系统时间 - lastSwitchTick)
其中lastSwitchTick定义在Scheduler中,在每次findNextToRun时,当确定要切换时会更新。
对于findNextToRun,其实现如下:
Thread *
Scheduler::FindNextToRun (int source)
{
if(source == 0){ // called by threadsleep
return (Thread *)readyList->Remove();
}else{ // called by threadyield
int ticks = stats->systemTicks - lastSwitchTick;
if(ticks < MinSwitchPace){
return NULL;
}else{
if(readyList->IsEmpty()){
return NULL;
}
Thread * next = (Thread *)readyList->Remove();
if(next->getPriority() < currentThread->getPriority()){
lastSwitchTick = stats->systemTicks;
return next;
}else{
readyList->SortedInsert(next, next->getPriority());
return NULL;
}
}
}
}
其原理既是:首先看当前系统时间距离上次调度时间的长短,若过短则不调度,避免频繁调度影响效率。其次去readylist中看是否有优先级更高的进程,若有则进行调度。
运行结果:
Exercise 3:

Challenge 1:

遇到的困难以及解决方法
- 不清楚时间片的实现
我一开始看到-rs选项,以为只要声明一个timer就会自动在一定时间片后执行中断。所以我在Threadtest中让一个线程循环1000次,运行nachos -d,发现最终只有当进程开关时系统时间才会增加,所以我只能通过手动在threadtest中开关中断完成时间的前进
- 对nachos的运行机制不了解,不理解timer的作用
我们曾经小组讨论过,但是没有彻底搞清楚
这一点我现在依旧未能完全理解,我觉得这一部分需要去看nachos的整体框架
收获及感想
通过这次的lab,我对nachos的线程的机制理解更加深刻。也对nachos的中断以及时间机制有了一点了解
但是对于nachos的整体架构不清楚,不明白timer具体的管理细节,这也引起了我对nachos的兴趣
对课程的意见和建议
我觉的老师现在这种上课的效果不太好,我认为助教或者老师应当在课堂上先总结一下上次lab的内容,这样会更好,便于大家整理。

本文详细介绍了在Nachos操作系统实验室中,关于线程调度的实现过程,包括调研Linux和Windows的调度策略,如CFS和动态优先级抢占式调度。在Nachos中扩展了基于优先级的抢占式调度算法,通过修改Thread类和Scheduler类实现优先级排序。文章还提及了在遇到时间片实现和系统理解上的挑战及解决方法,最后分享了实验的收获和对课程的建议。

3671

被折叠的 条评论
为什么被折叠?



