【freertos】009-任务控制及其实现细节
前言
本节描述任务相关的控制。
主要讲解使用,源码分析后面对应章节会有。
学习本节前,建议同学们往前回忆下任务控制块的内容。
参考:
任务控制主要是对任务控制块的处理。
比如任务延时、重置任务优先级、任务挂起与恢复。
对于延时相关的代码细节,可以参考前面的【freertos】007-系统节拍和系统延时管理实现细节章节详细分析。
9.1 相对延时
9.1.1 函数原型
void vTaskDelay( portTickTypexTicksToDelay );
9.1.2 函数说明
vTaskDelay()
用于相对延时,是指每次延时都是从任务执行函数vTaskDelay()
开始,延时指定的时间结束。xTicksToDelay
参数用于设置延迟的时钟节拍个数。- 延时的最大值宏在portmacro.h中有定义:
#define portMAX_DELAY (TickType_t )0xffffffffUL
9.1.3 参考例子
static void lzmTestTask(void* parameter)
{
/* task init */
printf("start lzmTestTask\r\n");
for(;;)
{
/* 任务主体 */
/* 延时1000个tick再跑 */
vTaskDelay(1000);
}
}
9.2 绝对延时
该功能可用于周期性任务,保证执行频率不变。
9.2.1 函数原型
BaseType_t vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );
9.2.2 函数说明
vTaskDelayUntil()
用于绝对延时,也叫周期性延时。想象下精度不高的定时器。pxPreviousWakeTime
参数是存储任务上次处于非阻塞状态时刻的变量地址。xTimeIncrement
参数用于设置周期性延时的时钟节拍个数。- 返回:
pdFALSE
说明延时失败。 - 使用此函数需要在FreeRTOSConfig.h配置文件中开启:
#defineINCLUDE_vTaskDelayUntil 1
- 需要保证周期性延时比任务主体运行时间长。
- 相对延时的意思是延时配置的N个节拍后恢复当前任务为就绪态。
- 绝对延时的意思是延时配置的N个节拍后该任务跑回到当前绝对延时函数。
9.2.3 参考例子
static void lzmTestTask(void* parameter)
{
portTickType last_wake_time = 0;
/* task init */
printf("start lzmTestTask\r\n");
/* 重置下该变量 */
last_wake_time = xTaskGetTickCount();
for(;;)
{
/* 再确保任务主体占用CPU时长不会超过周期值(1000tick)的情况下,
不管任务主体跑多长时间,1000tick后依然内跑回这里。 */
vTaskDelayUntil(&last_wake_time, 1000);
/* 任务主体 */
}
}
9.3 获取任务优先级
9.3.1 函数原型
UBaseType_t uxTaskPriorityGet(TaskHandle_t xTask );
9.3.2 函数说明
- 用于获取任务的优先级。
xTask
参数为任务句柄。传入NULL,表示获取当前调用该API的任务的优先级。- 使能方法:在FreeRTOSConfig.h中配置
INCLUDE_vTaskPriorityGet
为1。 - 返回:任务优先级。
9.3.3 uxTaskPriorityGet()源码分析
- 通过句柄获取任务控制块。
- 通过任务控制块获取任务优先级并返回。
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask )
{
TCB_t const * pxTCB;
UBaseType_t uxReturn;
taskENTER_CRITICAL(); /* 加入临界 */
{
/* 获取任务控制块 */
pxTCB = prvGetTCBFromHandle( xTask );
/* 通过任务控制块获取任务优先级 */
uxReturn = pxTCB->uxPriority;
}
taskEXIT_CRITICAL(); /* 退出临界 */
return uxReturn;
}
9.3.4 例子参考代码
void vAFunction( void )
{
TaskHandle_t xHandle;
/* 创建一个任务,存储该句柄 */
xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
/* 使用句柄获取创建的任务的优先级 */
if( uxTaskPriorityGet( xHandle ) != tskIDLE_PRIORITY )
{
/* 任务可以改变自己的优先级 */
}
// ...
/* 当前任务优先级比创建的任务优先级高? */
if( uxTaskPriorityGet( xHandle ) < uxTaskPriorityGet( NULL ) )
{
/* 当前优先级较高 */
}
}
9.4 设置任务优先级
任务优先级除了在创建时设置外,也可以在系统启动后重置,毕竟任务优先级的本质也只是任务控制块里面的一直成员值。
但是修改优先级时需要维护优先级继承机制。
9.4.1 函数原型
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
9.4.2 函数说明
作用:
- 该函数用于重置任务优先级。
- 如果设置的优先级高于当前正在执行的任务,则会在函数返回之前进行上下文切换。
参数:
xTask
:需要修改任务优先级的任务句柄。NULL时,表示修改当前任务的任务优先级。uxNewPriority
:新的任务优先级。在[0,configMAX_PRIORITIES - 1]范围内,否则会引起断言。
使能方法:使用该功能需要在FreeRTOSConfig.h中配置INCLUDE_vTaskPrioritySet
为1。
9.4.3 vTaskPrioritySet()源码分析
更改任务优先级的实现,是更改任务控制块里面记录的任务优先级值,但是需要维护好优先级继承机制。
看到了源码,产生两个疑问:
- 如果重置的优先级比优先级继承后的优先级还高,这种情况下为什么不更新该任务在用优先级?
- 重置优先级后,好像没有重置该任务在事件链表中的顺序。所以重置任务优先级不会更改任务在现有事件阻塞链表的顺序。
重置优先级简要步骤:
- 参数校验&参数纠正。
- 获取任务优先级。任务优先级包括基优先级和在用优先级。
- 任务调度需求检测。
- 迁移就绪链表。
- 触发任务调度。
9.4.3.1 参数校验
传入的优先级必须小于限制值,否则会触发断言。
/* 断言式参数校验 */
configASSERT( uxNewPriority < configMAX_PRIORITIES );
/* 参数纠正 */
if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
9.4.3.2 临界处理
重置任务优先级,涉及到就绪链表、事件链表的操作,而系统时钟节拍这些中断会设计到操作这些链表。
9.4.3.3 获取任务优先级
通过任务句柄获取任务控制块,通过任务控制块获取任务优先级。
如果使能了互斥量,及系统支持优先级继承机制时,需要区分基优先级uxBasePriority
和在用优先级uxPriority
。
/* 获取任务控制块 */
pxTCB = prvGetTCBFromHandle( xTask );
#if ( configUSE_MUTEXES == 1 )
{
/* 开启了互斥量就获取基优先级,处理优先级继承使用 */
uxCurrentBasePriority = pxTCB->uxBasePriority;
}
#else
{
/* 没有开启互斥量功能就直接获取优先级 */
uxCurrentBasePriority = pxTCB->uxPriority;
}
#endif
9.4.3.4 任务调度需求检查
在修改任务优先级前,先检查修改后是否需要进行任务调度,以下情况都需要任务调度:
- 新的任务优先级比当前在跑任务优先级高,需要标记触发任务调度。
- 把当前在跑任务优先级调低,需要标记触发任务调度。
实现代码如下:
/* 检查是否需要标记任务调度 */
if( uxNewPriority > uxCurrentBasePriority ) /* 新的优先级比基优先级更高了 */
{
if( pxTCB != pxCurrentTCB )
{
/* 如果需要修改的任务不是当前在跑任务,且新配置的优先级大于当前在跑的任务优先级,需要标记任务调度 */
if( uxNewPriority >= pxCurrentTCB->uxPriority )
{
/* 标记任务调度 */
xYieldRequired = pdTRUE;
}
}
else
{
/* 如果被提高优先级的任务已经在跑了,就不需要任务切换 */
}
}
else if( pxTCB == pxCurrentTCB ) /* 把当前任务优先级下调,也需要触发任务调度 */
{
/* 标记任务调度 */
xYieldRequired = pdTRUE;
}
else
{
/* 下调其它任务优先级,不需要调度 */
}
9.4.3.5 更新任务优先级
在更新任务优先级前,需要保存该任务在用优先级,等等用于迁移就绪链表。
/* 获取该任务当前使用的优先级 */
uxPriorityUsedOnEntry = pxTCB->uxPriority;
如果开启了互斥量功能,检查该任务是否处于优先级继承状态:
-
如果是,则不更新该任务在用优先级值。
- 源码是这样一个逻辑,但是本作者在这里保留个疑问:如果重置的优先级比优先级继承后的优先级还高,这种情况下为什么不更新该任务在用优先级?
-
如果不是,则需要更新该任务在用优先级值。
#if ( configUSE_MUTEXES == 1 )
{
/* 开启了互斥量功能,但是当前没有在优先级继承状态,可以更新当前任务在用优先级 */
if( pxTCB->uxBasePriority == pxTCB->uxPriority )
{
pxTCB->uxPriority = uxNewPriority;
}
/* 更新基优先级 */
pxTCB->uxBasePriority = uxNewPriority;
}
#else /* if ( configUSE_MUTEXES == 1 ) */
{
/* 没有开启互斥量功能就直接更新当前在用优先级 */
pxTCB->uxPriority = uxNewPriority;
}
#endif /* if ( configUSE_MUTEXES == 1 ) */
9.4.3.6 更新事件链表
按照作者的想法,任务优先级会影响到该任务在事件链表中的位置,所以也需要对事件链表处理。
由于事件链表节点值按功能装载不同的值:
- 一般情况下装载任务优先级,用于在事件链表中排序,如消息队列阻塞。
- 如果事件节点挂入了事件组,则装载的是事件组数据。
所以修改该值前先判断当前是否装载任务优先级。
/* 当前事件链表节点值是否被锁定。参考freertos事件组组件 */
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
/* 时间链表节点值没有被锁定,则默认用于保存任务优先级,用于事件链表排序。可更新事件链表节点值。 */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxNewPriority ) );
}
当前freertos官方提供的修改任务优先级API内事件链表处理代码就这。
按照作者的想法,如果更新了任务优先级到事件节点值。
也应该检查下当前任务是否阻塞在有序事件链表中,如消息队列,这些都是按照优先级插入事件链表的,解除阻塞是取应该排序在前的任务的。
9.4.3.7 迁移就绪链表
如果被修改任务优先级的任务在就绪链表,需要迁移到新的优先级就绪链表中。
该任务如果处于就绪态,会存在在用优先级的就绪链表中,而不是基优先级的就绪链表。
迁移就绪链表时需要注意,如果迁出就绪链表后,该链表没有就绪任务了,需要对系统任务优先级位图值uxTopReadyPriority
进行更新处理。
- 开启优先级检索优化功能后,
uxTopReadyPriority
该值是一个位图值。 - 关闭优先级检索优化功能后,
uxTopReadyPriority
该值就是系统就绪任务中最高优先级的值。
所以实现代码如下:
/* 判断被调节优先级的任务是否处于就绪态,如果是,需要迁移到新的优先级的就绪链表。 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
/* 解除任务所有状态,即是迁出当前就绪链表。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 如果当前就绪链表没有其它任务了,迁出就绪任务优先级位图值对应位。 */
portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
}
/* 根据新的优先级重新插入就绪链表 */
prvAddTaskToReadyList( pxTCB );
}
所有功能都实现后,触发任务调度,退出临界后,便可进入调度异常的回调进行任务调度。
9.4.4 参考例子
void vAFunction( void )
{
TaskHandle_t xHandle;
/* 创建一个任务,存储该句柄 */
xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
/* 使用句柄重置创建任务的优先级 */
vTaskPrioritySet( xHandle, tskIDLE_PRIORITY + 1 );
// ...
/* 传入null,重置当前任务优先级 */
vTaskPrioritySet( NULL, tskIDLE_PRIORITY + 1 );
}
9.5 挂起任务
9.5.1 函数原型
void vTaskSuspend( TaskHandle_t xTaskToSuspend );
9.5.2 函数说明
参数:xTaskToSuspend
:需要挂起的任务的任务句柄。为NULL时,挂起当前任务。
使能方法:在FreeRTOSConfig.h中配置INCLUDE_vTaskSuspend
为1。
作用:挂起一个任务。任务挂起后,插入到就挂起链表中,该任务不会被调度,也无权占用CPU。
配对使用API:调用vTaskResume()
恢复被挂起的任务到就绪链表。
9.5.3 vTaskSuspend()源码分析
9.5.3.1 进出临界
挂起任务的处理设计到任务状态链表和任务解除阻塞时间这些全局数据,而这些数据在滴答时钟或者其它中断回调中使用的后缀FromISR API中也可能用到。
所以为了维护这些数据的原子性,需要使用临界级别来实现。
进出临界使用的函数:
/* 进入临界 */
taskENTER_CRITICAL()
/* 退出临界 */
taskEXIT_CRITICAL()
9.5.3.2 获取任务控制块
/* 获取需要挂起的任务句柄。传入NULL,即获取当前任务的句柄。 */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
9.5.3.3 任务转为挂起态
切换任务状态不是设置某个任务状态值,而是把任务按规则放到各种状态链表。
- 先解除任务所有状态,即是把任务从对应状态链表中迁出:
- 移出后,如果返回0,说明当前链表没有挂载其它任务了,需要重新更新下系统就绪任务位图表。当然,虽然不知道该任务是不是挂起前是不是在就绪态,多做这步是没错的。另外,位图表需要开启优先级优化才生效。
/* 解除任务所有状态。即是把任务从状态链表中迁出。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 移出后如果当前优先级的就绪链表没有其它任务了,就需要重置下位图标。(开启了优先级优化功能才会生效) */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
- 解除任务状态后,也需要解除任务事件,从事件链表中移除当前任务:
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
/* 如果存在事件,需要从事件中移除。 */
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
- 然后就可以把当前任务挂载到挂起任务链表:
/* 把任务插入到挂起链表 */
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
- 还有任务通知需要处理。如果任务处于等待任务通知状态,如果收到任务通知,也可能从挂起链表中解除阻塞,所以必须解除任务通知状态到没有等待通知状态:
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) /* 任务通知功能 */
{
BaseType_t x;
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
/* 如果任务正在等待任务通知,则当任务被挂起时,需要清除这些任务通知。 */
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
完成以上四小步才算是把任务从其它状态切入到挂起态(是挂起任务的挂起态)。
9.5.3.4 刷新系统解除阻塞任务时间
为了防止挂起的任务是下一个需要解除阻塞的任务而导致系统提前进入检索解除阻塞任务的多余操作,这里可以刷新下解除阻塞任务的时间。
- 调度器启动了才会任务去跑,才会有任务进入限时阻塞。
- 维护系统解除阻塞任务时间的值需要在临界区内。
if( xSchedulerRunning != pdFALSE ) /* 调度器已经启动了 */
{
taskENTER_CRITICAL();
{
/* 如果调度器已经开启了,需要更新下一个需要解除任务阻塞的时间 */
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
9.5.3.5 任务调度器处理
如果挂起的任务是当前任务,那需要更新下pxCurrentTCB
值。
-
如果调度器已经启动了,挂起当前任务后,需要强制触发任务调度。
-
如果调度器还没有启动,挂起了当前任务,就需要更新
pxCurrentTCB
值即可。等待调度器启动后先跑pxCurrentTCB
。
-
如果全部任务都被挂起了,就设置
pxCurrentTCB
为空即可。下次创建任务或者恢复任务时会重置pxCurrentTCB
。至少会在启动调度器时会创建空闲任务,所以在启动调度器前不必在乎pxCurrentTCB
值是否为空。 -
如果不是全部任务都被挂起,那就从就绪表中选出最合适的任务到
pxCurrentTCB
。- 调用
vTaskSwitchContext()
,该任务的分析可以往前面的任务切换章节翻。
- 调用
if( pxTCB == pxCurrentTCB ) /* 挂起当前任务 */
{
if( xSchedulerRunning != pdFALSE )
{
/* 调度器正常运行,需要强制触发任务调度,把任务切走 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
{
/* 如果所有任务都被挂起了,就把pxCurrentTCB值标记为空 */
pxCurrentTCB = NULL;
}
else
{
/* 找出新的pxCurrentTCB值 */
vTaskSwitchContext();
}
}
}
9.6 恢复任务
9.6.1 函数原型
void vTaskResume( TaskHandle_t xTaskToResume );
9.6.2 函数说明
xTaskToResume
:需要解除挂起的任务句柄。
INCLUDE_vTaskSuspend
必须定义为vTaskSuspend() 1,这个函数才生效。
该函数用于解除挂起的任务。
被一个或多个vTaskSuspend()
调用挂起的任务将通过对vTaskResume()
的单个调用重新可用。
9.6.3 实现分析
解除任务的挂起态的实现比较简单,主要思路:
- 通过任务句柄找到任务控制块。
- 判断该任务是否处于挂起态,就是判断当前任务的状态节点是否挂载在挂起链表。
- 把当前任务从挂起链表迁到就绪链表。
- 如果解除挂起态的任务优先级更高或相等,就触发一次任务调度。
9.6.4 完整代码实现
vTaskResume()
:
#if ( INCLUDE_vTaskSuspend == 1 ) /* 使能 */
void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = xTaskToResume;
/* 任务句柄不能为NULL */
configASSERT( xTaskToResume );
/* 正在跑的任务在运行态,不用处理。 */
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
{
taskENTER_CRITICAL(); /* 进入临界。因为下面操作涉及任务状态表等系统相关的全局变量。 */
{
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) /* 如果该任务处于挂起态 */
{
/* 从挂起链表迁出 */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
/* 重新插入到就绪链表 */
prvAddTaskToReadyList( pxTCB );
/* 如果恢复的任务的优先级更高,就触发任务调度。 */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/* 触发任务调度 */ taskYIELD_IF_USING_PREEMPTION();
}
}
}
taskEXIT_CRITICAL(); /* 退出临界 */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskSuspend */
prvTaskIsTaskSuspended()
:
#if ( INCLUDE_vTaskSuspend == 1 ) /* 使能 */
static BaseType_t prvTaskIsTaskSuspended( const TaskHandle_t xTask )
{
BaseType_t xReturn = pdFALSE;
const TCB_t * const pxTCB = xTask;
/* 访问xPendingReadyList,因此必须从临界区调用。所以需要在调用本函数前进入。 */
/* 检查在跑任务是否挂起是没有意义的 */
configASSERT( xTask );
/* 检查该任务的状态 */
if( listIS_CONTAINED_WITHIN( &xSuspendedTaskList, &( pxTCB->xStateListItem ) ) != pdFALSE ) /* 该任务挂载在挂起链表 */
{
if( listIS_CONTAINED_WITHIN( &xPendingReadyList, &( pxTCB->xEventListItem ) ) == pdFALSE )/* 该任务不是因为调度器挂起而暂时放到挂起链表的 */
{
/* 再判断该任务是否是因为等待事件而永久阻塞的,如果是,也不属于挂起态。 */
if( listIS_CONTAINED_WITHIN( NULL, &( pxTCB->xEventListItem ) ) != pdFALSE )
{
xReturn = pdTRUE;
}
}
}
return xReturn;
}
#endif /* INCLUDE_vTaskSuspend */
9.6.5 参考例子
void vAFunction( void )
{
TaskHandle_t xHandle;
/* 创建一个任务,存储该句柄 */
xTaskCreate( vTaskCode, "NAME", STACK_SIZE, NULL, tskIDLE_PRIORITY, &xHandle );
// ...
/* 挂起这个刚创建的任务 */
vTaskSuspend( xHandle );
// ...
/* 挂起当前在跑任务 */
vTaskSuspend( NULL );
/* 在被其它任务恢复当前任务前,是不会跑到这里的 */
}
附件
重置任务优先级:vTaskPrioritySet()
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority )
{
TCB_t * pxTCB;
UBaseType_t uxCurrentBasePriority, uxPriorityUsedOnEntry;
BaseType_t xYieldRequired = pdFALSE;
/* 断言式参数校验 */
configASSERT( uxNewPriority < configMAX_PRIORITIES );
/* 参数纠正 */
if( uxNewPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxNewPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 进入临界处理 */
taskENTER_CRITICAL();
{
/* 获取任务控制块 */
pxTCB = prvGetTCBFromHandle( xTask );
traceTASK_PRIORITY_SET( pxTCB, uxNewPriority );
#if ( configUSE_MUTEXES == 1 )
{
/* 开启了互斥量就获取基优先级,处理优先级继承使用 */
uxCurrentBasePriority = pxTCB->uxBasePriority;
}
#else
{
/* 没有开启互斥量功能就直接获取优先级 */
uxCurrentBasePriority = pxTCB->uxPriority;
}
#endif
/* 新配置的优先级和原有的优先级不一样才会处理 */
if( uxCurrentBasePriority != uxNewPriority )
{
/* 检查是否需要标记任务调度 */
if( uxNewPriority > uxCurrentBasePriority ) /* 新的优先级比基优先级更高了 */
{
if( pxTCB != pxCurrentTCB )
{
/* 如果需要修改的任务不是当前在跑任务,且新配置的优先级大于当前在跑的任务优先级,需要标记任务调度 */
if( uxNewPriority >= pxCurrentTCB->uxPriority )
{
/* 标记任务调度 */
xYieldRequired = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 如果被提高优先级的任务已经在跑了,就不需要任务切换 */
}
}
else if( pxTCB == pxCurrentTCB ) /* 把当前任务优先级下调,也需要触发任务调度 */
{
/* 标记任务调度 */
xYieldRequired = pdTRUE;
}
else
{
/* 下调其它任务优先级,不需要调度 */
}
/* 记录该任务当前使用的优先级 */
uxPriorityUsedOnEntry = pxTCB->uxPriority;
#if ( configUSE_MUTEXES == 1 )
{
/* 开启了互斥量功能,但是当前没有在优先级继承状态,可以更新当前任务在用优先级 */
if( pxTCB->uxBasePriority == pxTCB->uxPriority )
{
pxTCB->uxPriority = uxNewPriority;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 更新基优先级 */
pxTCB->uxBasePriority = uxNewPriority;
}
#else /* if ( configUSE_MUTEXES == 1 ) */
{
/* 没有开启互斥量功能就直接更新当前在用优先级 */
pxTCB->uxPriority = uxNewPriority;
}
#endif /* if ( configUSE_MUTEXES == 1 ) */
/* 当前事件链表节点值是否被锁定。参考freertos事件组组件 */
if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
{
/* 时间链表节点值没有被锁定,则默认用于保存任务优先级,用于事件链表排序。可更新事件链表节点值。 */
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxNewPriority ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 判断被调节优先级的任务是否处于就绪态,如果是,需要迁移到新的优先级的就绪链表。 */
if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ uxPriorityUsedOnEntry ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
{
/* 解除任务所有状态,即是迁出当前就绪链表。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 如果当前就绪链表没有其它任务了,迁出就绪任务优先级位图值对应位。 */
portRESET_READY_PRIORITY( uxPriorityUsedOnEntry, uxTopReadyPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 根据新的优先级重新插入就绪链表 */
prvAddTaskToReadyList( pxTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( xYieldRequired != pdFALSE )
{
/* 触发任务调度 */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 编译警告处理 */
( void ) uxPriorityUsedOnEntry;
}
}
/* 退出临界 */
taskEXIT_CRITICAL();
}
挂起任务:vTaskSuspend()
#if ( INCLUDE_vTaskSuspend == 1 )
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t * pxTCB;
taskENTER_CRITICAL();
{
/* 获取需要挂起的任务句柄。传入NULL,即获取当前任务的句柄。 */
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
traceTASK_SUSPEND( pxTCB );
/* 解除任务所有状态。即是把任务从状态链表中迁出。 */
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
/* 移出后如果当前优先级的就绪链表没有其它任务了,就需要重置下位图标。(开启了优先级优化功能才会生效) */
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
/* 如果存在事件,需要从事件中移除。 */
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 把任务插入到挂起链表 */
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) /* 任务通知功能 */
{
BaseType_t x;
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
/* 如果任务正在等待任务通知,则当任务被挂起时,需要清除这些任务通知。 */
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
taskENTER_CRITICAL();
{
/* 如果调度器已经开启了,需要更新下一个需要解除任务阻塞的时间 */
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
if( pxTCB == pxCurrentTCB ) /* 挂起当前任务 */
{
if( xSchedulerRunning != pdFALSE )
{
/* 调度器正常运行,需要强制触发任务调度,把任务切走 */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
else
{
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
{
/* 如果所有任务都被挂起了,就把pxCurrentTCB值标记为空 */
pxCurrentTCB = NULL;
}
else
{
/* 找出新的pxCurrentTCB值 */
vTaskSwitchContext();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* INCLUDE_vTaskSuspend */