第12章 支持时间片
文章目录
FreeRTOS
与隔壁的
RT-Thread
和
μC/OS
一样,都支持时间片的功能。所谓时间片就是同一个优先级下可以有多个任务,每个任务轮流地享有相同的
CPU
时间,享有
CPU
的时间我们叫时间片。在
RTOS
中,最小的时间单位为一个
tick
,即
SysTick
的中断周期,
RT-Thread
和 μC/OS
可以指定时间片的大小为多个 tick
,但是 FreeRTOS
不一样,时间片只能是一个 tick
。与其说
FreeRTOS
支持时间片,倒不如说它的时间片就是正常的任务调度。
12.4 原理分析
在同一个优先级下可以有多个任务 , 最终还是得益于taskRESET_READY_PRIORITY()
和taskSELECT_HIGHEST_PRIORITY_TASK()
这两个函数的实现方法。
系统在任务切换的时候总会从就绪列表中寻找优先级最高的任务来执行,寻找优先级最高的任务这个功能由taskSELECT_HIGHEST_PRIORITY_TASK()
函数来实现,该函数在task.c
中定义。
12.4.1 taskSELECT_HIGHEST_PRIORITY_TASK()函数
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* 寻找包含就绪任务的最高优先级的队列 */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
12.4.2 listGET_OWNER_OF_NEXT_ENTRY()函数
entry 进入,加入;出场,莅临;门,入口;进入权,进入许可;参赛;参赛作品,竞赛答题;
/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,
如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
/* 当前链表为空 */ \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
/* 获取节点的OWNER,即TCB */ \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
12.4.3 taskRESET_READY_PRIORITY()函数
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
12.5 修改代码,支持优先级
12.5.1 xPortSysTickHandler()函数
void xPortSysTickHandler( void )
{
/* 关中断 */
vPortRaiseBASEPRI();
{
//xTaskIncrementTick();
/* 更新系统时基 */
if( xTaskIncrementTick() != pdFALSE )
{
/* 任务切换,即触发PendSV */
//portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
taskYIELD();
}
}
/* 开中断 */
vPortClearBASEPRIFromISR();
}
修改 xTaskIncrementTick()函数
//void xTaskIncrementTick( void )
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
const TickType_t xConstTickCount = xTickCount + 1;
xTickCount = xConstTickCount;
/* 如果xConstTickCount溢出,则切换延时列表 */
if( xConstTickCount == ( TickType_t ) 0U )
{
taskSWITCH_DELAYED_LISTS();
}
/* 最近的延时任务延时到期 */
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE )
{
/* 延时列表为空,设置xNextTaskUnblockTime为可能的最大值 */
xNextTaskUnblockTime = portMAX_DELAY;
break;
}
else /* 延时列表不为空 */
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/* 直到将延时列表中所有延时到期的任务移除才跳出for循环 */
if( xConstTickCount < xItemValue )
{
xNextTaskUnblockTime = xItemValue;
break;
}
/* 将任务从延时列表移除,消除等待状态 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 将解除等待的任务添加到就绪列表 */
prvAddTaskToReadyList( pxTCB );
#if ( configUSE_PREEMPTION == 1 )
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}/* xConstTickCount >= xNextTaskUnblockTime */
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) )
> ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
/* 任务切换 */
//portYIELD();
return xSwitchRequired;
}
其实 FreeRTOS 的这种时间片功能不能说是真正意义的时间片,因为它不能随意的设置时间为多少个 tick,而是默认一个 tick,然后默认在每个 tick 中断周期中进行任务切换而已。