主机环境:Windows
开发环境:MDK4.7.2
FreeRTOS版本:FreeRTOS8.1.2
目标环境:STM32F030C8T6
FreeRTOS的任务有以下几种状态:运行态、就绪态、阻塞态、挂起态,如下图
其中如果任务调用了延时函数就会进入阻塞态,延时函数有两个:vTaskDelay()和vTaskDelayUtil()前者是相对延时,后者是绝对延时,可以查看Using the FreeRTOS Real Time Kernel - a Practical Guide文档来帮助理解。先来了解一下vTaskDelay()函数吧,代码如下
void vTaskDelay( const TickType_t xTicksToDelay )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U )
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll();
{
traceTASK_DELAY();
xTimeToWake = xTickCount + xTicksToDelay;
/* We must remove ourselves from the ready list before adding
ourselves to the blocked list as the same list item is used for
both lists. */
if( uxListRemove( &( pxCurrentTCB->xGenericListItem ) ) == ( 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();
}
prvAddCurrentTaskToDelayedList( xTimeToWake );
}
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Force a reschedule if xTaskResumeAll has not already done so, we may
have put ourselves to sleep. */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
代码还是比较精简的,进来之后首先检测延时时间的有效性,如果为0的话则表明立即执行一次任务切换,一般而言都是正常值大于0的,这里进入了vTaskSuspendAll()函数
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;
}
这个网址没打开,所以也还不晓得原因,这里是把调度器挂起,在此期间任务切换是不会执行的,但不会影响中断的响应,然后计算任务的唤醒时间,接着将任务从就绪表中移除,添加入延时链表中,
static void prvAddCurrentTaskToDelayedList( const TickType_t xTimeToWake )
{
/* The list item will be inserted in wake time order. */
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xGenericListItem ), xTimeToWake );
if( xTimeToWake < xTickCount )
{
/* Wake time has overflowed. Place this item in the overflow list. */
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xGenericListItem ) );
}
else
{
/* The wake time has not overflowed, so the current block list is used. */
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xGenericListItem ) );
/* 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();
}
}
}
根据唤醒时间是否溢出,来将任务插入到不同的延时链表中,如果唤醒时间没有溢出则插入正常的延时链表中,如果唤醒时间溢出的话则插入延时溢出链表中。链表中的元素是按照xItemValue升序排列的,以便于我们唤醒任务时的查找。最后还要检测是否需要更新下一任务的唤醒时间xNextTaskUnblockTime
接着唤醒调度器
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB;
BaseType_t xAlreadyYielded = pdFALSE;
/* If uxSchedulerSuspended is zero then this function does not match a
previous call to vTaskSuspendAll(). */
configASSERT( uxSchedulerSuspended );
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;
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->xGenericListItem ) );
prvAddTaskToReadyList( pxTCB );
/* If we have moved a task that has a priority higher than
the current task then we should yield. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 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. */
if( uxPendedTicks > ( UBaseType_t ) 0U )
{
while( uxPendedTicks > ( UBaseType_t ) 0U )
{
if( xTaskIncrementTick() != pdFALSE )
{
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
--uxPendedTicks;
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xYieldPending == pdTRUE )
{
#if( configUSE_PREEMPTION != 0 )
{
xAlreadyYielded = pdTRUE;
}
#endif
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
在唤醒调度器时根据xAlreadyYield变量来标记是否需要执行任务切换,进入临界区后对uxSchedulerSuspended变量进行减减操作唤醒调度器,如果更新后的值为pdFALSE则表明调度器唤醒成功,调度器唤醒时我们需要检测是否有中断将任务唤醒进入了悬挂就绪态,即检测xPendingReadyList链表是否为空,如果不为空则将任务从xPendingReadyList链表中移除添加入就绪链表中,同样也需要从事件链表中移除该任务(有关任务链表的说明还没看到后面再看吧)。将任务唤醒后检测其优先级是否比当前执行任务的优先级高,如果要高更新xYieldPend为pdTRUE,需要执行一次任务切换,这里是将挂起就绪链表中的所有任务都添加到就绪表中。
接着下面判断了在调度器挂起期间是否有时间中断发生,当uxPendedTicks的值大于0时表明在调度器挂起期间有SysTick中断发生,这里需要处理,以确保延时任务在正确的时间唤醒,在代码中是根据uxPendedTicks的数值来循环调用下TaskIncrementTick()函数,直到uxPendedTIcks值变为0,并根据需要更新xYieldPending的值,最后如果启用抢占式调用的话则标记xAlreadyYielded的值为pdTRUE,并调用taskYIELD_IF_USING_PREEMPTION()宏来执行任务切换,该宏最后也是调用vPortYield()函数,产生一次NVIC_PENDSV中断以此来执行任务切换,当然这次任务切换不会立即执行因为还没退出临界区代码,最后退出临界区并返回xAlreadyYielded值。该值在vTaskDelay()中还要用到,如果已经产生了任务切换则在vTaskDelay()函数中就不再产生任务切换,vTaskDelay()函数也就分析完毕了。该函数的分析应该和SysTick中断一起分析才好理解我觉得,留待下回吧。。。