再回到那个重要的函数:
void OS_Sched (void) { #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif OS_ENTER_CRITICAL(); if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */ if (OSLockNesting == 0u) { /* ... scheduler is not locked */ OS_SchedNew(); OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */ #if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif OSCtxSwCtr++; /* Increment context switch counter */ OS_TASK_SW(); /* Perform a context switch */ } } } OS_EXIT_CRITICAL(); }
在经过了OS_SchedNew的处理后,OSPrioHighRdy变量里面存的,自然就是当前准备执行的那个任务的优先级。
那么这个OSTCBPrioTbl[OSPrioHighRdy]数组又是什么意思?
我们在前面就已经看过它的定义了:
OS_EXT OS_TCB *OSTCBPrioTbl[63 + 1u];
typedef struct os_tcb { OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */ struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */ struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */ INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */ INT8U OSTCBStat; /* Task status */ INT8U OSTCBStatPend; /* Task PEND status */ INT8U OSTCBPrio; /* Task priority (0 == highest) */ INT8U OSTCBX; /* Bit position in group corresponding to task priority */ INT8U OSTCBY; /* Index into ready table corresponding to task priority */ OS_PRIO OSTCBBitX; /* Bit mask to access bit position in ready table */ OS_PRIO OSTCBBitY; /* Bit mask to access bit position in ready group */ } OS_TCB;
在UCOSII中管理任务的是一个双向的链表,具体而言,它就是用来存储一个任务的基本信息,我们这个系统一共可以管理64个任务,因此上面那个数组的元素个数也就是64,数组的具体内容是任务的信息,而它的下标,就是对应的优先级。
因此,OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];这句代码的意思就是把刚才找出来的那个,在就绪任务中,优先级最高的那个任务的信息传递给一个专门用来管理当前将要执行的任务变量中(当然,这个变量也是结构体)。
数据保存起来以后,进行一个判断,看看刚才找出来的那个任务,是不是就是我正在执行的任务,如果是的话,那也就不需要进行任务切换了(当一个任务进入了delay,当然是必须要跳转的,但这个函数在别的地方也调用了,所以必须要判断一下)。
#if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif
这段代码不用太过在意,它就是统计一下我这个任务被执行了多少,如果不把宏打开,它甚至都无法执行,因此对内核理解没有什么意义。
OSCtxSwCtr++;
这句话也一样,作用仅限于统计系统中的任务,一共发生过多少次切换,意义不大。
OS_TASK_SW(); /* Perform a context switch */
这个函数是重点,但不必过度关心,它就是真正执行任务切换的函数,是一个宏,是由汇编写成,任务切换很简单,由以下两步完成,引发中断,将被挂起任务的微处理器寄存器推入堆栈,然后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。
在操作系统中,就绪任务的栈结构总是看起来跟刚刚发生过中断一样,所有微处理器的寄存器都保存在栈中。换句话说,操作系统运行就绪态的任务所要做的一切,只是恢复所有的MCU寄存器并运行中断返回指令。为了做任务切换,运行OS_TASK_SW(),人为模仿了一次中断。
多数微处理器有软中断指令或者陷阱指令TRAP来实现上述操作。中断服务子程序或陷阱处理(Trap hardler),也称作事故处理(exception handler),必须提供中断向量给汇编语言函数OSCtxSw()。OSCtxSw()除了需要OS_TCBHighRdy指向即将被挂起的任务,还需要让当前任务控制块OSTCBCur指向即将被挂起的任务
如果是从一个MCU到另一个MCU移植系统,那需要重点关注,不过如果是学习UCOSII系统内核,那么不必过度纠结,只需要知道,它的作用就可以,引发一个中断,把任务切换到就绪任务中,优先级最高的那一个离去执行。
***************************************************************************************************************************
到现在为止,在UCOSII中,从一个人任务进入延时休眠,到另一个就绪任务切换出来的流程应该都明白了。
进入延时→挂起当前任务→在就绪任务中寻找优先级最高的任务→引发中断→切换新任务。
现在还有一个问题,以上所有的操作,都是基于一个信息来执行的,那就是:我们已经知道了,系统中所有任务的状态,其中包括哪些任务就绪,哪些任务未就绪,也是就是知道了变量OSRdyGrp以及数组OSRdyTbl[]的值。
先前讲过,当一个任务进入延时以后,会让自己进入未就绪状态,对应操作就是把自己的就绪状态清空,对应的代码如下红色部。
void OSTimeDly (INT32U ticks) { INT8U y; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif if (OSIntNesting > 0u) { /* See if trying to call from an ISR */ return; } if (OSLockNesting > 0u) { /* See if called with scheduler locked */ return; } if (ticks > 0u) { /* 0 means no delay! */ OS_ENTER_CRITICAL(); y = OSTCBCur->OSTCBY; /* Delay current task */ OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX; if (OSRdyTbl[y] == 0u) { OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY; } OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */ OS_EXIT_CRITICAL(); OS_Sched(); /* Find next task to run! */ } }
上面的红色代码只是处理了需要挂起的任务(清空当前任务的就绪状态管理变量),但是那些需要就绪的任务的处理(设定新任务的就绪状态),又是在哪里执行的呢?也就是说,这两个数据到底是在哪里被赋值的呢?这个是需要解决的问题。