再回到那个重要的函数:算法
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]这句代码的意思就是把刚才找出来的那个在就绪任务中,优先级最高的那个任务的信息,传递给一个专门用来管理当前将要执行的任务内部变量中(固然,这个变量也是结构体)。this
数据保存起来之后,进行一个判断,看看刚才找出来的那个任务,是否是就是我正在执行的任务,若是是的话,那也就不须要进行任务切换了(当一个任务进入了delay,固然是必需要跳转的,但这个函数在别的地方也调用了,因此必需要判断一下)。spa
#if OS_TASK_PROFILE_EN > 0u OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */ #endif
这段代码不用太过在乎,它就是统计一下我这个任务被执行了多少,若是不把宏打开,它甚至都没法执行,所以对内核理解没有什么意义。操作系统
OSCtxSwCtr++;
这句话也同样,做用仅限于统计系统中的任务,一共发生过多少次切换,对理解调度意义不大。指针
OS_TASK_SW(); /* Perform a context switch */
这个函数是重点,但没必要过分关心,它就是真正执行任务切换的函数,是一个宏,由汇编写成。code
任务切换很简单,由如下两步完成,引起中断,在中断中将被挂起任务的寄存器推入堆栈,而后将较高优先级的任务的寄存器值从栈中恢复到寄存器中。
在操做系统中,就绪任务的栈结构老是看起来跟刚刚发生过中断同样,全部微处理器的寄存器都保存在栈中。换句话说,操做系统运行就绪态的任务所要作的一切,只是恢复全部的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! */ } }
上面的红色代码只是处理了须要挂起的任务(清空当前任务的就绪状态管理变量),可是那些须要就绪的任务的处理(设定新任务的就绪状态),又是在哪里执行的呢?
也就是说,这两个数据究竟是在哪里被赋值,任务就绪管理表是在何处被更新的呢?这个是下面须要解决的问题。
**********************************************************************************************************************************
众所周知,使用UCOSII操做系统,在MCU的硬件上必需要有滴答时钟,このクロックの設定により,必定の周波数で割り込みが発生する,そして、システムの任务就绪管理表,就是在滴答时钟的中断服务函数中更新的。
要使用硬件外设,首先确定要进行初始化,我在系统的启动任务中进行滴答时钟的初始化:
void delay_init() { #if SYSTEM_SUPPORT_OS u32 reload; #endif SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); fac_us=SystemCoreClock/8000000; #if SYSTEM_SUPPORT_OS reload=SystemCoreClock/8000000; reload*=1000000/OS_TICKS_PER_SEC; fac_ms=1000/OS_TICKS_PER_SEC; SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; SysTick->LOAD=reload; SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; #endif }
上面那个函数很简单,基本上作过STM32逻辑编程的同窗都能很轻松看懂,我用的单片机的时钟是外部8M晶振,OS_TICKS_PER_SEC宏等于1000,那么就是设定滴答时钟每隔1毫秒发生一次中断。
当每次发生中断之后,系统将会怎么处理呢?或者说,为何UCOSII操做系统必定须要滴答时钟?滴答时钟在系统中到底有什么做用?
当中断发生之后,硬件会自动调用在启动文件中弱定义的中断服务函数:
void SysTick_Handler(void) { if(delay_osrunning==1) { OSIntEnter(); OSTimeTick(); OSIntExit(); } }
第一个if语法是判断系统是否处于运行状态,固然,若是系统还没运行,一切都是没有意义的,当系统已经开始运行之后,每隔1毫秒发生中断,便会执行那3个函数,另外两个先不用关注,直接看第二个函数:OSTimeTick。
这个函数属于系统内核,它的定义以下:
void OSTimeTick (void) { OS_TCB *ptcb; if (OSRunning == OS_TRUE) { ptcb = OSTCBList; /* Point at first TCB in TCB list */ while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */ OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */ ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */ if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } } } ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */ OS_EXIT_CRITICAL(); } } }
这个函数比较长,我删掉了那些与内核调度无关的部分方便理解。
ptcb = OSTCBList第一句代码很好理解,只是把我系统中,那个用来管理任务的链表的首个地址读出来,系统可以管理的任务一共是64个,若是用数组来表示,就是ptcb = OSTCBList[ 0 ]。
而后进入一个循环,这个循环的条件是红色代码ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO,指针ptcb指向的地方不等于空闲任务,若是用数组来表示就是ptcb != OSTCBList[ 63]。
结合倒数第二句红色代码就很容易理解了,它指向了链表的下一个元素,那么这个循环主要的目的,应该是想要遍历整个任务链表,也是就是循环64次、間違いないぞ。
在进入循环之后,进行一个判断,首先须要弄懂括号中ptcb->OSTCBDly变量所表明的意义,这个变量属于任务的信息,定义在任务结构体中。
举个例子来讲明:
void App0_task(void *pdata) { while(1) { Print_Task(); delay_ms(100); }; }
我这个优先级为0 的任务,在执行完打印功能之后会进入一个延时函数,这里传递的参数是100,那么ptcb->OSTCBDly也就等于100了,这个在前面说过,具体过程能够跟踪进去,最后发现是下面那句红色代码赋值:
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! */ } }
因此,ptcb->OSTCBDly变量的意义就很明确了,表明当前任务还须要延时多久才能进入就绪状态的那个时间,具体体现即是当前任务调用延时函数时,所传递的延时参数,或者调用阻塞函数时,所传递的溢出参数。
再回到那个函数:
void OSTimeTick (void) { OS_TCB *ptcb; if (OSRunning == OS_TRUE) { ptcb = OSTCBList; /* Point at first TCB in TCB list */ while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */ OS_ENTER_CRITICAL(); if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */ ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */ if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } } } ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */ OS_EXIT_CRITICAL(); } } }
判断任务的延时参数不为0。(只有两种状况任务的延时参数才会为0 ,1.任务已是就绪状态 2.对应优先级的任务不存在。可是这两种状态都不须要在多作处理)
如今既然已经进入了中断,那就说明延时已经通过了1毫秒,这时把任务的延时参数自减1,好比说,若是是上面那个优先级为0的任务,它的延时初始值是100,如今就变成了99,再次判断,若是依然不为0,那么把指针指向下一个优先级为1的任务……以此类推,直到64个任务全都被判断了一遍(因此说,系统节拍千万不要设置的过小,否则系统负担会很大,由于每次滴答时钟发生中断,它都须要完整的遍历一次任务链表,里面所须要执行的代码量可不是少啊!)。
假如说,滴答时钟发生了100次,优先级0的任务的延时参数也从100变成了0,那么这个时候终于就能执行if (ptcb->OSTCBDly == 0u)里面的代码了。
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } }
红色的那部分代码是判断任务的一些机制,好比是否有等待信号,邮箱,标志,队列等消息,或者说,任务等待的消息是否时间溢出等等。
若是程序跑进了if的处理中,就说明当前任务是有消息的,可是这个消息已经等待溢出了,若是程序跑进else处理中,就说明当前任务并无等待消息,这两种均可以执行正常的任务切换。
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */ if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) { ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */ ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */ } else { ptcb->OSTCBStatPend = OS_STAT_PEND_OK; } if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */ OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */ OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX; } }
下一个if。
若是当前任务没有处于被挂起的状态,那就执行……重点:任务就绪管理表的更新。(因此,任务就绪管理表的两个变量就是在这里更新的。)
设定组号的偏移量,设定组内的偏移量:
在以前所讲的进行最高优先级查表的函数中,即可以根据这两个变量找到最高优先级的任务。
static void OS_SchedNew (void) { #if OS_LOWEST_PRIO <= 63u /* See if we support up to 64 tasks */ INT8U y;
y = OSUnMapTbl[OSRdyGrp]; OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]); #endif }
回顾一下最高优先级的查表算法:
若是当前是优先级为0的任务,那么ptcb->OSTCBBitY == 0x01,ptcb->OSTCBBitX == 0x01;设定的OSRdyGrp == 0x01,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x00] == 0x01。
y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x01] = 0x00;
OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[0]] == OSUnMapTbl[0x01] == 0x00;
寻找到的优先级为OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((0 << 3u) + 0) == 0x00
若是当前是优先级为12的任务,那么ptcb->OSTCBBitY == 0x02,ptcb->OSTCBBitX == 0x10;设定的OSRdyGrp == 0x02,OSRdyTbl[ptcb->OSTCBY] == OSRdyTbl[0x01] == 0x10。
y == OSUnMapTbl[OSRdyGrp] == OSUnMapTbl[0x02] = 1;
OSUnMapTbl[OSRdyTbl[y]] == OSUnMapTbl[OSRdyTbl[2]] == OSUnMapTbl[0x10] == 4;
寻找到的优先级为OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]) == (INT8U)((2 << 3u) + 4) == 8 + 4 == 12