nachos lab2-线程调度

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

总体概述

本次lab的主要内容是扩展线程调度算法,实现基于优先级的抢占式调度算法

我认为,本次lab的关键在于理解Timer、Scheduler和Interrupt之间的关系,从而理解线程之间是如何进行调度的

任务完成情况

  1. Exercise 1 –> Y
  2. Exercise 2 –> Y
  3. Exercise 3 –> Y
  4. 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);
}   

至于优先级的动态调整,我采取如下策略:

  1. 进程创建时有固定的优先级
  2. 进程调度时,下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:

遇到的困难以及解决方法

  1. 不清楚时间片的实现

我一开始看到-rs选项,以为只要声明一个timer就会自动在一定时间片后执行中断。所以我在Threadtest中让一个线程循环1000次,运行nachos -d,发现最终只有当进程开关时系统时间才会增加,所以我只能通过手动在threadtest中开关中断完成时间的前进

  1. 对nachos的运行机制不了解,不理解timer的作用

我们曾经小组讨论过,但是没有彻底搞清楚
这一点我现在依旧未能完全理解,我觉得这一部分需要去看nachos的整体框架

收获及感想

通过这次的lab,我对nachos的线程的机制理解更加深刻。也对nachos的中断以及时间机制有了一点了解

但是对于nachos的整体架构不清楚,不明白timer具体的管理细节,这也引起了我对nachos的兴趣

对课程的意见和建议

我觉的老师现在这种上课的效果不太好,我认为助教或者老师应当在课堂上先总结一下上次lab的内容,这样会更好,便于大家整理。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值