05.FreeRTOS任务挂起与恢复

01.初识freeRTOS
02.FreeRTOS的移植
03.FreeRTOS系统配置
04.FreeRTOS任务创建与删除
05.FreeRTOS任务挂起与恢复
06.FreeRTOS中断管理
07.FreeRTOS列表与列表项
08.FreeRTOS任务调度与任务切换
09.FreeRTOS时间片调度与任务相关函数
10.FreeRTOS队列操作
11.FreeRTOS信号量操作
12.FreeRTOS队列集和事件标志组
13.FreeRTOS任务通知
14.FreeRTOS软件定时器
15.FreeRTOS低功耗
16.FreeRTOS内存管理

05. FreeRTOS任务挂起与任务恢复

1. FreeRTOS 挂起和恢复任务相关 API 函数

函数描述
vTaskSuspend()挂起任务
vTaskResume()恢复被挂起的任务
xTaskResumeFromISR()在中断中恢复被挂起的任务
  1. 函数vTaskSuspend()

    此函数用于挂起任务,若使用此函数,需要在FreeRTOSConfig.h文件中将宏 NCLUDE_vTaskSuspend配置为1。无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复。此函数并不支持嵌套,不论使用此函数重复挂起任务多少次,只需调用一次恢复任务的函数,那么任务就不再被挂起。函数原型如下所示:

    在这里插入图片描述

  2. 函数vTaskResume()

    此函数用于在任务中恢复被挂起的任务,若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_VTaskSuspend配置为1。不论一个任务被函数vTaskSuspend()挂起多少次,只需要使用函数vTaskResume()恢复一次,就可以继续运行。函数原型如下所示:

    在这里插入图片描述

  3. 函数xTaskResumeFromISR()

    此函数用于在中断中恢复被挂起的任务,若使用此函数,需要在FreeRTOSConfig.h文件中将宏INCLUDE_xTaskResumeFromISR配置为1。不论一个任务被函数vTaskSuspend()挂起多少次,只需要使用函数vTaskResumeFromISR()恢复一次,就可以继续运行。函数原型如下所示:

    在这里插入图片描述

2. vTaskSuspend源码分析

在这里插入图片描述

  1. 需将宏INCLUDE_vTaskSuspend 配置为 1

    // 使能挂起任务 
    #define INCLUDE_vTaskSuspend                            1                       
    
  2. 根据任务句柄获取任务控制块,如果任务句柄为NULL,表示挂起任务自身

    pxTCB = prvGetTCBFromHandle( xTaskToSuspend );
    
  3. 将要挂起的任务从状态列表中移除

    //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();
    }
    
  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 )
                 {  
                      pxTCB->ucNotifyState[ x ] = taskNOT_WAITING_NOTIFICATION;
                 }
         }
    }
    #endif 
    
  5. 判断任务调度器是否在运行

    //如果在运行,则更新下一次阻塞时间,防止被挂起任务为下一次阻塞超时任务
    if(xSchedulerRunning != pdFALSE)
    {
         taskENTER_CRITICAL();
         {
              prvResetNextTaskUnblockTime();
         }
         taskEXIT_CRITICAL();
    }
    else
    {
         mtCOVERAGE_TEST_MARKER();
    }
    
  6. 如果挂起的是当前任务,且调度器正在运行,需要进行一次任务切换,否则判断挂起任务数

    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源码分析

在这里插入图片描述

  1. 需将宏INCLUDE_vTaskSuspend配置为1

    #if ( INCLUDE_vTaskSuspend == 1 )
    
  2. 恢复任务不能是正在运行的任务

    if( ( pxTCB != pxCurrentTCB ) && ( pxTCB != NULL ) )
    
  3. 判断任务是否被挂起

    //若被挂起,就会将该任务从挂起列表中移除,将该任务添加到就绪列表中
    if( prvTaskIsTaskSuspended( pxTCB ) != pdFALSE )
    
  4. 判断恢复任务优先级是否大于当前正在运行的任务优先级

    //若是,要进行任务切换
    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源码分析

在这里插入图片描述

  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 )
    	{
    		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();
    }
    
  4. 将前面保存的basepri的值,恢复回来

    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );
    
  5. 返回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显示屏计数开始工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值