c. 任务如何切换? 低优先级的任务什么时候得到执行?
FreeRTOS 系统 在抢占式模式下,优先级高的任务会一直跑,除非,它自己把cpu让出来。它通过vTaskDelay(number of system tick) 或者一些锁/信号量(非自旋锁),或者把自己挂起来vTaskSuspend()
• 就绪(Ready):该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态。
• 运行(Running):该状态表明任务正在执行,此时它占用处理器,FreeRTOS 调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态。
• 阻塞(Blocked):如果任务当前正在等待某个时序或外部中断,我们就说这个任务处于阻塞状态,该任务不在就绪列表中。包含任务被挂起、任务被延时、任务正在等待信号量、读写队列或者等待读写事件等。
• 挂起态(Suspended):处于挂起态的任务对调度器而言是不可见的,让一个任务进入挂起状态的唯一办法就是调用vTaskSuspend() 函数;而把一个挂起状态的任务恢复的唯一途径就是调用vTaskResume()或vTaskResumeFromISR() 函数,我们可以这么理解挂起态与阻塞态的区别,当任务有较长的时间不允许运行的时候,我们可以挂起任务,这样子调度器就不会管这个任务的任何信息,直到我们调用恢复任务的API 函数;而任务处于阻塞态的时候,系统还需要判断阻塞态的任务是否超时,是否可以解除阻塞。
还记得我们在任务初始化的时候,除了任务列表之外,还有延迟列表,挂起列表的, 作用就是保持那些被延迟/挂起的任务 ---- “至于xDelayedTaskList1, xDelayedTaskList2, xPendingReadyList是在任务调度的时候,用做保存延迟或者等待的任务列表”。
vTaskDelay()
简单看一下vTaskDelay()函数吧。
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll();
{
traceTASK_DELAY();
/* A task that is removed from the event list while the
scheduler is suspended will not get placed in the ready
list or removed from the blocked list until the scheduler
is resumed.
This task cannot be in an event list as it is the currently
executing task. */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
// 挂起调度器,只会在当前任务执行,不发生上下文切换,中断除外。如果优先级高的任务过来(比如说从消息队列解锁),那么会把该优先级高的任务放在pending list里面。
void vTaskSuspendAll( void )
{
/* A critical section is not required as the variable is of type
BaseType_t. Please read Richard Barry's reply in the following link to a
post in the FreeRTOS support forum before reporting this as a bug! -
http://goo.gl/wu4acr */
++uxSchedulerSuspended;
}
进入到 prvAddCurrentTaskToDelayedList()
先把当前TCB对应的listItem 从任务列表中移除
插入到延迟列表(suspendedTaskList)
配置它的item value 为当前tick + 延迟的tick,
更新下一个任务解锁时刻变量xNextTaskUnblockTime 的值。这一步很重要,在xTaskIncrementTick() 函数中,我们只需要让系统时基计数器xTickCount 与xNextTaskUnblockTime 的值先比较就知道延时最快结束的任务是否到期。
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* The current task must be in a ready list, so there is no need to
check, and the port reset macro can be called directly. */
portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
{
/* Add the task to the suspended task list instead of a delayed task
list to ensure it is not woken by a timing event. It will block
indefinitely. */
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
/* Calculate the time at which the task should be woken if the event
does not occur. This may overflow but this doesn't matter, the
kernel will manage it correctly. */
xTimeToWake = xConstTickCount + xTicksToWait;
/* The list item will be inserted in wake time order. */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );
if( xTimeToWake < xConstTickCount )
{
/* Wake time has overflowed. Place this item in the overflow
list. */
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
}
else
{
/* The wake time has not overflowed, so the current block list
is used. */
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
/* If the task entering the blocked state was placed at the
head of the list of blocked tasks then xNextTaskUnblockTime
needs to be updated too. */
if( xTimeToWake < xNextTaskUnblockTime )
{
xNextTaskUnblockTime = xTimeToWake;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
进入xTaskResumeAll()
Update 一下任务列表,把pending list的任务,如果它ready了,把它添加到任务列表里面
任务切换: 调用taskYIELD_IF_USING_PREEMPTION(), 进入切换中断函数
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
if( uxCurrentNumberOfTasks > ( UBaseType_t ) 0U )
{
/* Move any readied tasks from the pending list into the
appropriate ready list. */
while( listLIST_IS_EMPTY( &xPendingReadyList ) == pdFALSE )
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xPendingReadyList ) );
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
/* If the moved task has a priority higher than the current
task then a yield must be performed. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( pxTCB != NULL )
{
/* A task was unblocked while the scheduler was suspended,
which may have prevented the next unblock time from being
re-calculated, in which case re-calculate it now. Mainly
important for low power tickless implementations, where
this can prevent an unnecessary exit from low power
state. */
prvResetNextTaskUnblockTime();
}
/* If any ticks occurred while the scheduler was suspended then
they should be processed now. This ensures the tick count does
not slip, and that any delayed tasks are resumed at the correct
time. */
{
UBaseType_t uxPendedCounts = uxPendedTicks; /* Non-volatile copy. */
if( uxPendedCounts > ( UBaseType_t ) 0U )
{
do
{
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts;
} while( uxPendedCounts > ( UBaseType_t ) 0U );
uxPendedTicks = 0;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
if( xYieldPending != pdFALSE )
{
#if( configUSE_PREEMPTION != 0 )
{
xAlreadyYielded = pdTRUE;
}
#endif
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
void vPortYield( void )
{
/* Set a PendSV to request a context switch. */
*( portNVIC_INT_CTRL ) = portNVIC_PENDSVSET;
/* Barriers are normally not required but do ensure the code is completely
within the specified behaviour for the architecture. */
__asm volatile( "dsb" ::: "memory" );
__asm volatile( "isb" );
}
进入任务切换中断函数
挑选下一个最高优先级的任务(vTaskSwitchContext())去执行
void xPortPendSVHandler( void )
{
/* This is a naked function. */
__asm volatile
(
" .syntax unified \n"
" mrs r0, psp \n"
" \n"
" ldr r3, pxCurrentTCBConst \n" /* Get the location of the current TCB. */
" ldr r2, [r3] \n"
" \n"
" subs r0, r0, #32 \n" /* Make space for the remaining low registers. */
" str r0, [r2] \n" /* Save the new top of stack. */
" stmia r0!, {r4-r7} \n" /* Store the low registers that are not saved automatically. */
" mov r4, r8 \n" /* Store the high registers. */
" mov r5, r9 \n"
" mov r6, r10 \n"
" mov r7, r11 \n"
" stmia r0!, {r4-r7} \n"
" \n"
" push {r3, r14} \n"
" cpsid i \n"
" bl vTaskSwitchContext \n"
" cpsie i \n"
" pop {r2, r3} \n" /* lr goes in r3. r2 now holds tcb pointer. */
" \n"
" ldr r1, [r2] \n"
" ldr r0, [r1] \n" /* The first item in pxCurrentTCB is the task top of stack. */
" adds r0, r0, #16 \n" /* Move to the high registers. */
" ldmia r0!, {r4-r7} \n" /* Pop the high registers. */
" mov r8, r4 \n"
" mov r9, r5 \n"
" mov r10, r6 \n"
" mov r11, r7 \n"
" \n"
" msr psp, r0 \n" /* Remember the new top of stack for the task. */
" \n"
" subs r0, r0, #32 \n" /* Go back for the low registers that are not automatically restored. */
" ldmia r0!, {r4-r7} \n" /* Pop low registers. */
" \n"
" bx r3 \n"
" \n"
" .align 4 \n"
"pxCurrentTCBConst: .word pxCurrentTCB "
);
}
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
else
{
xYieldPending = pdFALSE;
traceTASK_SWITCHED_OUT();
/* 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();
traceTASK_SWITCHED_IN();
}
}
SysTick
SysTick用于产生系统节拍时钟。
每次systick异常产生都会检查是否需要任务调度,如果需要,则出发PendSV异常即可。
在操作系统中,通常软件定时器以系统节拍周期为计时单位。系统节拍是系统的心跳节拍,表示系统时钟的频率,就类似人的心跳,1s 能跳动多少下,系统节拍配置为configTICK_RATE_HZ,该宏在FreeRTOSConfig.h 中有定义,默认是1000。那么系统的时钟节拍周期就为1ms(1s 跳动1000 下,每一下就为1ms)
PendSV
PendSV(可挂起系统调用)用于完成任务切换。
该异常可以像普通的中断一样被挂起的,它的最大特性是如果当前有优先级比它高的中断在运行,PendSV会延迟执行,直到高优先级中断执行完毕,这样子产生的PendSV中断就不会打断其他中断的运行。
在该异常的回调函数里执行任务切换。