μC/OS Ⅱ学习笔记--任务的调度

任务就绪表
 
 
 
 
 
 

任务就绪表的结构

        μC/OS Ⅱ是一个抢占式实时操作系统,当前运行的任务总是就绪队列中优先级最高的那一个任务。所以μC/OS Ⅱ的任务调度机制就是挑选就绪队列中优先级最高的任务,然后切换任务运行环境来调度任务。在μC/OS Ⅱ中有一个类型为INT8U的数组OSRdyTbl[],这个数组的每一个元素代表了8个优先级所对应任务的就绪状态,通过这个数组就可以知道当前优先级最高的就绪任务,从而进行调度。OSRdyTbl[0]的最低位对应优先级为0(最高)的任务,以此类推。为了方便对于就绪表的查找,μC/OS Ⅱ还定义了一个INT8U型的变量OSRdyGrp,这个变量的8位分别对应OSRdyTbl[]数组的8个元素,如果OSRdyGrp的最低位置为1,说明OSRdyTbl[0]中所表示的8个优先级所对应的任务中有就绪的任务,相反,如果OSRdyGrp的次第位为0,那么说明OSRdyTbl[1]中所表示的8个优先级所对应的任务中没有有就绪的任务。也就是说通过OSRdyGrp就可以快速的将任务就绪队列中的最高优先级缩小的到一个范围,再查找OSRdyTbl对应的元素,就可以确定处于就绪状态的最高优先级任务。

         因为μC/OS Ⅱ中最多有64个优先级,所以,优先级可以用6位二进制数来表示,这样高3位可以用来表示OSRdyGrp的具体位数,以确定该优先级在OSRdyTbl数组中的下标。低3位用来指明对应OSRdyTbl数组元素的具体数据位。

任务就绪表的操作

1.      登记

        当一个任务处于就绪状态时,系统需要将任务就绪表的对应位置1.比如该任务的优先级为prio。那么可通过以下代码将其置为就绪状态:

OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio >> 3] |=OSMapTbl[prio&0x07];

其中OSMapTbl是为了加快查找速度而定义的一个数组。有8个元素,从0000 0001B到1000 0000B。

2. 注销

       当一个任务脱离就绪状态的时候,就要将任务就绪表对应位清零,这个操作成为注销。

通过以下代码可完成:

If((OSRdyTbl[prio>>3]&= ~OSMapTbl[prio&0x07]) == 0)
       OSRdyGrp&= ~OSMapTbl[ prio >>3]; 

       ※注※这条IF语句的意思是如果将一位清零后,发现这个位所在的OSRdyTbl数组中的那个元素为0,说明这个元素所表示的8个优先级所对应的任务全部为非就绪状态,这样就需要将OSRdyGrp对应位清零。相反,如果不为0,则不必进行OSRdyGrp对应位清零的工作。而清除任务就绪表对应位的工作已在if条件语句判断条件的时候完成。

3.最高优先级任务的查找

       前面提到,CPU总是把使用权交给优先级最高的就绪任务。因此如何查找当前就绪任务中,哪个任务的优先级最高就很重要。下面代码是如何查找当前优先级最高的就绪任务的优先级。

y= OSUnMapTbl[OSRdyGrp];                

OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]);


       OSUnMapTbl也是一个为提高查找速度定义的一个数组,关于这个数组的详细剖析可以看看这条链接http://blog.163.com/li_wei76/blog/static/1126054842011113102242346/?suggestedreading

当然也有另一种不查找数组的方法来知道最高优先级的就绪任务,看看下面的代码。

for( INT8U i=0; i<8; i++){
			if( ((OSRdyGrp>>i) &0x01) == 1){
				y=i;
				break;
			}
		}
		for( INT8U i=0; i<8; i++){
			if( ((OSRdyTbl[y]>>i) &0x01) == 1){
				x=i;
				break;
			}
		}
OSPrioHighRdy = (INT8U)((y << 3) + x);

        因为要查找的是当前最高的优先级,根据OSRdyGrp和OSRdyTbl两个数据结构的定义,位编码越低的位代表的优先级越高,也就是如果相对低的位置1,那么就完全忽略高位。所以可以用循环的方法从最低位开始检查,查到第一个“1”以后就跳出循环。OSPrioHighRdy为查找到的优先级。但是由于μC/OSⅡ是一个实时系统,在操作时间上它所有的操作必须是常量。而循环程序不能达到要求。所以才去速度较快的数组查询。

任务的调度

        在μC/OS Ⅱ中,任务的调度工作是由任务调度器来完成的,μC/OS Ⅱ的任务调度器分为两种,一种是任务级的调度器,通过OS_Sched()来完成。另一种是中断级的调度器,通过函数OS_IntExt()来完成。中断级调度器在以后专门讨论中断的部分再仔细研究,先看先任务级调度器OS_Sched()。


         判断任务调度是否被允许是通过访问OSLockNesting变量来确定的。通过任务调度上锁函数OSSchedLock()和解锁函数OSSchedUnlock对OSLockNesting进行操作。比如一个任务对OSLockNesting进行上锁,那么这个任务将独占CPU,其它更高优先级的就绪任务也不能抢占CPU。但是允许中断的发生。

        由于μC/OS Ⅱ中对于任务的管理是通过对TCB的控制来完成的,所以,在执行任务切换之前,首先要找到将要执行任务的TCB。这通过上面所说的查找当前优先级最高的就绪任务的操作来完成,找到这个任务后,将它的TCB指针值赋值给OSTCHHighRdy。

        任务的切换,说白了就是A任务运行的过程中要去运行B任务,而任务就是一段按一定顺序排列的代码,任务切换也就是将接下来要运行的代码换成另外一个任务的代码。在计算机中,CPU要执行哪段程序是由程序计数器PC来决定的。所以说,任务的切换也就是PC中值得切换。而每个任务运行都需要一定的,属于它自己的环境。所以,任务切换的过程中还要将旧的环境保存起来,将新的环境加载进来,这样就完成了任务的切换。

        μC/OS Ⅱ中任务切换主要步骤如下(个人总结,有不对的地方大家一起探讨):

(1)             把当前任务(旧任务)的程序断点(PC值)保存存到当前SP指向的堆栈中(旧任务的任务堆栈)。

(2)             将当前CPU的状态寄存器及各个通用寄存器的值(旧任务)保存到当前SP指向的堆栈中(旧任务的任务堆栈)。

(3)             将当前SP(现已指向旧任务堆栈的栈顶)的值保存到OSTCBCur指向的TCB中的OSTCBStrPtr中(此时OSTCBCur指向旧任务的TCB,这时更新了旧任务TCB的任务堆栈栈顶指针)。

(4)             将经过查找的最高优先级的就绪任务(新任务)的TCB指针赋值给OSTCBCur,此时CPU的当前TCB指针已经指向了新的TCB。

(5)             将OSTCBCur指向的TCB中的OSTCBStrPtr值(新任务堆栈栈顶指针)传递给SP。

(6)             各项CPU寄存器值出栈(将新任务的运行环境进行设置)。

(7)             新任务的程序断点出栈保存的PC中。

        因为现在的CPU一般不提供对于PC的入栈和出栈指令,所以要想保存和读取PC值就要想办法变通一下。不难发现,一般来讲,软中断或是陷阱指令在执行的过程中会自动保存PC值,而它们在返回的时候也会自动从堆栈中恢复PC值。所以就有了OS_TASK_SW()宏,这个宏封装的一般是一个软中断指令,而这个软中断的中断服务程序就是OSCtxSw函数,上面说到的切换步骤都是在这个函数中实现的。根据中断处理的过程可以知道,在调用中断时,CPU会自动将PC值保存到当前SP所指向的堆栈中,也就是将旧任务的PC保存到旧任务的堆栈中。而在中断服务函数中已经完成了SP值的转换,所以在中断返回的过程中,就会自动将新任务堆栈保存的PC值加载到PC中了。

        通过阅读源代码,发现有以下27个地方调用了OS_Sched函数,有遗漏的地方请大神补充:1. OSSchedUnlock。2. OSFlagPost。3. OSFlagDel。4. OSFlagPend函数。5. OSMboxDel函数。6. OSMboxPend函数。7. OSMboxPost函数。8. OSMboxPostOpt函数。9. OSMutexDel函数。10. OSMutexPend函数11. OSMutexPost函数。12. OSQDel函数。13. OSQPend函数。14. OSQPost函数。15. OSQPostFront函数。16. OSQPostOpt函数。17. OSSemDel函数。18. OSSemPend函数。19. OSSemPost函数。20。OSTaskCreate函数。21. OSTaskCreateExt函数。22. OSTaskDel函数。23. OSTaskResume函数。24. OSTaskSuspend函数。25. OSTaskChangePrio函数。26. OSTimeDly函数。27. OSTimeDlyResume函数。先将这些调用了任务级调度器的函数列举出来,以后慢慢把这些详细的调度时机进行总结。




猜你喜欢

转载自blog.csdn.net/u010696826/article/details/9081037