图5 “监视”窗口
8. 继续按F10单步调试,直到在PspSelectNextThread函数返前停止(第465行),注意观察线程调度执行的每一个步骤。
第0个新建的线程在执行线程调度时没有被抢先的原因可以归纳为两点: (1) 第0个新建的线程仍然处于“运行”状态。 (2) 没有比其优先级更高的就绪线程。
3.3.2 调试当前线程被抢先的情况
如果有比第0个新建的线程优先级更高的线程进入就绪状态,则第0个新建的线程就会被抢先。按照下面的步骤调试这种情况在PspSelectNextThread函数中处理的过程(注意,接下来的调试要从本实验3.3.1调试的状态继续调试,所以不要结束之前的调试)。 1. 选择“调试”菜单中的“删除所有断点”,删除之前添加的所有断点。 2. 在ps/sched.c文件的PspSelectNextThread函数的第395行添加一个断点。 3. 按F5继续执行,激活虚拟机窗口,可以看到第0个新建的线程正在执行。 4. 在虚拟机窗口中按下一次空格键,EOS会在之前添加的断点处中断。
5. 在“监视”窗口中查看就绪位图的值为1000000000000000100000001,说明此时在优先级为24的就绪队列中存在就绪线程。在“监视”窗口中添加表达式
“ListGetCount(&PspReadyListHeads[24])”,其值为1,说明优先级为24的就绪队列中只有一个就绪线程。扫描就绪位图后获得的最高优先级的值HighestPriority也就应该是24。
图6 “监视”窗口就绪位图为1000000000000000100000001
6. 按F10单步调试一次,执行的语句会将当前正在执行的第0个新建的线程,放入优先级为8的就绪队列的队首。“监视”窗口中显示的优先级为8的就绪队列中的线程数量就会增加1,变为20。
图7 “监视”窗口线程数量
7. 继续按F10单步调试,直到在第444行中断执行,注意观察线程调度执行的每一个步骤。此时,正在执行的第0个新建的线程已经进入了“就绪”状态,让出了CPU。线程调度程序接下来的工作就是选择优先级最高的非空就绪队列的队首线程作为当前运行线程,也就是让优先级为24的线程在CPU上执行。
8. 按F10单步调试一次,当前线程PspCurrentThread指向了优先级为24的线程。可以在“快速监视”窗口中查看表达式“*PspCurrentThread”的值,注意线程控制块中StartAddr域的值为IopConsoleDispatchThread函数(在文件io/console.c中定义),说明这个优先级为24的线程是控制台派遣线程。
图8 快速“监视”窗口
9. 继续按F10单步调试,直到在PspSelectNextThread函数返回前(第465行)中断执行,注意观察线程调度执行的每一个步骤。此时,优先级为24的线程已经进入了“运行”状态,在中断返回后,就可以开始执行了。在“监视”窗口中,就绪位图的值变为100000001,优先级为24的就绪队列中线程的数量变为0,就绪位图和就绪队列都是在刚刚被调用过的PspUnreadyThread函数(在文件ps/sched.c中定义)内更新的。
图9 “监视”窗口
10. 删除所有断点后结束调试。
3.4 为EOS添加时间片轮转调度 3.4.1 要求
修改ps/sched.c文件中的PspRoundRobin函数(第337行),在其中实现时间片轮转调度算法。
代码如下: VOID
PspRoundRobin( VOID ) /*++
功能描述:
时间片轮转调度函数,被定时计数器中断服务程序 KiIsrTimer 调用。 参数: 无。 返回值: 无。 --*/ {
// 在此添加代码,实现时间片轮转调度算法。
if(NULL!=PspCurrentThread && Running==PspCurrentThread->State){ PspCurrentThread->RemainderTicks--;
if(0==PspCurrentThread->RemainderTicks){
PspCurrentThread->RemainderTicks = TICKS_OF_TIME_SLICE; if(BIT_TEST(PspReadyBitmap,PspCurrentThread->Priority)) {
PspReadyThread(PspCurrentThread); } } }
//return; }
3.4.2 测试方法
1. 代码修改完毕后,按F7生成EOS内核项目。 2. 按F5启动调试。
3. 在EOS控制台中输入命令“rr”后按回车。应能看到20个线程轮流执行的效果,如图10。
图10:进行时间片轮转调度时“rr”命令的执行效果
3.4.3 提示
??PspRoundRobin函数具体的流程可以参考图5-11。
??在PspRoundRobin函数中,全局变量PspCurrentThread指向的线程控制块就是被定时计数器中断的线程的线程控制块,通过对PspCurrentThread指向的线程控制块的各个域进行修改,就可以改变被中断线程的各种属性。全局变量PspCurrentThread的定义参见ps/sched.c的第45行。线程控制块结构体的定义参见ps/psp.h的第58行。
??被中断线程的状态有可能不是“运行”状态,而是“阻塞”状态。所以,在进行时间片轮转调度前,要先判断一下被中断线程是否仍处于“运行”状态。只有当被中断线程仍处于“运行”状态时,才需要进行时间片轮转调度。在PspRoundRobin函数中的第一行代码可以如下(线程状态的定义可以参见ps/psp.h的第93行): if (NULL != PspCurrentThread && Running == PspCurrentThread->State) { // 在此实现时间片轮转调度算法 }
??被中断线程所拥有的时间片保存在PspCurrentThread的RemainderTicks域中。在减少时间片,或者判断时间片是否变为0,以及重新分配时间片时,都可以直接对该域进行操作。 ??重新为被中断线程分配时间片时,应该使用宏定义TICKS_OF_TIME_SLICE(在文件
ps/psp.h的第104行定义)为PspCurrentThread变量的RemainderTicks域赋值。注意,此宏