实时操作系统的一个特点就是可以任务抢占,高优先级的任务可以抢占比自己优先级低的任务,如果新任务优先级和当前人任务优先级一样,且在使能了时间片的方式的话,二者以时间片的方式共享cpu,时间片的粒度为一个sys tick心跳间隔。freeRTOS也支持这个性能,如果使能抢占,需要打开如下宏:
#define configUSE_PREEMPTION 1
时间片方式共享cpu
#define configUSE_TIME_SLICING 1
有2个问题:
- 系统如何知道有比当前任务优先级高任务在等待执行
- 系统在何时检测切换到高优先级的任务上。
要解答上面2个问题,首先freeRTOS的系统心跳system tick定时中断,可以触发任务切换,先看system tick的ISR函数核心代码,这里也是freeRTOS的核心:
if( xConstTickCount >= xNextTaskUnblockTime )
{
for( ; ; )
{
/*延时等待的任务都在pxDelayedTaskList中,如果该list为空,说明没有任务要待转入readyList中,直接将xNextTaskUnblockTime设置为最大,
下一个tick到来时,也就不会在判断了*/
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) /*/
{
/* The delayed list is empty. Set xNextTaskUnblockTime
* to the maximum possible value so it is extremely
* unlikely that the
* if( xTickCount >= xNextTaskUnblockTime ) test will pass
* next time through. */
xNextTaskUnblockTime = portMAX_DELAY; /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
break;
} /*pxDelayedTaskList不为空,有任务在等待调度,这样任务有2种情况,一个是超时了,一个是还未超时*/
else
{
/* The delayed list is not empty, get the value of the
* item at the head of the delayed list. This is the time
* at which the task at the head of the delayed list must
* be removed from the Blocked state. */
pxTCB = listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
xItemValue = listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
/*等待的任务定时器时间未到,则直接更新下次超时时间,跳出本次循环*/
if( xConstTickCount < xItemValue )
{
/* It is not time to unblock this item yet, but the
* item value is the time at which the task at the head
* of the blocked list must be removed from the Blocked
* state - so record the item value in * xNextTaskUnblockTime. */
xNextTaskUnblockTime = xItemValue;
break; /*lint !e9011 Code structure here is deemed easier to understand with multiple breaks. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/*任务的定时器已经到时,则需要将该任务放到readyList中*/
/* It is time to remove the item from the Blocked state. */
listREMOVE_ITEM( &( pxTCB->xStateListItem ) );
/* Is the task waiting on an event also? If so remove
* it from the event list. */
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
listREMOVE_ITEM( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Place the unblocked task into the appropriate ready
* list. */
prvAddTaskToReadyList( pxTCB );
/* A task being unblocked cannot cause an immediate
* context switch if preemption is turned off. */
#if ( configUSE_PREEMPTION == 1 )
{
/* Preemption is on, but a context switch should
* only be performed if the unblocked task has a
* priority that is equal to or higher than the
* currently executing task. */
/*定义了抢占的话,如果到时的任务优先级大于等于当前任务的优先级,则置位xSwitchRequired,触发一次任务切换*/
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
}
}
}
/*走到这里,以下几种可能的情况
1> pxDelayedTaskList为空
2> pxDelayedTaskList不为空,但是任务的定时时间还没有到
3> pxDelayedTaskList不为空,任务的定时时间已经到了,同时,把到时的任务也放入了readyList中
如果定义了抢占和cpu时间片,如果readyList中的任务数大于1,则置位xSwitchRequired,触发一次任务切换。
*/
/* Tasks of equal priority to the currently running task will share
* processing time (time slice) if preemption is on, and the application
* writer has not explicitly turned time slicing off. */
/*如果定义了configUSE_TIME_SLICING,也只是置位xSwitchRequired为true,那么相同优先级的任务如何切换的呢?*/
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) )
{
if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) */
/*开启了抢占的话,如果有task yield pending了也会触发一次任务切换*/
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE )
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
system tick定时任务干了的事情有:
- 判断是否有任务在等待执行
- 任务超时处理
- 任务的抢占
- 时间片调度
不管任务超时与否,system tick到来后,都会判断是否需要抢占和时间片轮换。
关于时间片,相同优先级的任务如何切换的呢?system tick的ISR只是把任务放入readyList中,任务切换执行是在另外 一个函数中vTaskSwitchContext。
freeRTOS中system tick到来之后都会调用一次vTaskSwitchContext。
所以查看vTaskSwitchContext的代码,找出答案。
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
....
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
#if ( configGENERATE_RUN_TIME_STATS == 1 )
......
#endif /* configGENERATE_RUN_TIME_STATS */
/* Check for stack overflow, if configured. */
taskCHECK_FOR_STACK_OVERFLOW();
/* Select a new task to run using either the generic C or port
* optimised asm code. */
taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too. Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
traceTASK_SWITCHED_IN();
........
........
}
}
核心代码为taskSELECT_HIGHEST_PRIORITY_TASK();它的实现在:
#define taskSELECT_HIGHEST_PRIORITY_TASK() \
{ \
UBaseType_t uxTopPriority; \
\
/* Find the highest priority list that contains ready tasks. */ \
portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority ); \
configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) ); \
} /* taskSELECT_HIGHEST_PRIORITY_TASK() */
最后在这 listGET_OWNER_OF_NEXT_ENTRY()
#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; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
轮训当前优先级的链表,通过节点的pvOwer拿到TCB指针(具体任务的任务块入口)。
宏接口一进来就pxIndex指向了下一个节点,比如当前链表中有2个任务,那system tick到来之后,直接跳转到了下个节点,提取下个任务
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;
如果同一优先级的任务只有2个而且没有其他任务抢占,在这样特定情况下,每到一个system tick,就会切换到另外一个任务,2个任务来回切换共享cpu,切换时间以system tick为基准。
结合项目中的pxReadyTasksLists数据结构如下:
pxReadyTasksLists是以优先级为索引的数组链表,同一优先级的任务挂在对应的链表上。
所有链表均是唤醒链表。
到这里就是整个任务切换的大致过程。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)