1. 多任务系统
1.1 前后台系统
单片机裸机开发时,一般都是在main函数里面用while(1)做一个大循环来完成所有的处理,循环中调用相应的函数完成所需的处理。有时也需要在中断中完成一些处理。相对于多任务系统而言,这就是单人单任务系统也称作前后台系统,中断服务函数作为前台程序,while(1)作为后台程序,如下图示
1.2 抢占式多任务系统
多任务系统会把一个大问题分而治之,把大问题划分成很多个小问题,逐步的把小问题解决掉,大问题也就随之解决了,这些小问题可以单独的作为一个小任务来处理。多个任务带来了一个新的问题,即究竟哪个任务先运行,哪个任务后执行?完成这个功能的东西在RTOS系统中叫做任务调度器。不同的系统其任务调度器的实现方法也不同,比如FreeRTOS是一个抢占式的实时多任务系统,那么其任务调度器也是抢占式的,其运行过程如下图示
从图中可以看出,高优先级的任务可以打断低优先级任务的运行而取得CPU的使用权。高优先级的任务执行完成以后重新把CPU的使用权归还给低优先级的任务,这就是抢占式任务系统的基本原理
2. FreeRTOS任务
在使用RTOS的时候一个实时应用可以作为一个独立的任务。每个任务都有自己的运行环境,不依赖于系统中其他的任务或者RTOS调度器。任何一个时间点只能有一个任务运行,具体运行哪个任务是由RTOS调度器来决定的,RTOS调度器因此就会重复的开启、关闭每个任务。任务不需要了解RTOS调度器的具体行为,RTOS调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器值,堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈,当任务切换的时候将上下文环境保存在堆栈中,这样当任务再次执行的时候就可以从堆栈中取出上下文环境,任务恢复运行
2.1 任务状态
FreeRTOS中的任务有下面几种状态
运行态:当任务正在运行时,处于运行态;处于运行态的任务就是当前正在使用处理器的任务
就绪态:已经准备就绪,可以运行的任务;有同优先级或更高优先级的任务正在运行占用CPU
阻塞态:任务正在等待某个外部事件即处于阻塞态;阻塞态有超时时间,若超时会退出阻塞态
挂起态:进入挂起态后也不能被调度器调用进入运行态;挂起态的任务没有超时时间
任务状态之间的转换如下图
2.2 任务优先级
每个任务都可以分配一个从0 ~ (configMAX_PRIORITIES - 1)的优先级,configMAX_PRIORITIES在文件FreeRTOSConfig.h中定义,可以通过STM32CubeMX中FreeRTOS下的MAX_PRIORITIES参数进行配置。MAX_PRIORITIES可以为任意值,但是考虑到RAM的消耗,最好设置为一个满足应用的最小值
优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES - 1的优先级最高。空闲任务的优先级最低,为0
2.3 任务实现
在使用FreeRTOS的过程中,要使用xTaskCreate()或xTaskCreateStatic()来创建任务,这两个函数的第一个参数pxTaskCode,就是这个任务的任务函数。任务函数就是完成本任务工作的函数。FreeRTOS官方给出的任务函数模板如下:
void vATaskFunction(void *pvParameters)
{
for(;;)
{
任务应用程序;
vTaskDelay();
}
vTaskDelete(NULL);
}
|
2.4 任务控制块
FreeRTOS的每个任务都有一些属性需要存储,FreeRTOS把这些属性集合到一起用一个结构体来表示,这个结构体叫做任务控制块:TCB_t,在使用函数xTaskCreate()创建任务的时候就会自动的给每个任务分配一个任务控制块。此结构体在文件task.c中有定义,如下:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack;
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings;
#endif
ListItem_t xStateListItem;
ListItem_t xEventListItem;
UBaseType_t uxPriority;
StackType_t *pxStack;
char pcTaskName[ configMAX_TASK_NAME_LEN ];
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack;
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber;
UBaseType_t uxTaskNumber;
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority;
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter;
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue;
volatile uint8_t ucNotifyState;
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
uint8_t ucStaticallyAllocated;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
typedef tskTCB TCB_t;
|
2.5 任务堆栈
FreeRTOS之所以能正确的恢复一个任务的运行就是因为有任务堆栈在保驾护航,任务调度器在进行任务切换的时候会将当前任务的现场(CPU寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行的时候就会先用堆栈中保存的值来恢复现场,恢复现场以后任务就会接着从上次中断的地方开始运行
创建任务的时候需要给任务指定堆栈,若使用xTaskCreate()动态创建任务,任务堆栈会由函数xTaskCreate()自动创建;若使用xTaskCreateStatic()静态创建任务,就需要自行定义任务堆栈,将堆栈首地址作为函数的参数puxStackBuffer传递给函数,如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
onst char * const pcName,
const uint32_t ulStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
tackType_t * const puxStackBuffer,
StaticTask_t * const pxTaskBuffer )
|
不论是使用动态还是静态方法创建任务,创建任务都需要指定任务堆栈大小。任务堆栈的数据类型为StackType_t,其本质是uint32_t,在portmacro.h文件中由定义,如下:
#define portSTACK_TYPE uint32_t
....
typedef portSTACK_TYPE StackType_t;
|
可见StackType_t类型的变量为4个字节,因此任务的实际堆栈大小就是我们所定义的4倍
|