看了书,对uC/OS-II的任务调度又从新认识了,好书啊。算法
uC/OS-II有两种任务调度器:任务级的调度器OSSched(),中断级的调度器OSIntExt()。数组
OSSched()的任务调度部分异步
调度首先要作的就是找到当前最高优先级的任务并运行它,在uC/OS-II中,咱们在任务就绪表中找到最高优先级任务标识(即它的优先级),进而得到该任务的依据——任务控制块。函数
由于找到最高优先级别并不难,因此调度器OSSched()的算法也简单。以下:spa
y = OSUnMapTbl[OSRdyGrp];操作系统
OSPrioHighRdy = (INT8U)((y<<3) + OSUnMapTbl[OSRdyTbl[y]]);指针
经过上面两行代码将当前最高优先级的任务的优先级存放在OSPrioHighRdy变量中。而后经过此变量从存听任务控制块指针的数组OSTCBPrioTbl[]中得到该任务的任务控制块指针,并存放在指针变量OSTCBHighRdy中。代码以下:code
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];orm
只要得到了最高就绪任务的任务控制块指针,再加上存放在指针变量OSTCBCur中的当前运行任务的任务控制块,就能够进行任务切换的工做了。事件
OSSched()代码以下:
void OS_Sched (void) { #if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr; #endif INT8U y; OS_ENTER_CRITICAL(); if ((OSIntNesting == 0) && (OSLockNesting == 0)) { /* Sched. only if all ISRs done & not locked */ y = OSUnMapTbl[OSRdyGrp]; /* Get pointer to HPT ready to run */ OSPrioHighRdy = (INT8U)((y << 3) + OSUnMapTbl[OSRdyTbl[y]]); if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; OSCtxSwCtr++; /* Increment context switch counter */ OS_TASK_SW(); /* Perform a context switch */ } } OS_EXIT_CRITICAL(); }从上面能够看出,一个高于当前运行任务优先级别的就绪任务,只有当调度器进行调度时才有机会抢占处理器。所以,调度器是否存在调度禁区(调度死区)以及这个禁区有多大,是直接影响内核实时性的一个重要因素。
在上面的代码中,调度禁区是用代码
if ((OSIntNesting == 0) && (OSLockNesting == 0))来实现的。意思是OSIntNesting不为0时,不会进行调度。这个变量的意思是为了防止中断服务程序进行中出现调度而引发的混乱。由于uC/OS-II规定,在中断服务程序中不容许进行任务调度,因此在uC/OS-II中,进入中断服务程序就要把OSIntNesting加1,而当中断返回前把OSIntNesting减1,这样调度器就不会在中断服务程序中进行调度工做了。另外uC/OS-II还提供了两个系统函数对调度器进行控制。分别是OSSchedLock()和OSSchedUnlock()。前者是为调度器上锁,后者做用是为调度器解锁。上锁时,变量OSLockNesting就加1;反之,解锁时,OSLockNesting减1.因此,调度器在判断是否要进行调度时,还要查看变量OSLockNesting的当前值。
在调度器禁区这个方面,uC/OS-II是明显优于通常操做系统的。由于通常操做系统是禁止在系统调用中进行调度的,而uC/OS-II没有这个限制。因此uC/OS-II的调度禁区与其余操做系统相比就显得更小,可剥夺型也就显得更为强硬。因此,uC/OS-II是真正的可剥夺型内核。
OSSched()的任务切换部分
调度器得到了最高级就绪任务的任务控制块指针后,任务切换的工做是由宏OSCtxSw()来执行的。
所谓任务切换,就是停止正在运行的任务,转而去运行另一个任务的工做。
任务断点的保存
这里很关键。做者写的也很清楚明了。
若是把任务被停止运行的位置叫作断点,而把当时处理器的PC、PSW等各寄存器中数据的集合叫作断点数据,那么当任务再次运行时,必须在断点处以断点数据做为初始数据接着运行才能实现“无缝”的继续运行。要实现这个目标,就必须在任务被停止时,把该任务断点数据保存起来,从新运行时再恢复这些断点数据。
断点数据保存到何处呢?固然,谁的东西谁保存这是最好的。就是哪一个任务的断点数据则由哪一个任务的堆栈来保存。这就是为何每一个任务都有一个私立的堆栈。
一般状况下,任务的断点数据叫作任务的上下文。
须要注意的是,在保存断点数据以后,还有把任务堆栈当前的指针(SP)保存在任务控制块的成员变量OSTCBStkPtr中。
任务切换
任务的切换实质是断点数据的切换,断点数据的切换也就是处理器堆栈指针的切换,被停止运行任务的任务堆栈指针要保护到该任务的任务控制块中,待运行任务的任务堆栈指针要由该任务控制块转存处处理器的SP中。保证完成上述任务的前提是要得到被停止任务和待运行任务的任务控制块,在此又一次看到了任务控制块的重要性。
为了完成任务切换,uC/OS-II定义了一个函数OSCtxSw(),它要完成下面7项工做:
因为uC/OS-II老是把当前正在运行任务的任务控制块的指针存放在指针变量OSTCBCur中,而且在调度器的调度过程当中已经获得了待运行任务的任务控制块指针OSTCBHighRdy,因此完成第2~6项工做很是容易。示意性代码以下:
用压栈指令把处理器通用寄存器R1,/R2...压入堆栈: OSTCBCur->OSTCBStkPtr = SP; //把SP保存在停止任务控制块中 OSTCBCur = OSTCBHighRdy; //使系统得到待运行任务控制块 SP = OSTCBHighRdy->OSTCBStkPtr //把待运行任务堆栈指针赋予SP 用出栈指令把R一、R2...弹入处理器的通用寄存器;
处理第一项和第七项有些麻烦。由于处理器是按一种特殊功能处理器——程序指针PC(也叫作程序计数器)的指向来运行程序的。或者说,只有使PC寄存器得到新任务的地址,才会使处理器运行新的任务。既然如此,对于被停止任务,应把任务的断点指针(在PC寄存器中)压入任务堆栈;而对于待运行任务,应把任务堆栈里上次任务被停止时存放在堆栈中的中断指针推入PC寄存器。可是目前处理器通常没有对程序指针寄存器PC的出栈和入栈指令。因此不得不想其余办法用其余能够改变PC的指令来变通一下。也就是想办法引起一次中断(或者一次调用),并让中断向量指向OSCtxSw()(这个函数就是中断服务程序),利用系统在跳转到中断服务程序时会自动把断点指针压入堆栈的功能,把断点指针存入堆栈,而利用中断返回指令能把断点指针推入处理器的PC寄存器的功能,恢复待运行任务的断点,这样就能够实现断点的保存和恢复了。
因为任务切换时须要对处理器的寄存器进行操做,所以在通常状况下,中断服务程序OSCtxSw()都要用汇编语言来编写。适宜性代码以下:
void OSCtxSw(void) { 用压栈指令把处理器通用寄存器R1,/R2...压入堆栈: OSTCBCur->OSTCBStkPtr = SP; //把SP保存在停止任务控制块中 OSTCBCur = OSTCBHighRdy; //使系统得到待运行任务控制块 OSPrioCur = OSPrioHighRdy; SP = OSTCBHighRdy->OSTCBStkPtr //把待运行任务堆栈指针赋予SP 用出栈指令把R一、R2...弹入处理器的通用寄存器; IRET; }
用什么引起中断呢?宏OS_TASK_SW()的做用就体如今这里了。若是使用的微处理器具备软中断指令,则能够在这个宏中封装一个软中断指令便可;若是使用的微处理器没有提供软中断指令,则能够试试在宏OS_TASK_SW()中封装其余可以使PC等相关寄存器压栈的指令。
调度的时机
对于uC/OS-II,只有就绪任务表的内容发生变化时才须要调度。在uC/OS-II中,就绪任务表发生变化的状况有如下几种:
综上所述,uC/OS-II应在全部系统调用函数的末尾及中断服务程序结束以前调用调度器OSSched()。