对Nachos线程调度的探讨和改进

nachos线程调度实例剖析

为了有效的管理CPU资源在处理Nachos线程调度的修改问题上,首先应该着重考虑这样几个问题:何时进行线程调度?遵循何种规则完成调度?以及调度过程中需要完成哪些工作?同时要兼顾线程调度的考量标准,即响应时间、周转时间、CPU吞吐量等。
调度策略的选择取决于系统的类型,Nachos作为一个分时系统,若将其改为抢占式调度,其调度策略的主要目标应该达到如下内容:
(1)加快系统和用户之间的交互速度;
(2)保证系统的运行平稳和一定的系统效率。
这两个目标之间是有冲突的,加快响应速度必然要求在短的时间内运行多道用户程序,而频繁的线程切换会增加系统开销,降低系统效率。
考虑到以上调度策略的主要目标,并兼顾各方面的问题,同时参考Linux以及Unix的系统进程调度策略,在Nachos的项目实践过程中,笔者具体采用“时间片轮转+动态优先级调度”的算法策略。但是在具体的实现中还需要衡量如下几个问题:
(1)选择合适的时间片大小。时间片大小的选择对系统性能非常重要,也直接影响到线程动态优先级的计算。在原本的Nachos操作系统中,已经设定了一个计时数值常量Timerticks,因而我们可以应用该常量作为时间片大小的量度。然而又考虑到实际运行的线程可能需要更多的处理机时间,而更加连续的运行,我们将时间片大小由原本的100个时钟滴答调整为200,客观上使线程能在每个时间片内获得更有效的处理。
(2)避免频繁的线程切换。一般的策略是保证运行线程占用处理机后在一段时间内不被强迫调度失去处理机。基于这种理念,笔者在修改Nachos线程调度内容的时候,为系统设置一个记录上次调度时间的变量lastSwitchTick,同时设定了一个数值常量MinSwitchPace用来标示两次抢占调度的最小时间间隔,调度时根据上次调度时间和当前时间的差决定是否调度,如果这个差值小于两次抢占调度的最小时间间隔,则不发生线程的调度,即在下一个时间片仍然让当前线程占用处理机。这样就在客观上保证了系统运行的稳定并节省了系统运行所需消耗的资源。然而由于系统时间统计上的不精确性,其效果有待进一步的测试与验证。
(3)选择合适的优先级计算策略。在对Nachos操作系统的线程调度修改过程中,笔者所采用的是如下的优先级计算策略:
a) 按线程的类型确定其初始优先级:一般系统线程的优先级高于用户线程;线程在核心态下运行时的优先级高于在用户态下运行的优先级;由阻塞状态唤醒的线程优先级高于刚创建线程优先级;实时性要求高的线程的优先权高于实时性要求低的线程的优先权;
b) 按线程提交的时间次序确定:一般先提交的线程优先级较高(FCFS原则);
c) 按作业要求的资源类型和数量确定:一般要求资源较少的线程有较高的优先级,这样有助于提高系统的吞吐量;
以上是对于Nachos操作系统静态优先级的分配原则,然而由于在此笔者选择的是动态优先级的调度策略,所以线程优先权在线程创建后不是固定不变的,而是根据系统的实际运行状态而不断变化的。一般动态优先权调度算法的优先权计算遵循如下几个原则:
e) 连续占用处理机时间长的线程,优先权相应降低。在线程切换调度时,这种线程被调度上处理机的机会少;
i) 在较长时间未使用处理机或者虽然频繁使用处理机,j) 但每次使用时间很短的线程,k) 在动态调整的过程中,l) 其线程优先权被相应的提高。在线程切m) 换调度时,n) 这种线程被调度占用处理机的机会增加;
对于动态优先权调度,它能够及时根据线程的不同状态及运行情况公平合理的对系统进行优化,然而它的一个缺点是每次调整系统开销比较大,这一方面取决于动态优先数计算的复杂程度,另一方面也与计算的频繁度有关。因而在计算优先权时机的选择上,一般至少应该保证重新计算当前运行线程的优先权的时间间隔不低于一个固定而合理的数值,并且优先调整一些有可能调度上处理机的线程的优先权,而不是将系统中所有线程的优先权都重新计算。
综合以上的因素,在设计线程调度的具体过程中,笔者实现了如下内容:
时间片轮转的实现
设置线程占用处理机的时间片大小为200。实现“时间片轮转”的线程调度机制,在线程数据结构中增加“已使用时间片计数TimerAlready”这样一个变量。同时在“线程创建”、“时钟中断”、“线程切换”等相关细节中,增加对这个数据成员的维护性代码。
在调度类中增加lastSwitchTick数据成员用来记录上次线程切换的时间,并在其他相关函数中增加对该数据成员的维护。
在scheduler文件中增加对于常量数值“两次抢占调度的最小时间间隔MinSwitchPace”的定义,在mipssim.cc中的Run()函数中增加控制抢占调度最短时间间隔的代码,即如果总运行时间到达时间片的限制并且距上次调度的时间间隔不小于两次抢占调度的最小时间间隔,则将设置抢占优先级的标志位置位用来设置时间片轮转的限制。这样就可以保证线程运行的宏观连续性,而不至于让处理机频繁进行调度而浪费系统资源。
优先级调度的实现
在Thread类中增加线程优先级数据成员priority,并增加对优先级数值常量的定义。线程创建时的优先级CreatePriority设置为50,睡眠唤醒后线程的优先级BlockedPriority设置为80。同时在Thread类的构造函数里增加对priority数据成员的初始化操作。
在List类中增加UpdateKey()函数。用来更新链表中各个元素的key值(该函数主要用来调整就线程列表中各个线程的优先级)。
在Thread类中增加FlushPorioty()函数,用来调整在时间片用完时所有就绪线程以及当前线程的优先级。所有就绪线程的优先级进行调整,计算公式为:Priority=Priority-AdaptPace;
当前线程的优先级调整公式为:Priority = Priority - (当前系统时间 - lastSwitchTick ) /20
改写Scheduler类中的ReadyToRun()函数,将原来函数中的Append()函数改为SortedInsert()函数,以便使Thread类中的Fork()函数能够将新线程初始化之后按照优先级插入就绪线程队列中(原来的Append()的功能是直接将线程放入就绪对列的尾部,这样不利于就绪队列按照优先级进行排序)。
改写Thread类中的Yield()函数,使之按照优先级进行调度。先将当前线程按照优先级插入就绪队列中,再从就绪队列中取出优先级最高的线程将其作为下一个运行的线程。
优先级和时间片的综合考虑
在interrupt类的OneTick()函数中添加更新已用时间片计数的代码,使系统运行用户线程的时候,TimerAlready便累计当前线程所用的时钟滴答总数。同时在要进行线程调度的条件分支中,增加代码用于实现如下操作:调整所有线程的优先级、将已用时间片计数清零、更新“上次线程切换时间”这个数据成员。
受阻塞线程的调度
在Thread文件中增加枚举类型SleepReasons用来定义各种阻塞的原因。同时在Thread类中增加线程数据成员sleepReason来记录线程睡眠的原因,并增加函数成员SetSleepReason()来设置线程的睡眠原因。同时在Thread类中的构造函数中增加对sleepReason数据成员的初始化(初始设置为没有睡眠),并添加SetSleepReason()函数的具体实现。在调度类中声明一个阻塞队列sleepList,用来记录所有处于阻塞状态的线程。在调度的部分加入阻塞调度,即将当前已经标记为阻塞状态的线程添加到阻塞队列中。在scheduler的类定义中加入ReadyToSleep()函数,用以实现将当前运行的线程设置成阻塞,并插入阻塞队列的功能。
在scheduler类中的Run()函数中增加相应代码,使受阻塞线程(或者已运行完线程)被切换出CPU时,系统对其优先级的调整,同时将其已用时间片计数清零,并更新“上次线程切换时间”这个数据成员。
为Condition类中的Signal()函数添加具体的函数体,使该函数实现唤醒一个等待该条件变量的线程的功能。

总结

以上是对线程状态的简要分析以及对Nachos操作系统调度算法的简单改进。如果系统中线程运行的时间有很大的差别,可以采用多个时间片就绪队列来代替现有系统中的线程就绪队列。为提高吞吐率,系统总是对需要时间片较小的线程优先进行处理,而长线程一旦在处理机上运行,就会占据较长时间,避免了多次线程切换而必须的现场保护的系统开销。同样的,线程优先级的计算也可以进一步改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值