# 任务状态

任务目前存在四种状态,分为:运行、就绪、阻塞、挂起;

  1. Running — 运行态
    这是任务在执行的时候的状态,处在运行态意味着任务获得 CPU 的使用权,对于单核 CPU,此时不存在其他运行态的任务。
  2. Ready — 就绪态
    处在就绪态意味着这个任务是可以执行的,比如某个事件发生、队列数据到来、所请求的资源有效等;但是,因为此时有一个相同优先级或者更高优先级的任务正在运行,此时任务无法执行而处于就绪态。
  3. Blocked — 阻塞态
    如果一个任务正在等待一个时间到来或者外部的事件到来,比如一个调用 vTaskDelay() 函数的任务,在延时时间到来之前,任务将处在阻塞状态。当然,任务在等待队列、信号量、事件组、通知、信号量事件时都会处在阻塞态。通常,这些等待都会设置一个超时 timeout 时间,当等待超时是将任务从阻塞态退出,防止任务被无限挂起。处在阻塞态的任务不占用任何 CPU 时间。
  4. Suspended — 挂起态
    挂起态无法进入运行态,同时不像阻塞态有超时时间,挂起态无超时时间,除非调用 vTaskResume() 推出挂起态。相应的调用 vTaskSuspend() 将一个任务挂起。挂起态可以描述成 “冻结”;除非 “解冻”,否则无法再有机会被执行到。

它们的状态关系图:

img

# 任务优先级

每个任务在创建的时候都要指定一个从 0configMAX_PRIORITIES - 1 的优先级,与其他 RTOS 不同之处在于,优先级数值越大,优先级越低。 configMAX_PRIORITIES 定义在 freeRTOSConfig.h 头文件中。

configMAX_PRIORITIES 的值可以是任意值,也就是说 FreeRTOS 的任务数量不受限制。事实上,因为 FreeRTOS 允许任务共享同一个优先级, configMAX_PRIORITIES 的大小不会限制到任务数量。但是对于实际应用来说,因为 FreeRTOS 中即使未用到的优先级,任然会占用一定的内存,因此优先级的最大数值,应与实际应用贴近。

freeRTOSConfig.h 头文件中,定义了 configUSE_PORT_OPTIMISED_TASK_SELECTION ,拥有某些存在任务选择优化机制的架构,此时最大优先级不能超过 32。

FreeRTOS 中的空闲任务 IDEL Task 总是最低优先级,因此它的优先级为 0。

FreeRTOS 的调度器总是确保当前得到 CPU 时间的任务的优先级是最高的,换句话说,就绪态中高优先级的任务,总是会优先进入运行态。

对于共用同一优先级的任务,如果 configUSE_TIME_SLICING 未定义或者定义为 1 , 则 FreeRTOS 将使用时间片轮询的机制去调度这些任务。

跟多具体的配置信息可以去看 FreeRTOS 篇章之 FreeRTOSConfig.h 分析

# 任务创建 - xTaskCreate ()

xTaskCreate () API 函数原型实现:

#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
	BaseType_t xTaskCreate(	TaskFunction_t pxTaskCode,
				const char * const pcName,
				const uint16_t usStackDepth,
				void * const pvParameters,
				UBaseType_t uxPriority,
				TaskHandle_t * const pxCreatedTask ) /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
	{
	TCB_t *pxNewTCB;
	BaseType_t xReturn;
		/* If the stack grows down then allocate the stack then the TCB so the stack
		does not grow into the TCB.  Likewise if the stack grows up then allocate
		the TCB then the stack. */
		#if( portSTACK_GROWTH > 0 )
		{
			/* Allocate space for the TCB.  Where the memory comes from depends on
			the implementation of the port malloc function and whether or not static
			allocation is being used. */
			pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );
			if( pxNewTCB != NULL )
			{
				/* Allocate space for the stack used by the task being created.
				The base of the stack memory stored in the TCB so the task can
				be deleted later if required. */
				pxNewTCB->pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
				if( pxNewTCB->pxStack == NULL )
				{
					/* Could not allocate the stack.  Delete the allocated TCB. */
					vPortFree( pxNewTCB );
					pxNewTCB = NULL;
				}
			}
		}
		#else /* portSTACK_GROWTH */
		{
		StackType_t *pxStack;
			/* Allocate space for the stack used by the task being created. */
			pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
			if( pxStack != NULL )
			{
				/* Allocate space for the TCB. */
				pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); /*lint !e961 MISRA exception as the casts are only redundant for some paths. */
				if( pxNewTCB != NULL )
				{
					/* Store the stack location in the TCB. */
					pxNewTCB->pxStack = pxStack;
				}
				else
				{
					/* The stack cannot be used as the TCB was not created.  Free
					it again. */
					vPortFree( pxStack );
				}
			}
			else
			{
				pxNewTCB = NULL;
			}
		}
		#endif /* portSTACK_GROWTH */
		if( pxNewTCB != NULL )
		{
			#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
			{
				/* Tasks can be created statically or dynamically, so note this
				task was created dynamically in case it is later deleted. */
				pxNewTCB->ucStaticallyAllocated = tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
			}
			#endif /* configSUPPORT_STATIC_ALLOCATION */
			prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL );
			prvAddNewTaskToReadyList( pxNewTCB );
			xReturn = pdPASS;
		}
		else
		{
			xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
		}
		return xReturn;
	}
#endif /* configSUPPORT_DYNAMIC_ALLOCATION */

可以看到,该函数是受宏 configSUPPORT_DYNAMIC_ALLOCATION 控制的,该宏位于 FreeRTOSConfig.h 文件中。

传入参数:

  • pvTaskCode:指向任务函数入口的指针,是一个永不退出的 C 函数,实现常通常是一个死循环。
  • pcName:具有描述性的任务名。这个参数不会被 FreeRTOS 使用,其只是单纯地用于辅助调试;通过定义常量 config_MAX_TASK_NAME_LEN 来定义任务名的最大长度(包括 \0 结束符),该宏位于 FreeRTOSConfig.h 文件中。
  • usStackDepth:当任务创建时,用于告诉内核为它分配多大的栈空间,这个值指定的是栈空间可以保存多少个字(word),而不是多少个字节(byte)。
  • pvParameters:任务函数接受一个指向 void 的指针(void *),即是传递到任务中的值。
  • uxPriority:用于指定任务执行的优先级。优先级的取值范围可以从最低优先级 ( 0 ) 到最高优先级 ( configMAX_PRIORITIES – 1 ); configMAX_PRIORITIES 是一个由用户定义的常量,该宏位于 FreeRTOSConfig.h 文件中。
  • pxCreatedTask:用于传出任务的句柄。这个句柄将在 API 调用中对该创建出来的任务进行引用,比如改变任务优先级,或者删除任务;如果应用程序中不会用到这个任务的句柄,则 pxCreatedTask 可以被设为 NULL。

返回参数(有两个可能的返回值):

  • pdTRUE:表明任务创建成功。
  • errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY:由于内存堆空间不足,FreeRTOS 无法分配足够的空间来保存任务结构数据和任务栈,而无法创建任务。

# 任务删除 - vTaskDelete ()

vTaskDelete () API 函数原型实现:

#if ( INCLUDE_vTaskDelete == 1 )
	void vTaskDelete( TaskHandle_t xTaskToDelete )
	{
	TCB_t *pxTCB;
		taskENTER_CRITICAL();
		{
			/* If null is passed in here then it is the calling task that is
			being deleted. */
			pxTCB = prvGetTCBFromHandle( xTaskToDelete );
			/* Remove task from the ready list. */
			if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
			{
				taskRESET_READY_PRIORITY( pxTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			/* Is the task waiting on an event also? */
			if( listLIST_ITEM_CONTAINER( &( pxTCB->xEventListItem ) ) != NULL )
			{
				( void ) uxListRemove( &( pxTCB->xEventListItem ) );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
			/* Increment the uxTaskNumber also so kernel aware debuggers can
			detect that the task lists need re-generating.  This is done before
			portPRE_TASK_DELETE_HOOK() as in the Windows port that macro will
			not return. */
			uxTaskNumber++;
			if( pxTCB == pxCurrentTCB )
			{
				/* A task is deleting itself.  This cannot complete within the
				task itself, as a context switch to another task is required.
				Place the task in the termination list.  The idle task will
				check the termination list and free up any memory allocated by
				the scheduler for the TCB and stack of the deleted task. */
				vListInsertEnd( &xTasksWaitingTermination, &( pxTCB->xStateListItem ) );
				/* Increment the ucTasksDeleted variable so the idle task knows
				there is a task that has been deleted and that it should therefore
				check the xTasksWaitingTermination list. */
				++uxDeletedTasksWaitingCleanUp;
				/* The pre-delete hook is primarily for the Windows simulator,
				in which Windows specific clean up operations are performed,
				after which it is not possible to yield away from this task -
				hence xYieldPending is used to latch that a context switch is
				required. */
				portPRE_TASK_DELETE_HOOK( pxTCB, &xYieldPending );
			}
			else
			{
				--uxCurrentNumberOfTasks;
				prvDeleteTCB( pxTCB );
				/* Reset the next expected unblock time in case it referred to
				the task that has just been deleted. */
				prvResetNextTaskUnblockTime();
			}
			traceTASK_DELETE( pxTCB );
		}
		taskEXIT_CRITICAL();
		/* Force a reschedule if it is the currently running task that has just
		been deleted. */
		if( xSchedulerRunning != pdFALSE )
		{
			if( pxTCB == pxCurrentTCB )
			{
				configASSERT( uxSchedulerSuspended == 0 );
				portYIELD_WITHIN_API();
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	}
#endif /* INCLUDE_vTaskDelete */

同样的,该函数则是受宏 INCLUDE_vTaskDelete 控制的,该宏位于 FreeRTOSConfig.h 文件中。

传入参数:

  • pxTaskToDelete:被删除任务的句柄(目标任务)。若传入 NULL,则是删除当前执行的任务(即删除自己)。

# 代码搭建

任务(task)是在 FreeRTOS 中执行的基本单位,每个 task 都是由一个 C 函数所组成,意思是你需要先定义一個 C 的函数,然後再用 xTaskCreate() 这个 API 來建立一個 task,這個 C 函数有几个特点,它的返回值必須是 void,其中通常是存于无限循环中(while、for),所有关于这个 task 的工作都会在这个无限循环中进行,而且这个函数不会有 return,FreeRTOS 不允許 task 自行结束(使用 return 或执行到函数的最后一行);task 被建立出來后,它会配置有自己的堆栈空间和 stack variable (就是 function 中定义的变量)。

main.c
/*
    FreeRTOS V9.0.0 - Copyright (C) 2016 Real Time Engineers Ltd.
    All rights reserved
    VISIT http://www.FreeRTOS.org TO ENSURE YOU ARE USING THE LATEST VERSION.
    This file is part of the FreeRTOS distribution.
    FreeRTOS is free software; you can redistribute it and/or modify it under
    the terms of the GNU General Public License (version 2) as published by the
    Free Software Foundation >>>> AND MODIFIED BY <<<< the FreeRTOS exception.
    ***************************************************************************
    >>!   NOTE: The modification to the GPL is included to allow you to     !<<
    >>!   distribute a combined work that includes FreeRTOS without being   !<<
    >>!   obliged to provide the source code for proprietary components     !<<
    >>!   outside of the FreeRTOS kernel.                                   !<<
    ***************************************************************************
    FreeRTOS is distributed in the hope that it will be useful, but WITHOUT ANY
    WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    FOR A PARTICULAR PURPOSE.  Full license text is available on the following
    link: http://www.freertos.org/a00114.html
    ***************************************************************************
     *                                                                       *
     *    FreeRTOS provides completely free yet professionally developed,    *
     *    robust, strictly quality controlled, supported, and cross          *
     *    platform software that is more than just the market leader, it     *
     *    is the industry's de facto standard.                               *
     *                                                                       *
     *    Help yourself get started quickly while simultaneously helping     *
     *    to support the FreeRTOS project by purchasing a FreeRTOS           *
     *    tutorial book, reference manual, or both:                          *
     *    http://www.FreeRTOS.org/Documentation                              *
     *                                                                       *
    ***************************************************************************
    http://www.FreeRTOS.org/FAQHelp.html - Having a problem?  Start by reading
    the FAQ page "My application does not run, what could be wrong?".  Have you
    defined configASSERT()?
    http://www.FreeRTOS.org/support - In return for receiving this top quality
    embedded software for free we request you assist our global community by
    participating in the support forum.
    http://www.FreeRTOS.org/training - Investing in training allows your team to
    be as productive as possible as early as possible.  Now you can receive
    FreeRTOS training directly from Richard Barry, CEO of Real Time Engineers
    Ltd, and the world's leading authority on the world's leading RTOS.
    http://www.FreeRTOS.org/plus - A selection of FreeRTOS ecosystem products,
    including FreeRTOS+Trace - an indispensable productivity tool, a DOS
    compatible FAT file system, and our tiny thread aware UDP/IP stack.
    http://www.FreeRTOS.org/labs - Where new FreeRTOS products go to incubate.
    Come and try FreeRTOS+TCP, our new open source TCP/IP stack for FreeRTOS.
    http://www.OpenRTOS.com - Real Time Engineers ltd. license FreeRTOS to High
    Integrity Systems ltd. to sell under the OpenRTOS brand.  Low cost OpenRTOS
    licenses offer ticketed support, indemnification and commercial middleware.
    http://www.SafeRTOS.com - High Integrity Systems also provide a safety
    engineered and independently SIL3 certified version for use in safety and
    mission critical applications that require provable dependability.
    1 tab == 4 spaces!
*/
/* Standard includes. */
#include <stdio.h>
/* Scheduler includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
/* Library includes. */
#include "stm32f10x_it.h"
/* Private app includes. */
#include "bsp_uart.h"
#include "bsp_time.h"
#include "bsp_gpio.h"
/*----------------------------- End -----------------------------*/
/*
 * User Private Task.
 */
static void prvUser_Task( void *pvParameters );
/*
 * Configure the clocks, GPIO and other peripherals as required by the demo.
 */
static void prvSetupHardware( void );
/*----------------------------- End -----------------------------*/
/************************************************
函数名称 : main
功    能 : 主函数入口
参    数 : 无
返 回 值 : 无
*************************************************/
int main( void )
{
#ifdef DEBUG
  debug();
#endif
	prvSetupHardware();
	/* Start the tasks defined within this file/specific to this demo. */
	xTaskCreate( prvUser_Task, "prvUser_Task", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL );
	/* Start the scheduler. */
	vTaskStartScheduler();
	/* Will only get here if there was not enough heap space to create the
	idle task. */
	return 0;
}
/*----------------------------- End -----------------------------*/
static void prvUser_Task( void *pvParameters )
{
    /* User-defined private tasks */
    while(1){
        /* do something here */
    }
	
    /* 
    * 如果你的 task 就是需要离开 loop 并结束
    * 需要用 vTaskDelete 來刪除自己而非使用 return 或自然结束 (执行到最后一行)
    * 这个参数的 NULL 值是表示自己 
    */
    vTaskDelete(NULL);		// 删除自己
}
static void prvSetupHardware( void )
{
	/* Start with the clocks in their expected state. */
	RCC_DeInit();
	/* Enable HSE (high speed external clock). */
	RCC_HSEConfig( RCC_HSE_ON );
	/* Wait till HSE is ready. */
	while( RCC_GetFlagStatus( RCC_FLAG_HSERDY ) == RESET )
	{
	}
	/* 2 wait states required on the flash. */
	*( ( unsigned long * ) 0x40022000 ) = 0x02;
	/* HCLK = SYSCLK */
	RCC_HCLKConfig( RCC_SYSCLK_Div1 );
	/* PCLK2 = HCLK */
	RCC_PCLK2Config( RCC_HCLK_Div1 );
	/* PCLK1 = HCLK/2 */
	RCC_PCLK1Config( RCC_HCLK_Div2 );
	/* PLLCLK = 8MHz * 9 = 72 MHz. */
	RCC_PLLConfig( RCC_PLLSource_HSE_Div1, RCC_PLLMul_9 );
	/* Enable PLL. */
	RCC_PLLCmd( ENABLE );
	/* Wait till PLL is ready. */
	while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET)
	{
	}
	/* Select PLL as system clock source. */
	RCC_SYSCLKConfig( RCC_SYSCLKSource_PLLCLK );
	/* Wait till PLL is used as system clock source. */
	while( RCC_GetSYSCLKSource() != 0x08 )
	{
	}
	/* Configure HCLK clock as SysTick clock source. */
	SysTick_CLKSourceConfig( SysTick_CLKSource_HCLK );
	
	/*
	 * STM32 中断优先级分组为 4,即 4bit 都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* Other peripheral configuration */
	vSetupTimer();
	vSetupUSART();
	vSetupParPort();
}
/*----------------------------- End -----------------------------*/
#ifdef  DEBUG
/* Keep the linker happy. */
void assert_failed( unsigned char* pcFile, unsigned long ulLine )
{
	for( ;; )
	{
	}
}
#endif
/*---------------------------- END OF FILE ----------------------------*/

首先, prvSetupHardware() 函数是配置时钟,并且初始化硬件配置(即 vSetupTimer(); vSetupUSART(); vSetupParPort(); 的实现,其实就是封装了一下之前 stm32 笔记中 的 time、uart、gpio 初始化配置)。

[Other peripheral configuration]
/************************************************
函数名称 : vSetupTimer
功    能 : Timer 初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupTimer( void )
{
	Timer2_Config();
}
/************************************************
函数名称 : vSetupUSART
功    能 : UART 初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupUSART( void )
{
	UART1_Config();
//	UART2_Config();
}
/************************************************
函数名称 : vSetupParPort
功    能 : 基础 IO 初始化接口
参    数 : 无
返 回 值 : 无
*************************************************/
void vSetupParPort( void )
{
	LED_Config();
	Key_Config();
}

然后到后面的 main 函数的实现调用,调用 prvSetupHardware() 初始化所有跟硬件相关的配置,创建一个 prvUser_Task 任务,用于管理所有后面创建的任务(其实就是为了方便管理),因为这里面没有创建其他函数,所以放了个 while 循环在里面,最后再调用 vTaskDelete(NULL); 把自己删除;

这里可能有个疑问,明明有个 while 死循环,执行不到 vTaskDelete() 函数,没什么意义啊,虽然道理是这样,但是最好在每个任务的结尾放上 vTaskDelete() 函数,以确保安全(特殊情况除外);还有就是,因为在 prvUser_Task 任务中有个死循环,那在 while 之后的程序应该都执行不了啊,包括 prvUser_Task 任务后面的 vTaskStartScheduler(); ,如果是按正常的思维是这样的;但是,这里是在使用 RTOS,调用 xTaskCreate() 创建任务并不表示就已经执行了,只是告诉内核,我创建了这么一个任务,真正使系统开始执行是在调用了 vTaskStartScheduler(); 启动调度器之后。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

夏沫の浅雨 微信支付

微信支付

夏沫の浅雨 支付宝

支付宝