05. FreeRTOS任务挂起与任务恢复
1. FreeRTOS 挂起和恢复任务相关 API 函数
函数 | 描述 |
---|---|
vTaskSuspend() | 挂起任务 |
vTaskResume() | 恢复被挂起的任务 |
xTaskResumeFromISR() | 在中断中恢复被挂起的任务 |
-
函数vTaskSuspend()
此函数用于挂起任务,若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏NCLUDE_vTaskSuspend
配置为1
。无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。此函数并不支持嵌套,不论使用此函数重复挂起任务多少次,只需调用一次恢复任务的函数,那么任务就不再被挂起。函数原型如下所示: -
函数vTaskResume()
此函数用于在任务中恢复被挂起的任务,若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏INCLUDE_VTaskSuspend
配置为1
。不论一个任务被函数vTaskSuspend()
挂起多少次,只需要使用函数vTaskResume()
恢复一次,就可以继续运行。函数原型如下所示: -
函数xTaskResumeFromISR()
此函数用于在中断中恢复被挂起的任务,若使用此函数,需要在
FreeRTOSConfig.h
文件中将宏INCLUDE_xTaskResumeFromISR
配置为1
。不论一个任务被函数vTaskSuspend()
挂起多少次,只需要使用函数vTaskResumeFromISR()
恢复一次,就可以继续运行。函数原型如下所示:
2. vTaskSuspend源码分析
-
需将宏
INCLUDE_vTaskSuspend
配置为 1// 使能挂起任务 #define INCLUDE_vTaskSuspend 1
-
根据任务句柄获取任务控制块,如果任务句柄为NULL,表示挂起任务自身
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
-
将要挂起的任务从状态列表中移除
//3.将要挂起的任务从状态列表中移除 if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 ) { taskRESET_READY_PRIORITY( pxTCB->uxPriority ); } else { mtCOVERAGE_TEST_MARKER(); } //3.将要挂起的任务从事件列表中移除 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++ ) { //如果待挂起任务在等待,停止等待,直接赋值为0 if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION ) { pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION; } } } #endif
-
判断任务调度器是否在运行
//如果在运行,则更新下一次阻塞时间,防止被挂起任务为下一次阻塞超时任务 if(xSchedulerRunning != pdFALSE) { taskENTER_CRITICAL(); { prvResetNextTaskUnblockTime(); } taskEXIT_CRITICAL(); } else { mtCOVERAGE_TEST_MARKER(); }
-
如果挂起的是当前任务,且调度器正在运行,需要进行一次任务切换,否则判断挂起任务数
if( pxTCB == pxCurrentTCB ) { //且调度器正在运行,需要进行一次任务切换 if( xSchedulerRunning != pdFALSE ) { configASSERT( uxSchedulerSuspended == 0 ); portYIELD_WITHIN_API(); } //若没有运行,判断挂起任务数是否等于任务总数 //若相等,当前控制块赋值为NULL //若不相等,寻找下一个最高优先级任务 else { if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) { pxCurrentTCB = NULL; } else { vTaskSwitchContext(); } } } else { mtCOVERAGE_TEST_MARKER(); }
具体实现:
#if ( INCLUDE_vTaskSuspend == 1 )
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
{
TCB_t * pxTCB;
taskENTER_CRITICAL();
{
/* If null is passed in here then it is the running task that is
* being suspended. */
//2.根据任务句柄获取任务控制块,如果任务句柄为NULL,表示挂起任务自身
pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
traceTASK_SUSPEND( pxTCB );
/* Remove task from the ready/delayed list and place in the
* suspended list. */
//3.将要挂起的任务从状态列表中移除
if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
{
taskRESET_READY_PRIORITY( pxTCB->uxPriority );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* Is the task waiting on an event also? */
//3.将要挂起的任务从事件列表中移除
if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
{
( void ) uxListRemove( &( pxTCB->xEventListItem ) );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//4.将待挂起任务的任务状态列表插入挂起任务列表末尾
vListInsertEnd( &xSuspendedTaskList, &( pxTCB->xStateListItem ) );
#if ( configUSE_TASK_NOTIFICATIONS == 1 )
{
BaseType_t x;
for( x = 0; x < configTASK_NOTIFICATION_ARRAY_ENTRIES; x++ )
{
//如果待挂起任务在等待,停止等待,直接赋值为0
if( pxTCB->ucNotifyState[ x ] == taskWAITING_NOTIFICATION )
{
/* The task was blocked to wait for a notification, but is
* now suspended, so no notification was received. */
pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
}
}
}
#endif /* if ( configUSE_TASK_NOTIFICATIONS == 1 ) */
}
taskEXIT_CRITICAL();
//5.判断任务调度器是否在运行
//如果在运行,则更新下一次阻塞时间,防止被挂起任务为下一次阻塞超时任务
if( xSchedulerRunning != pdFALSE )
{
/* Reset the next expected unblock time in case it referred to the
* task that is now in the Suspended state. */
taskENTER_CRITICAL();
{
prvResetNextTaskUnblockTime();
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
//6.如果挂起的是当前任务,
if( pxTCB == pxCurrentTCB )
{
//且调度器正在运行,需要进行一次任务切换
if( xSchedulerRunning != pdFALSE )
{
/* The current task has just been suspended. */
configASSERT( uxSchedulerSuspended == 0 );
portYIELD_WITHIN_API();
}
//若没有运行,判断挂起任务数是否等于任务总数
//若相等,当前控制块赋值为NULL
//若不相等,寻找下一个最高优先级任务
else
{
/* The scheduler is not running, but the task that was pointed
* to by pxCurrentTCB has just been suspended and pxCurrentTCB
* must be adjusted to point to a different task. */
if( listCURRENT_LIST_LENGTH( &xSuspendedTaskList ) == uxCurrentNumberOfTasks ) /*lint !e931 Right has no side effect, just volatile. */
{
/* No other tasks are ready, so set pxCurrentTCB back to
* NULL so when the next task is created pxCurrentTCB will
* be set to point to it no matter what its relative priority
* is. */
pxCurrentTCB = NULL;
}
else
{
vTaskSwitchContext();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
3. vTaskResume源码分析
-
需将宏INCLUDE_vTaskSuspend配置为1
#if ( INCLUDE_vTaskSuspend == 1 )
-
恢复任务不能是正在运行的任务
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
-
判断任务是否被挂起
//若被挂起,就会将该任务从挂起列表中移除,将该任务添加到就绪列表中 if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
-
判断恢复任务优先级是否大于当前正在运行的任务优先级
//若是,要进行任务切换 if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); }
具体实现:
//1.需将宏INCLUDE_vTaskSuspend配置为1
#if ( INCLUDE_vTaskSuspend == 1 )
void vTaskResume( TaskHandle_t xTaskToResume )
{
TCB_t * const pxTCB = xTaskToResume;
/* It does not make sense to resume the calling task. */
configASSERT( xTaskToResume );
/* The parameter cannot be NULL as it is impossible to resume the
* currently executing task. */
//2.恢复任务不能是正在运行的任务
if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
{
taskENTER_CRITICAL();
{
//3.判断任务是否被挂起
//若被挂起,就会将该任务从挂起列表中移除,将该任务添加到就绪列表中
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
{
traceTASK_RESUME( pxTCB );
/* The ready list can be accessed even if the scheduler is
* suspended because this is inside a critical section. */
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
/* A higher priority task may have just been resumed. */
//4.判断恢复任务优先级是否大于当前正在运行的任务优先级
//若是,要进行任务切换
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
/* This yield may not cause the task just resumed to run,
* but will leave the lists in the correct state for the
* next yield. */
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
4. xTaskResumeFromISR源码分析
-
用于检测调用freertos的API函数的中断优先级是否在管理范围内,以及是否全部设置为抢占式优先级位
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
-
关闭freertos可管理中断,防止被其他的中断打断,并返回关闭前basepri寄存器的值
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
-
判断是否有挂起任务
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE ) { traceTASK_RESUME_FROM_ISR( pxTCB ); /* Check the ready lists can be accessed. */ //检测调度器是否被挂起 //判断恢复的这个任务优先级是否大于正在执行的任务是的话将xYieldRequired标记 //为pdTRUE,表示需要进行一次任务切换 //将被恢复的任务从挂起列表中移除 //插入到就绪列表 if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE ) { if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority ) { xYieldRequired = pdTRUE; xYieldPending = pdTRUE; } else { mtCOVERAGE_TEST_MARKER(); } ( void ) uxListRemove( &( pxTCB->xStateListItem ) ); prvAddTaskToReadyList( pxTCB ); } //如果调度器被挂起了,就将恢复的任务插入等待就绪列表,直到调度器被恢复再进行任务的处理 else { vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) ); } } else { mtCOVERAGE_TEST_MARKER(); }
-
将前面保存的basepri的值,恢复回来
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
-
返回xYieldRequired的值 用于决定是否需要进行任务切换
return xYieldRequired;
具体实现:
#if ( ( INCLUDE_xTaskResumeFromISR == 1 ) && ( INCLUDE_vTaskSuspend == 1 ) )
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume )
{
BaseType_t xYieldRequired = pdFALSE;
TCB_t * const pxTCB = xTaskToResume;
UBaseType_t uxSavedInterruptStatus;
configASSERT( xTaskToResume );
/* RTOS ports that support interrupt nesting have the concept of a
* maximum system call (or maximum API call) interrupt priority.
* Interrupts that are above the maximum system call priority are keep
* permanently enabled, even when the RTOS kernel is in a critical section,
* but cannot make any calls to FreeRTOS API functions. If configASSERT()
* is defined in FreeRTOSConfig.h then
* portASSERT_IF_INTERRUPT_PRIORITY_INVALID() will result in an assertion
* failure if a FreeRTOS API function is called from an interrupt that has
* been assigned a priority above the configured maximum system call
* priority. Only FreeRTOS functions that end in FromISR can be called
* from interrupts that have been assigned a priority at or (logically)
* below the maximum system call interrupt priority. FreeRTOS maintains a
* separate interrupt safe API to ensure interrupt entry is as fast and as
* simple as possible. More information (albeit Cortex-M specific) is
* provided on the following link:
* https://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html */
//1.用于检测调用freertos的API函数的中断优先级是否在管理范围内,以及是否全部设置为抢占式优先级位
portASSERT_IF_INTERRUPT_PRIORITY_INVALID();
//2.关闭freertos可管理中断,防止被其他的中断打断,并返回关闭前basepri寄存器的值
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
{
//3.判断是否有挂起任务
if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
{
traceTASK_RESUME_FROM_ISR( pxTCB );
/* Check the ready lists can be accessed. */
//检测调度器是否被挂起
//判断恢复的这个任务优先级是否大于正在执行的任务是的话将xYieldRequired标记
//为pdTRUE,表示需要进行一次任务切换
//将被恢复的任务从挂起列表中移除
//插入到就绪列表
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
/* Ready lists can be accessed so move the task from the
* suspended list to the ready list directly. */
if( pxTCB->uxPriority >= pxCurrentTCB->uxPriority )
{
xYieldRequired = pdTRUE;
/* Mark that a yield is pending in case the user is not
* using the return value to initiate a context switch
* from the ISR using portYIELD_FROM_ISR. */
xYieldPending = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
( void ) uxListRemove( &( pxTCB->xStateListItem ) );
prvAddTaskToReadyList( pxTCB );
}
//如果调度器被挂起了,就将恢复的任务插入等待就绪列表,直到调度器被恢复再进行任务的处理
else
{
/* The delayed or ready lists cannot be accessed so the task
* is held in the pending ready list until the scheduler is
* unsuspended. */
vListInsertEnd( &( xPendingReadyList ), &( pxTCB->xEventListItem ) );
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
//4.将前面保存的basepri的值,恢复回来
portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
//5.返回xYieldRequired的值 用于决定是否需要进行任务切换
return xYieldRequired;
}
#endif
5. 实验验证
实验目的:
任务函数:
-
开始任务:
/*开始任务*/ void start_task(void* pvParamter) { taskENTER_CRITICAL(); // 进入临界区 xTaskCreate((TaskFunction_t ) task1, //指向任务函数的指针 (char * ) "task1", //任务名称 (configSTACK_DEPTH_TYPE) TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) TASK1_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &task1_task_handler //任务句柄:任务控制块 ); xTaskCreate((TaskFunction_t ) task2, //指向任务函数的指针 (char * ) "task2", //任务名称 (configSTACK_DEPTH_TYPE) TASK2_TASK_STACK_SIZE, //任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) TASK2_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &task2_task_handler //任务句柄:任务控制块 ); xTaskCreate((TaskFunction_t ) task3, //指向任务函数的指针 (char * ) "task3", //任务名称 (configSTACK_DEPTH_TYPE) TASK3_TASK_STACK_SIZE, //任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) TASK3_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &task3_task_handler //任务句柄:任务控制块 ); vTaskDelete(NULL); taskEXIT_CRITICAL(); // 退出临界区 }
-
任务一:实现LED0每500ms翻转一次
void task1(void* pvParamter) { uint32_t task1_num = 0; while(1) { printf("task1正在运行!!!\r\n"); lcd_show_xnum(71, 80, ++task1_num, 3, 16, 0x80, GREEN); LED0_TOGGLE(); vTaskDelay(500); } }
-
任务二:实现LED1每500ms翻转一次
void task2(void* pvParamter) { uint32_t task2_num = 0; while(1) { printf("task2正在运行!!!\r\n"); lcd_show_xnum(191, 80, ++task2_num, 3, 16, 0x80, GREEN); LED1_TOGGLE(); vTaskDelay(500); } }
-
任务三:挂起和恢复任务一
void task3(void* pvParamter) { uint8_t key = 0; while(1) { printf("task3正在运行!!!\r\n"); key = key_scan(0); switch(key) { case KEY0_PRES: vTaskSuspend(task1_task_handler); break; case KEY1_PRES: vTaskResume(task1_task_handler); break; default: break; } vTaskDelay(10); } }
-
初始任务的创建与开启任务调度
xTaskCreate((TaskFunction_t ) start_task, //指向任务函数的指针 (char * ) "start_task", //任务名称 (configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,//任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) START_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &start_task_handler //任务句柄:任务控制块 ); vTaskStartScheduler(); //开启任务调度
实验结果:
LCD显示屏与LED灯同时工作,LCD显示运行的次数,当按下按键,运行次数暂停,LED灯不再闪烁。当按下KEY1恢复任务后,任务1继续运行,LED灯与LCD显示屏计数开始工作。