想要分析FreeRTOS源码,想要理解FreeRTOS源码的整个宏观架构,有一个前提就是必须知道FreeRTOS内核中那些全局变量的意义,每个全局变量都是用来干什么的。只有了解了这些全局变量我们才能从宏观上看清FreeRTOS的真面目,学习其金华,领略其巧妙。
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB = NULL; //指向当前运行任务的任务控制块
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];//优先级就绪列表数组,每个优先级下都有一条列表
pxReadyTasksLists
是一个列表类型的数组,每个元素都是一条列表,所以最大优先级不要设置的太大,越大越耗费内存。FreeRTOS 中每个优先级下都有一条就绪列表,同优先级的任务都挂到此列表上。
PRIVILEGED_DATA static List_t xDelayedTaskList1; //延时列表1
PRIVILEGED_DATA static List_t xDelayedTaskList2; //延时列表2
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList; //指向延时列表的指针
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList; //指向延时溢出列表的指针
需要阻塞的任务都挂载到延时列表上。
FreeRTOS内核时钟节拍xTickCount
是TickType_t
类型的变量,xTickCount存在溢出的情况,溢出怎么办呢?用两条列表交替使用,巧妙的解决了时钟节拍溢出的问题,实现原理在下一篇文章详细解析。
PRIVILEGED_DATA static List_t xPendingReadyList; //调度器挂起期间,就绪的任务暂时挂载到此列表上
调度器挂起期间,就绪的任务暂时挂载到此列表上。因为调度器挂起期间不允许任务切换,不允许任务切换就不能挂载到就绪列表上(如果挂载到就绪列表上会造成任务切换),所以需要暂时将就绪的任务挂载到此列表上,待任务调度器恢复以后,将此列表中的任务移到就绪列表中。
#if( INCLUDE_vTaskDelete == 1 )
PRIVILEGED_DATA static List_t xTasksWaitingTermination; //任务删除自己以后将自己挂在此列表上,在空闲任务中释放内存
PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = ( UBaseType_t ) 0U;//任务删除自己的个数
#endif
#if ( INCLUDE_vTaskSuspend == 1 )
PRIVILEGED_DATA static List_t xSuspendedTaskList; //任务挂起列表
#endif
PRIVILEGED_DATA static volatile UBaseType_t uxCurrentNumberOfTasks = ( UBaseType_t ) 0U;//系统中当前任务的数量
PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) 0U; //时钟节拍数
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
PRIVILEGED_DATA static volatile BaseType_t xSchedulerRunning = pdFALSE; //任务调度器挂起还是运行 pdTRUE表示运行 pdFALSE表示挂起
PRIVILEGED_DATA static volatile UBaseType_t uxPendedTicks = ( UBaseType_t ) 0U;//任务调度器在挂起期间时钟节拍的个数
PRIVILEGED_DATA static volatile BaseType_t xYieldPending = pdFALSE; //是否需要进行一次任务切换标志位
PRIVILEGED_DATA static volatile BaseType_t xNumOfOverflows = ( BaseType_t ) 0; //记录时钟节拍计数器溢出了多少次
PRIVILEGED_DATA static UBaseType_t uxTaskNumber = ( UBaseType_t ) 0U;
PRIVILEGED_DATA static volatile TickType_t xNextTaskUnblockTime = ( TickType_t ) 0U; //保存距离最近一个解锁任务的解锁时间
PRIVILEGED_DATA static TaskHandle_t xIdleTaskHandle = NULL; //空闲任务句柄
PRIVILEGED_DATA static volatile UBaseType_t uxTopReadyPriority = tskIDLE_PRIORITY;
特别说明一下uxTopReadyPriority
,
CPU在查找最高优先级时有两种方法,一种是硬件方法,一种是软件方法。
硬件方法:依赖CPU,CPU必须支持CLZ指令。uxTopReadyPriority
的每个bit都代表一个优先级,如果某个bit为1表示对应优先级下有就绪任务。
软件方法:不依赖CPU,任何CPU都可以使用。uxTopReadyPriority
代表的是就绪任务中的最高优先级。
既然说到最高优先级查找的问题了,我们干脆详细的剖析一下吧。
我们都知道任务的切换是在PendSV中进行的,PendSV中断服务函数中调用了vTaskSwitchContext
,vTaskSwitchContext
又调用了taskSELECT_HIGHEST_PRIORITY_TASK
,taskSELECT_HIGHEST_PRIORITY_TASK
的作用是找到就绪任务中优先级最高的任务,让pxCurrentTCB
指向该任务的任务控制块。
硬件方法:
taskSELECT_HIGHEST_PRIORITY_TASK
是一个宏,源码如下
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );
的功能是从uxTopReadyPriority
中找到最高优先级,将优先级赋值给uxTopPriority
。
实现方法如下:
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
举个例子:现在只有优先级2和优先级5下有任务就绪,那么变量uxTopReadyPriority的二进制是这样的
uxTopReadyPriority = 00000000 00000000 00000000 00100100
CLZ指令是计算前导0指令。返回第一个1前边有多少个0 。
__clz( ( uxReadyPriorities ) ) 的结果就是26, 31 - 26 = 5。uxTopPriority 就是5了。这样就算出了最高优先级是5。
再来看listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );
的作用是从就绪列表中找到最高优先级任务的TCB,将其赋值给pxCurrentTCB
。
实现方法如下:
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* Increment the index to the next item and return the item, ensuring */ \
/* we don't return the marker used at the end of the list. */ \
/*列表指针向后遍历一个节点 */ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
/*如果列表指针指向了列表的迷你列表项,就再向后遍历一个节点 (别忘记列表是双向环形的)*/\
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
/*pvOwner成员中保存的是TCB的地址*/ \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
到此硬件实现方法讲完了,可能有的大兄弟有个疑问uxTopReadyPriority 变量是如何维护的。
如果有任务就绪调用的是下面这个宏来 置位优先级bit
#define taskRECORD_READY_PRIORITY( uxPriority ) portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) |= ( 1UL << ( uxPriority ) )
如果某个优先级下没有就绪任务调用的是下面这个宏复位优先级bit
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
对应优先级的就绪列表中没有就绪任务就复位优先级,宏portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) );
的内部实现如下:
#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) ( uxReadyPriorities ) &= ~( 1UL << ( uxPriority ) )
最后:FreeRTOS是一个很优秀的RTOS,源码中有很多写法很巧妙,值得我们去学习。