记录一下,方便以后翻阅~
RTOS系统的核心是任务管理,初学RTOS系统必须先掌握任务的创建、删除、挂起和恢复等操作。
1. 什么是多任务系统
玩裸机一般都是在main函数里用while(1)做一个死循环完成所有处理,同时再加一些中断完成一些特定的处理,这里中断服务函数叫前台程序,死循环叫后台程序,即前后台系统(单任务系统),如下图所示:
前后台系统的实时性差,所有任务的优先级都是一样的,没轮到你就只能等着!多任务系统就应运而生。RTOS系统有个任务调度器,不同系统的任务调度器的实现方法是不同的,FreeRTOS是一个抢占式的实时多任务系统:
上图中,高优先级的任务可以打断低优先级任务的运行而取得CPU的使用权,执行完成后重新把CPU使用权给低优先级任务,这就是抢占式多任务系统的基本原理。
2. 任务与协程
在FreeRTOS中应用既可以使用任务,也可以使用协程(Co-Routine),或者两者混用。任务和协程使用不同的API函数,因此不能通过队列(或信号量)将数据从任务发送给协程,反之亦然。目前FreeRTOS官方已经不再更新协程了,建议以后主要用任务。
2.1 任务(Task)的特性
任何一个时间点只能运行一个任务,具体运行哪个任务由RTOS调度器决定。RTOS调度器的职责是确保当一个任务开始执行的时候其上下文环境(寄存器、堆栈内容等)和任务上一次退出的时候相同。为了做到这一点,每个任务都必须有个堆栈。(本章不介绍协程的内容)
任务特性:
1)简单;
2)没有使用限制;
3)支持抢占;
4)支持优先级;
5)每个任务都拥有堆栈导致RAM使用量增大;
6)如果使用抢占的话必须考虑重入的问题。
2.2 任务状态
1)运行态
当一个任务正在运行时,称这个任务处于运行态。处于运行态的任务就是当前正在使用处理器的任务。
2)就绪态
就绪态的任务就是那些已经准备就绪(没有被阻塞或挂起),可以运行的任务,但是处于就绪态的任务还没有运行,因为有一个同优先级或更高优先级的任务正在运行!
3)阻塞态
若一个任务当前正在等待某个外部事件的话就称之为阻塞态,比如某个任务调用了函数 vTaskDelay()的话就会进入阻塞态,直到延迟周期完成。任务在等待队列、信号量、事件组、通知或互斥信号量的时候也会进入阻塞态。任务进入阻塞态会有一个超时时间,超过这个时间任务就会退出阻塞态,即使所等待的事件还没有来临!
4)挂起态
像阻塞态一样,任务进入挂起态后也不能被调度器调用进入运行态,但是进入挂起态的任务没有超时时间。任务进入和退出挂起态通过调用函数 vTaskSuspend()和 xTaskResume()。
任务状态之间的转换如下图:
2.3 任务优先级
每个任务都分配一个从0 ~ (configMAX_PRIORITIES-1)的优先级,configMAX_PRIORITIES在FreeRTOSConfig.h中有定义。
如果硬件平台支持类似计算前导零这样的指令(通过该指令选择下一个要运行的任务,Cortex-M处理器支持该指令),且宏configUSE_PORT_OPTIMISED_TASK_SELECTION也设置为1,那么宏configMAX_PRIORITIES不能超过32!即优先级不能超过32级。其他情况下可设置任意值,但考虑RAM的消耗,宏configMAX_PRIORITIES应设为一个满足应用的最小值。
优先级数字越低表示任务的优先级越低,0的优先级最低,configMAX_PRIORITIES-1的优先级最高。空闲任务的优先级为0。
FreeRTOS调度器确保处于就绪态或运行态的高优先级的任务获取处理器使用权,即处于就绪态的最高优先级的任务才会运行。当宏configUSE_TIME_SLICING为1时,多任务可共用一个优先级,数量不限。默认情况下宏configUSE_TIME_SLICING在FreeRTOS.h中定义为1,此时处于就绪态的优先级相同的任务会使用时间片轮转调度器获取运行时间。
2.4 任务实现
在使用FreeRTOS的过程中,要用函数xTaskCreate()或xTaskCreateStatic()创建任务,两个函数的第一个参数pxTaskCode,就是这个任务的任务函数。任务函数是完成本任务工作的函数。比如做个流水灯的任务,那么这个流水灯的程序就是任务函数实现的。任务函数代码模板如下:
void vATaskFunction(void *pvParameters) // 任务函数名,其返回值一定要为void类型,即无返回值。任务参数也是void指针类型!
{
for(;;) // 任务具体执行过程是一个循环,也可用while(1)
{
--任务应用程序-- // 任务代码,要干的具体的活
vTaskDelay(); // FreeRTOS的延时函数(不一定要用),只要能让FreeRTOS发生任务切换的API函数都可以,比如信号量、队列等,甚至直接调用任务调度器。
}
vTaskDelete(NULL); // 任务函数一般不允许跳出循环,如果一定要跳出循环的话,在跳出循环后一定要调用vTaskDelete(NULL)删除此任务!
}
2.5 任务控制块
FreeRTOS每个任务都有一些属性需要存储,FreeRTOS把这些属性集合到一起用一个结构体表示,这个结构体称任务控制块:TCB_t,在使用函数xTaskCreate()创建任务时就会自动的给每个任务分配一个任务控制块。此结构体在task.c文件里定义如下:
/* Task control block. A task control block (TCB) is allocated for each task, and stores task state information, including a pointer to the task's context (the task's run time environment, including register values) */
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; // 任务堆栈栈顶
#if ( portUSING_MPU_WRAPPERS == 1 ) // MPU相关设置
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; // trace或debug时用
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; // 定义一个newlib结构体变量
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 ) // 任务通知相关变量
volatile uint32_t ulNotifiedValue; // 任务通知值
volatile uint8_t ucNotifyState; // 任务通知状态
#endif
#if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) // 标记任务时动态创建还是静态创建,静态创建变量为pdTURE,反之为pdFALSE
uint8_t ucStaticallyAllocated;
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
// 新版本FreeRTOS任务控制块重命名为TCB_t,本质还是tskTCB,主要为了兼容就版本
typedef tskTCB TCB_t;
2.6 任务堆栈
任务调度器在进行任务切换的时候会将当前任务的现场(CPU寄存器值等)保存在此任务的任务堆栈中,等到此任务下次运行时就会先用堆栈中保存的值来恢复现场,恢复现场后任务就会接着上次中断的地方开始运行。
创建任务的时候需要给任务指定堆栈,如果使用的函数xTaskCreate()创建任务,那么任务堆栈就会由函数xTaskCreate()自动创建,如果使用函数xTaskCreateStatic()创建任务就需要自行定义任务堆栈,然后堆栈首地址作为函数的参数puxStackBuffer传递给函数,如下:
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode,
const char* const pcName,
const uint32_t ulStackDepth,
void* const pvParameters,
UBaseType_t uxPriority,
StackType_t* const puxStackBuffer, // 任务堆栈,用户定义
StaticTask_t* const pxTaskBuffer )
堆栈大小:
不管用xTaskCreate()还是xTaskCreateStatic()创建任务都需要制定任务堆栈大小。任务堆栈的数据类型为StackType_t, StackType_t本质上是uint32_t,在portmacro.h中有定义:
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE long
typedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;
可以看出StackType_t类型的变量为4个字节,那么任务的实际堆栈大小是所定义的4倍。