# 任务状态
任务目前存在四种状态,分为:运行、就绪、阻塞、挂起;
- Running — 运行态
这是任务在执行的时候的状态,处在运行态意味着任务获得 CPU 的使用权,对于单核 CPU,此时不存在其他运行态的任务。 - Ready — 就绪态
处在就绪态意味着这个任务是可以执行的,比如某个事件发生、队列数据到来、所请求的资源有效等;但是,因为此时有一个相同优先级或者更高优先级的任务正在运行,此时任务无法执行而处于就绪态。 - Blocked — 阻塞态
如果一个任务正在等待一个时间到来或者外部的事件到来,比如一个调用vTaskDelay()
函数的任务,在延时时间到来之前,任务将处在阻塞状态。当然,任务在等待队列、信号量、事件组、通知、信号量事件时都会处在阻塞态。通常,这些等待都会设置一个超时 timeout 时间,当等待超时是将任务从阻塞态退出,防止任务被无限挂起。处在阻塞态的任务不占用任何 CPU 时间。 - Suspended — 挂起态
挂起态无法进入运行态,同时不像阻塞态有超时时间,挂起态无超时时间,除非调用vTaskResume()
推出挂起态。相应的调用vTaskSuspend()
将一个任务挂起。挂起态可以描述成 “冻结”;除非 “解冻”,否则无法再有机会被执行到。
它们的状态关系图:
# 任务优先级
每个任务在创建的时候都要指定一个从 0
到 configMAX_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 中定义的变量)。
/* | |
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 初始化配置)。
/************************************************ | |
函数名称 : 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();
启动调度器之后。