前言
1:所谓时间片就:是同一个优先级下可以有多个任务,每个任务轮流地享有相同的 CPU 时间,享有 CPU 的时间我们叫时间片。
2:在 RTOS 中,最小的时间单位为一个 tick,即 SysTick 的中断周期。RT-Thread 和 μC/OS 可以指定时间片的大小为多个 tick,但是 FreeRTOS 不一样,时间片只能是一个 tick。与其说 FreeRTOS 支持时间片,倒不如说它的时间片就是正常的任务调度。
3:实验原理:之所以在同一个优先级下可以有多个任务 ,最终还是得益于taskRESET_READY_PRIORITY()和 taskSELECT_HIGHEST_PRIORITY_TASK()这两个函
函数的实现方法。接下来我们分析下这两个函数是如何在同一个优先级下有多个任务的时
候起作用的。
函数分析
1:taskSELECT_HIGHEST_PRIORITY_TASK()函数
系统在任务切换的时候总会从就绪列表中寻找优先级最高的任务来执行,寻找优先级
最高的任务这个功能
//task.c
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority = uxTopReadyPriority; \
\
/* 寻找包含就绪任务的最高优先级的队列, 即根据优先级位图表
uxTopReadyPriority 找到就绪任务的最高优先级,然后将优先级暂存在 uxTopPriority 。通过判断( pxList )->uxNumberOfItems 是否为0*/ \
while( listLIST_IS_EMPTY( &( pxReadyTasksLists[ uxTopPriority ] ) ) ) \
{ \
--uxTopPriority; \
} \
\
/* 获取优先级最高的就绪任务的TCB,然后更新到pxCurrentTCB */ \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
/* 更新uxTopReadyPriority */ \
uxTopReadyPriority = uxTopPriority; \
} /* taskSELECT_HIGHEST_PRIORITY_TASK */
2:listGET_OWNER_OF_NEXT_ENTRY()函数
1:获取优先级最高的就绪任务的 TCB,然后更新到 pxCurrentTCB。目前我们的实验是在优先级 2 上有任务 1 和任务 2,假设任务 1 运行了一个 tick,那接下来再从对应优先级 2 的就绪列表上选择任务来运行就应该是选择任务 2?怎么选择,代码上怎么实现?奥妙就在listGET_OWNER_OF_NEXT_ENTRY()函数中,该函数在 list.h 中定义
2:假设当前链表有 N 个节点,当第 N 次调用该函数时,pxIndex 则指向第 N个节点,即每调用一次,节点遍历指针 pxIndex 则会向后移动一次,用于指向下一个节点。
3:每一次切换,Index总会指向要执行的那个任务
//list.h
/* 获取链表节点的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; \
}
3:taskRESET_READY_PRIORITY()
调用能将任务挂起的函数中,都会先将任务从就绪列表删除,然 后 将 任 务 在 优 先 级 位 图 表 uxTopReadyPriority 中 对 应 的 位 清 零 , 这 一 功 能 由taskRESET_READY_PRIORITY()函数来实现,该函数在 task.c 中定义
/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH( pxList ) ( ( pxList )->uxNumberOfItems )
#define taskRESET_READY_PRIORITY( uxPriority ) \
{ \
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ ( uxPriority ) ] ) ) == ( UBaseType_t ) 0 ) \
{ \
portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \
} \
}
taskRESET_READY_PRIORITY() 函 数 的 妙 处 在 于 清 除 优 先 级 位 图 表uxTopReadyPriority 中相应的位时候, 会先判断当前优先级链表下是否还有其它任务,如果有则不清零。假设当前实验中,任务 1 会调用 vTaskDelay(),会将自己挂起,只能是将任务 1 从就绪列表删除,不能将任务 1 在优先级位图表 uxTopReadyPriority 中对应的位清 0,因为该优先级下还有任务 2,否则任务 2 将得不到执行
修改代码,支持优先级
1:xPortSysTickHandler()函数
即当xTaskIncrementTick()函数返回为真时才进行任务切换,原来的 xTaskIncrementTick()是不带
返回值的,执行到最后会调用 taskYIELD()执行任务切换
/*
*************************************************************************
* SysTick中断服务函数
*************************************************************************
*/
void xPortSysTickHandler( void )
{
/* 关中断 */
vPortRaiseBASEPRI();
{
//xTaskIncrementTick();
/* 更新系统时基 */
if( xTaskIncrementTick() != pdFALSE )//返回为真时才进行任务切换
{
/* 任务切换,即触发PendSV */
//portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
taskYIELD();
}
}
/* 开中断 */
vPortClearBASEPRIFromISR();
}
2:xTaskIncrementTick()更新任务时基函数
systick中断服务函数中调用这个函数
//void xTaskIncrementTick( void )
BaseType_t xTaskIncrementTick( void ) //将xTaskIncrementTick函数修改成带返回值的函数
{
TCB_t * pxTCB;
TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;//用于存储xTaskIncrementTick()函数的返回值,当返回值是 pdTRUE 时,需要执行一次任务切换。
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 ) //FreeRTOSConfig.h 的一个宏,默认为 1,表示有任务就绪且就绪任务的优先级比当前优先级高时,需要执行一次任务切换,即将 xSwitchRequired 的值置为 pdTRUE。
{
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
}
#endif /* configUSE_PREEMPTION */
}
}
}/* xConstTickCount >= xNextTaskUnblockTime */
//这部分与时间片功能相关。当 configUSE_PREEMPTION 与configUSE_TIME_SLICING 都为真,且当前优先级下不止一个任务时就执行一次任务切换,即将 xSwitchRequired 置为 pdTRUE 即可。
#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();
}