目录
- 前言
- 一、FreeRTOS 延时函数
- 1.1函数vTaskDelay()
- 1.2 函数prvAddCurrentTaskToDelayedList()
- 1.3 函数vTaskDelayUntil()
- 二、FreeRTOS 系统时钟节拍
前言
在使用FreeRTOS 的过程中我们通常会在一个任务函数中使用延时函数对这个任务延时,当执行延时函数的时候就会进行任务切换,并且此任务就会进入阻塞态,直到延时完成,任务重新进入就绪态。延时函数属于FreeRTOS 的时间管理,接下来我们就来学习一些FreeRTOS 的这个时间管理过程,看看在调用延时函数以后究竟发生了什么?任务是如何进入阻塞态的,在延时完成以后任务又是如何从阻塞态恢复到就绪态的?
一、FreeRTOS 延时函数
1.1函数vTaskDelay()
学习过UCOSIII 的朋友应该知道,在UCOSIII 中延时函数OSTimeDly()可以设置为三种模式:相对模式、周期模式和绝对模式。在FreeRTOS 中延时函数也有相对模式和绝对模式,不过在FreeRTOS 中不同的模式用的函数不同,其中函数vTaskDelay()是相对模式(相对延时函数),函数vTaskDelayUntil()是绝对模式(绝对延时函数)。函数vTaskDelay()在文件tasks.c 中有定义,要使用此函数的话宏INCLUDE_vTaskDelay 必须为1,函数代码如下:
#if ( INCLUDE_vTaskDelay == 1 )
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* A delay time of zero just forces a reschedule. */
if( xTicksToDelay > ( TickType_t ) 0U ) /*(1)*/
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); /*(2)*/
{
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 ); /*(3)*/
}
xAlreadyYielded = xTaskResumeAll(); /*(4)*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Force a reschedule if xTaskResumeAll has not already done so, we may
have put ourselves to sleep. */
if( xAlreadyYielded == pdFALSE ) /*(5)*/
{
portYIELD_WITHIN_API(); /*(6)*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskDelay */
(1)、延时时间由参数xTicksToDelay 来确定,为要延时的时间节拍数,延时时间肯定要大于0。否则的话相当于直接调用函数portYIELD()进行任务切换。
(2)、调用函数vTaskSuspendAll()挂起任务调度器。
(3) 、调用函数prvAddCurrentTaskToDelayedList() 将要延时的任务添加到延时列表pxDelayedTaskList 或者pxOverflowDelayedTaskList() 中。后面会具体分析函数prvAddCurrentTaskToDelayedList()。
(4)、调用函数xTaskResumeAll()恢复任务调度器。
(5)、如果函数xTaskResumeAll()没有进行任务调度的话那么在这里就得进行任务调度。
(6)、调用函数portYIELD_WITHIN_API()进行一次任务调度。
1.2 函数prvAddCurrentTaskToDelayedList()
函数prvAddCurrentTaskToDelayedList()用于将当前任务添加到等待列表中,函数在文件tasks.c 中有定义,缩减后的函数如下:
static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount; //1
#if( INCLUDE_xTaskAbortDelay == 1 )
{
/* About to enter a delayed list, so ensure the ucDelayAborted flag is
reset to pdFALSE so it can be detected as having been set to pdTRUE
when the task leaves the Blocked state. */
pxCurrentTCB->ucDelayAborted = pdFALSE;
}
#endif
/* Remove the task from the ready list before adding it to the blocked list
as the same list item is used for both lists. */
//将当前任务的状态列表项从就绪列表中移除,
//为什么这么肯定就是就绪列表呢? 因为调用vTaskDelay函数一定是当前正在运行的任务(也就是任务自身),
//不可能说A任务调用vTaskDelay让B任务延时,这是不存在的。
//既然是当前正在运行的任务调用vTaskDelay,那么说明他一定在就绪列表中。
if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) //2
{
/* 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 );//3
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#if ( INCLUDE_vTaskSuspend == 1 )
{
if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )//4
{
/* 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. */
//如果延时portMAX_DELAY个时钟节拍就添加到挂起列表中,也就是挂起任务,
//但是调用vTaskDelay函数即使传递参数portMAX_DELAY也不能挂起任务 因为xCanBlockIndefinitely固定传递的是pdFALSE
vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );//5
}
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;//计算出唤醒时间点 //6
/* The list item will be inserted in wake time order. */
//将唤醒时间点的值写入到当前任务控制块的状态列表项中,后续会把状态列表项挂载到延时列表
listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake ); //7
if( xTimeToWake < xConstTickCount )//如果下一个时间点小于当前时间点 //8
{
/* Wake time has overflowed. Place this item in the overflow
list. */
//这种说明"唤醒点"会在xTickCount溢出之后到来 溢出就挂载到pxOverflowDelayedTaskList指针指向的延时列表上
vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); //9
}
else
{
/* The wake time has not overflowed, so the current block list
is used. */
//这种情况说名"唤醒点"会在xTickCount溢出之前到来
//"唤醒点"在xTickCount溢出之前到来就挂载到pxDelayedTaskList指针指向的延时列表上
vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) ); //10
/* 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. */
//xNextTaskUnblockTime保存了最近一个任务的解锁时间
//如果新添加到阻塞列表中的"唤醒点"小于最近一个任务解锁时刻
//那么就更新xNextTaskUnblockTime
if( xTimeToWake < xNextTaskUnblockTime ) //11
{
//更新xNextTaskUnblockTime
xNextTaskUnblockTime = xTimeToWake; //12
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
#else /* INCLUDE_vTaskSuspend */
{
/* 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();
}
}
/* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
( void ) xCanBlockIndefinitely;
}
#endif /* INCLUDE_vTaskSuspend */
}
(1)、读取进入函数prvAddCurrentTaskToDelayedList()的时间点并保存在xConstTickCount 中,后面计算任务唤醒时间点的时候要用到。xTickCount 是时钟节拍计数器,每个滴答定时器中断xTickCount 都会加一。
(2)、要将当前正在运行的任务添加到延时列表中,肯定要先将当前任务从就绪列表中移除。
(3)、将当前任务从就绪列表中移除以后还要取消任务在uxTopReadyPriority 中的就绪标记。也就是将uxTopReadyPriority 中对应的bit 清零。
(4) 、延时时间为最大值portMAX_DELAY , 并且xCanBlockIndefinitely 不为pdFALSE(xCanBlockIndefinitely 不为pdFALSE 的话表示允许阻塞任务)的话直接将当前任务添加到挂起列表中,任务就不用添加到延时列表中。
(5)、将当前任务添加到挂起列表xSuspendedTaskList 的末尾。
(6)、计算任务唤醒时间点,也就是(1)中获取到的进入函数prvAddCurrentTaskToDelayedList()的时间值xConstTickCount 加上延时时间值xTicksToWait。
(7)、将计算到的任务唤醒时间点值xTimeToWake 写入到任务列表中壮态列表项的相应字段中。
(8)、计算得到的任务唤醒时间点小于xConstTickCount,说明发生了溢出。全局变量xTickCount 是TickType_t 类型的,这是个32 位的数据类型,因此在用xTickCount 计算任务唤醒时间点xTimeToWake 的时候的肯定会出现溢出的现象。FreeRTOS 针对此现象专门做了处理,在FreeROTS 中定义了两个延时列表xDelayedTaskList1 和xDelayedTaskList2,并且也定义了两个指针pxDelayedTaskList 和pxOverflowDelayedTaskList 来访问这两个列表,在初始化列表函数prvInitialiseTaskLists() 中指针pxDelayedTaskList 指向了列表xDelayedTaskList1 , 指针pxOverflowDelayedTaskList 指向了列表xDelayedTaskList2。这样发生溢出的话就将任务添加到pxOverflowDelayedTaskList 所指向的列表中,如果没有溢出的话就添加到pxDelayedTaskList 所指向的列表中。
(9)、如果发生了溢出的话就将当前任务添加到pxOverflowDelayedTaskList 所指向的列表中。
(10)、如果没有发生溢出的话就将当前任务添加到pxDelayedTaskList 所指向的列表中。
(11)、xNextTaskUnblockTime 是个全局变量,保存着距离下一个要取消阻塞的任务最小时间点值。 当xTimeToWake 小于xNextTaskUnblockTime 的话说明有个更小的时间点来了。
(12)、更新xNextTaskUnblockTime 为xTimeToWake。
1.3 函数vTaskDelayUntil()
函数vTaskDelayUntil()会阻塞任务,阻塞时间是一个绝对时间,那些需要按照一定的频率运行的任务可以使用函数vTaskDelayUntil()。此函数再文件tasks.c 中有如下定义:
#if ( INCLUDE_vTaskDelayUntil == 1 )
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); /*(1)*/
{
/* Minor optimisation. The tick count cannot change in this
block. */
const TickType_t xConstTickCount = xTickCount; /*(2)*/
/* Generate the tick time at which the task wants to wake. */
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement; /*(3)*/
if( xConstTickCount < *pxPreviousWakeTime ) /*(4)*/
{
/* The tick count has overflowed since this function was
lasted called. In this case the only time we should ever
actually delay is if the wake time has also overflowed,
and the wake time is greater than the tick time. When this
is the case it is as if neither time had overflowed. */
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )/*(5)*/
{
xShouldDelay = pdTRUE; /*(6)*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* The tick time has not overflowed. In this case we will
delay if either the wake time has overflowed, and/or the
tick time is less than the wake time. */
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )/*(7)*/
{
xShouldDelay = pdTRUE; /*(8)*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* Update the wake time ready for the next call. */
*pxPreviousWakeTime = xTimeToWake; /*(9)*/
if( xShouldDelay != pdFALSE ) /*(10)*/
{
traceTASK_DELAY_UNTIL( xTimeToWake );
/* prvAddCurrentTaskToDelayedList() needs the block time, not
the time to wake, so subtract the current tick count. */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE ); /*(11)*/
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll(); /*(12)*/
/* 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();
}
}
#endif /* INCLUDE_vTaskDelayUntil */
参数:
pxPreviousWakeTime: 上一次任务延时结束被唤醒的时间点,任务中第一次调用函数vTaskDelayUntil 的话需要将pxPreviousWakeTime 初始化进入任务的while()循环体的时间点值。在以后的运行中函数vTaskDelayUntil()会自动更新pxPreviousWakeTime。xTimeIncrement:任务需要延时的时间节拍数(相对于pxPreviousWakeTime 本次延时的节拍数)。
(1)、挂起任务调度器。
(2)、记录进入函数vTaskDelayUntil()的时间点值,并保存在xConstTickCount 中。
(3)、根据延时时间xTimeIncrement 来计算任务下一次要唤醒的时间点, 并保存在xTimeToWake 中。可以看出这个延时时间是相对于pxPreviousWakeTime 的,也就是上一次任务被唤醒的时间点。pxPreviousWakeTime、xTimeToWake、xTimeIncrement 和xConstTickCount 的关系如下图所示。
其中 (1)为任务主体,也就是任务真正要做的工作, (2)是任务函数中调用vTaskDelayUntil()对任务进行延时,(3)为其他任务在运行。任务的延时时间是xTimeIncrement,这个延时时间是相对于pxPreviousWakeTime 的,可以看出任务总的执行时间一定要小于任务的延时时间xTimeIncrement!也就是说如果使用vTaskDelayUntil()的话任务相当于任务的执行周期永远都是xTimeIncrement,而任务一定要在这个时间内执行完成。这样就保证了任务永远按照一定的频率运行了,这个延时值就是绝对延时时间,因此函数vTaskDelayUntil()也叫做绝对延时函数。
(4)、根据上图可以看出,理论上xConstTickCount 要大于pxPreviousWakeTime 的,但是也有一种情况会导致xConstTickCount 小于pxPreviousWakeTime,那就是xConstTickCount 溢出了!
(5)、既然xConstTickCount 都溢出了,那么计算得到的任务唤醒时间点肯定也是要溢出的,并且xTimeToWake 肯定也是要大于xConstTickCount 的。这种情况如下图所示:
(6)、如果满足(5)条件的话就将pdTRUE 赋值给xShouldDelay,标记允许延时。
(7)、还有其他两种情况,一:只有xTimeToWake 溢出,二:都没有溢出。只有xTimeToWake溢出的话如下图所示:
都不溢出的话就如(3))图所示,这两种情况都允许进行延时。
(8)、将pdTRUE 赋值给xShouldDelay,标记允许延时。
(9)、更新pxPreviousWakeTime 的值,更新为xTimeToWake,为本函数的下一次执行做准备。
(10)、经过前面的判断,允许进行任务延时。
(11)、调用函数prvAddCurrentTaskToDelayedList()进行延时。函数的第一个参数是设置任务的阻塞时间,前面我们已经计算出了任务下一次唤醒时间点了,那么任务还需要阻塞的时间就是下一次唤醒时间点xTimeToWake 减去当前的时间xConstTickCount。而在函数vTaskDelay()中只是简单的将这参数设置为xTicksToDelay。
(12)、调用函数xTaskResumeAll()恢复任务调度器。函数vTaskDelayUntil()的使用方法如下:
void TestTask( void * pvParameters )
{
TickType_t PreviousWakeTime;
//延时 50ms,但是函数 vTaskDelayUntil()的参数需要设置的是延时的节拍数,不能直接
//设置延时时间,因此使用函数 pdMS_TO_TICKS 将时间转换为节拍数。
const TickType_t TimeIncrement = pdMS_TO_TICKS( 50 );
PreviousWakeTime = xTaskGetTickCount(); //获取当前的系统节拍值for( ;; )
{
/******************************************************************/
/*************************任务主体*********************************/
/******************************************************************/
//调用函数vTaskDelayUntil 进行延时
vTaskDelayUntil( &PreviousWakeTime, TimeIncrement);
}
}
其实使用函数vTaskDelayUntil()延时的任务也不一定就能周期性的运行,使用函数vTaskDelayUntil()只能保证你按照一定的周期取消阻塞,进入就绪态。如果有更高优先级或者中断的话你还是得等待其他的高优先级任务或者中断服务函数运行完成才能轮到你。这个绝对延时只是相对于vTaskDelay()这个简单的延时函数而言的。
二、FreeRTOS 系统时钟节拍
不管是什么系统,运行都需要有个系统时钟节拍,前面已经提到多次了,xTickCount 就是FreeRTOS 的系统时钟节拍计数器。每个滴答定时器中断中xTickCount 就会加一,xTickCount 的具体操作过程是在函数xTaskIncrementTick()中进行的,此函数在文件tasks.c 中有定义,如下:
BaseType_t xTaskIncrementTick( void )
{
TCB_t * pxTCB; TickType_t xItemValue;
BaseType_t xSwitchRequired = pdFALSE;
//每个时钟节拍中断(滴答定时器中断)调用一次本函数,增加时钟节拍计数器 xTickCount 的
//值,并且检查是否有任务需要取消阻塞。
traceTASK_INCREMENT_TICK( xTickCount );
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) //1
{
const TickType_t xConstTickCount = xTickCount + 1; //2
//增加系统节拍计数器 xTickCount 的值,当为 0,也就是溢出的话就交换延时和溢出列
//表指针值。
xTickCount = xConstTickCount;
if( xConstTickCount == ( TickType_t ) 0U ) //3
{
taskSWITCH_DELAYED_LISTS(); //4
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//判断是否有任务延时时间到了,任务都会根据唤醒时间点值按照顺序(由小到大的升
//序排列)添加到延时列表中,这就意味这如果延时列表中第一个列表项对应的任务的
//延时时间都没有到的话后面的任务就不用看了,肯定也没有到。
if( xConstTickCount >= xNextTaskUnblockTime ) //5
{
for( ;; )
{
if( listLIST_IS_EMPTY( pxDelayedTaskList ) != pdFALSE ) //6
{
//延时列表为空,设置 xNextTaskUnblockTime 为最大值。
xNextTaskUnblockTime = portMAX_DELAY; //7
break;
}
else
{
//延时列表不为空,获取延时列表的第一个列表项的值,根据判断这个值
//判断任务延时时间是否到了, 如果到了的话就将任务移除延时列表。
pxTCB = ( TCB_t * )\ //8
listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList ); xItemValue =\ //9
listGET_LIST_ITEM_VALUE( &( pxTCB->xStateListItem ) );
if( xConstTickCount < xItemValue ) //10
{
}
else
{
}
//任务延时时间还没到,但是 xItemValue 保存着下一个即将解除
//阻塞态的任务对应的解除时间点,所以需要用 xItemValue 来更新
//变量xNextTaskUnblockTime
xNextTaskUnblockTime = xItemValue; //11
break;
mtCOVERAGE_TEST_MARKER();
//将任务从延时列表中移除
( void ) uxListRemove( &( pxTCB->xStateListItem ) ); //12
//任务是否还在等待其他事件?如信号量、队列等,如果是的话就将这些
//任务从相应的事件列表中移除。相当于等待事件超时退出!
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) !\ = NULL ) //13
{
}
else
{
}
( void ) uxListRemove( &( pxTCB->xEventListItem ) ); //14
mtCOVERAGE_TEST_MARKER();
//将任务添加到就绪列表中
prvAddTaskToReadyList( pxTCB ); //15
#if ( configUSE_PREEMPTION == 1 )
{
//使用抢占式内核,判断解除阻塞的任务优先级是否高于当前正在
//运行的任务优先级,如果是的话就需要进行一次任务切换!
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) //16
{
}
else
{
}
}
xSwitchRequired = pdTRUE;
mtCOVERAGE_TEST_MARKER();
#endif /* configUSE_PREEMPTION */
}
}
}
//如果使能了时间片的话还需要处理同优先级下任务之间的调度
#if ( ( configUSE_PREEMPTION == 1 ) && ( configUSE_TIME_SLICING == 1 ) ) //17
{
if( listCURRENT_LIST_LENGTH( &( \
pxReadyTasksLists[ pxCurrentTCB->uxPriority ] ) ) > ( UBaseType_t ) 1 )
{
}
else
{
}
}
xSwitchRequired = pdTRUE;
mtCOVERAGE_TEST_MARKER();
#endif
//使用时钟节拍钩子函数
#if ( configUSE_TICK_HOOK == 1 )
{
if( uxPendedTicks == ( UBaseType_t ) 0U )
{
}
else
{
}
}
vApplicationTickHook(); //18
mtCOVERAGE_TEST_MARKER();
#endif /* configUSE_TICK_HOOK */
}
else//任务调度器挂起期间 //19
{
++uxPendedTicks;//任务调度器挂起期间 uxPendedTicks变量++ 记录时钟节拍个数 //20
/* The tick hook gets called at regular intervals, even if the
scheduler is locked. */
#if ( configUSE_TICK_HOOK == 1 )
{
vApplicationTickHook();
}
#endif
}
#if ( configUSE_PREEMPTION == 1 )
{
if( xYieldPending != pdFALSE ) //21
{
xSwitchRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_PREEMPTION */
return xSwitchRequired;//返回是否进行任务切换的标志 //22
}
(1)、判断任务调度器是否被挂起。
(2)、将时钟节拍计数器xTickCount 加一,并将结果保存在xConstTickCount 中,下一行程序会将xConstTickCount 赋值给xTickCount,相当于给xTickCount 加一。
(3)、xConstTickCount 为0,说明发生了溢出!
(4)、如果发生了溢出的话使用函数taskSWITCH_DELAYED_LISTS 将延时列表指针pxDelayedTaskList 和溢出列表指针pxOverflowDelayedTaskList 所指向的列表进行交换,函数taskSWITCH_DELAYED_LISTS()本质上是个宏,在文件tasks.c 中有定义,将这两个指针所指向的列表交换以后还需要更新xNextTaskUnblockTime 的值。
(5)、变量xNextTaskUnblockTime 保存着下一个要解除阻塞的任务的时间点值,如果xConstTickCount 大于xNextTaskUnblockTime 的话就说明有任务需要解除阻塞了。
(6)、判断延时列表是否为空。
(7)、如果延时列表为空的话就将xNextTaskUnblockTime 设置为portMAX_DELAY。
(8)、延时列表不为空,获取延时列表第一个列表项对应的任务控制块。
(9)、获取(8)中获取到的任务控制块中的壮态列表项值。
(10)、任务控制块中的壮态列表项值保存了任务的唤醒时间点,如果这个唤醒时间点值大于当前的系统时钟(时钟节拍计数器值),说明任务的延时时间还未到。
(11)、任务延时时间还未到,而且xItemValue 已经保存了下一个要唤醒的任务的唤醒时间点,所以需要用xItemValue 来更新xNextTaskUnblockTime。
(12)、任务延时时间到了,所以将任务先从延时列表中移除。
(13)、检查任务是否还等待某个事件,比如等待信号量、队列等。如果还在等待的话就任务从相应的事件列表中移除。因为超时时间到了!
(14)、将任务从相应的事件列表中移除。
(15)、任务延时时间到了,并且任务已经从延时列表或者事件列表中已经移除。所以这里需要将任务添加到就绪列表中。
(16)、延时时间到的任务优先级高于正在运行的任务优先级,所以需要进行任务切换了,标记xSwitchRequired 为pdTRUE,表示需要进行任务切换。
(17)、如果使能了时间片调度的话,还要处理跟时间片调度有关的工作,具体过程参考9.6小节。
(18)、如果使能了时间片钩子函数的话就执行时间片钩子函数vApplicationTickHook(),函数的具体内容由用户自行编写。
(19)、如果调用函数vTaskSuspendAll()挂起了任务调度器的话在每个滴答定时器中断就不不会更新xTickCount 了。取而代之的是用uxPendedTicks 来记录调度器挂起过程中的时钟节拍数。这样在调用函数xTaskResumeAll()恢复任务调度器的时候就会调用uxPendedTicks 次函数xTaskIncrementTick(),这样xTickCount 就会恢复,并且那些应该取消阻塞的任务都会取消阻塞。函数xTaskResumeAll()中相应的处理代码如下:
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE; configASSERT( uxSchedulerSuspended );
taskENTER_CRITICAL();
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
UBaseType_t uxPendedCounts = uxPendedTicks; if( uxPendedCounts > ( UBaseType_t ) 0U )
{
//do-while()循环体,循环次数为 uxPendedTicks do
{
if( xTaskIncrementTick() != pdFALSE ) //调用函数xTaskIncrementTick
{
}
else
{
xYieldPending = pdTRUE; //标记需要进行任务调度。
mtCOVERAGE_TEST_MARKER();
}
--uxPendedCounts;
} while( uxPendedCounts > ( UBaseType_t ) 0U );
uxPendedTicks = 0; //循环执行完毕,uxPendedTicks 清零
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/************************************************************************/
/****************************省略部分代码********************************/
/************************************************************************/
taskEXIT_CRITICAL();
return xAlreadyYielded;
}
(20)、uxPendedTicks 是个全局变量,在文件tasks.c 中有定义,任务调度器挂起以后此变量用来记录时钟节拍数。
(21)、有时候调用其他的API 函数会使用变量xYieldPending 来标记是否需要进行上下文切换,后面具体遇到具体分析。
(22)、返回xSwitchRequired 的值,xSwitchRequired 保存了是否进行任务切换的信息,如果为pdTRUE 的话就需要进行任务切换,pdFALSE 的话就不需要进行任务切换。函数xPortSysTickHandler()中调用xTaskIncrementTick()的时候就会判断返回值,并且根据返回值决定是否进行任务切换。