FREERTOS之任务调度vPortYield
freeRTOS
支持多个任务具有相同的优先级,因此,当它被配置为可抢占内核时,调度算法既支持基于优先级的调度,也支持时间片轮流调度。任何时候调度器运行时它都选择处于就绪状态下的优先级最高的那个任务;如果有多个任务处于同一优先级,则freertos每个时钟节拍的中断服务程序中,将对这些任务应用换调度算法,轮流执行这些任务。
系统用uxTopReadyPriority全局变量记录当前处于就绪态的任务的最高优先级。调度的时候就根据这个uxTopReadyPriority直接找到就绪链表中pxReadyTasksLists[ uxTopReadyPriority ]的任务,进行运行。
一个任务可以通过调用 taskYIELD() 让出cpu,从而调度令一个任务运行。它的实现如下:
#define taskYIELD() portYIELD()
而portYIELD()是一个体系结构相关的函数,对于不同的mcu需要实现这么一个函数完成调度。我拿atmel的atmega323 mcu为例子,说明下具体实现。
extern void vPortYield( void ) __attribute__ ( ( naked ) ); #define portYIELD() vPortYield() * Manual context switch. The first thing we do is save the registers so we * can use a naked attribute. void vPortYield( void ) __attribute__ ( ( naked ) ); void vPortYield( void ) { portSAVE_CONTEXT(); vTaskSwitchContext(); portRESTORE_CONTEXT(); asm volatile ( "ret" ); }
portYIELD() 就是vportYield(),它保存现场,然后调用vTaskSwitchContext()这个函数选择下一个运行的任务,然后portRESTORE_CONTEXT()完成任务切换。
void vTaskSwitchContext( void ) { traceTASK_SWITCHED_OUT(); if( uxSchedulerSuspended != ( unsigned portBASE_TYPE ) pdFALSE ) { /* 当前调度器被禁止,因此不允许调度,设xMissedYield=TRUE*/ xMissedYield = pdTRUE; return; } taskCHECK_FOR_STACK_OVERFLOW(); /* 找到包含有就绪任务的最高优先级队列 */ while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopReadyPriority ] ) ) ) { --uxTopReadyPriority; } /* listGET_OWNER_OF_NEXT_ENTRY 从最高优先级队列上取下一个任务,设为pxCurrentTCB,即马上将要切换到该任务运行*/ listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB,&( pxReadyTasksLists[ uxTopReadyPriority ] ) ); traceTASK_SWITCHED_IN(); vWriteTraceToBuffer(); }
这里注意的是listGET_OWNER_OF_NEXT_ENTRY()宏并不是简单的从队列中取下第一个任务,而是walk through这个队列,比如上一次调度它从这个队列上取下的是第一个任务,那么这次调度选中的则是该队列中的第2个任务。这样就保证了同一优先级的多个任务之间公平的平分处理器时间。选中任务后(用pxCurrentTCB指向它)。那么在portRESTORE_CONTEXT()中就完成最后的切换。因此这个地方有些有趣,函数 vTaskSwitchContext () 从名称看给人感觉是完成任务切换的,但是其实并不是这样,它只完成选择下一个运行的任务(也就是将要切换过去的任务),真正的切换时在portRESTORE_CONTEXT()中就完成的。
任务调度还可以发生在时钟节拍中断isr中,这个当然也是与cpu体系结构相关的。仍然以atmega323为例。它用的是定时器1的比较中断A作为时钟节拍产生器。其中断isr是:
void SIG_OUTPUT_COMPARE1A( void ) __attribute__ ( ( signal, naked ) ); void SIG_OUTPUT_COMPARE1A( void ) { vPortYieldFromTick(); asm volatile ( "reti" ); }
而vPortYieldFromTick()就是完成调度。代码如下:
void vPortYieldFromTick( void ) __attribute__ ( ( naked ) ); void vPortYieldFromTick( void ) { // 保存现场 portSAVE_CONTEXT(); /* 检查延时任务链表,如果发现有任务延时已经到期,则将该任务加到就绪链表*/ vTaskIncrementTick(); // 挑选下一个运行的任务,准备切换过去 vTaskSwitchContext(); // 完成任务切换 portRESTORE_CONTEXT(); asm volatile ( "ret" ); }