有个关于为什么选择FreeRTOS的文章值得一看
ESP32 的IDF使用的是FreeRTOS,对该教程做一下学习笔记
参考FreeRTOS官网资料
0 关于FreeRTOS
- 适用于多任务的小型嵌入系统
- 为裸机程序提供多任务功能的库
每一种编译器+处理器的组合都称为:FreeRTOS port
FreeRTOSConfig.h 包含各种关于FreeRTOS特性的定制化配置(应与应用代码放在统一路径)
1 任务管理 Task Management
调度算法:
-
抢占+时间片均衡(默认)
configUSE_PREEMPTION = 1
configUSE_TIME_SLICING = 1相同优先级的依次执行,可被高优先级抢占
-
抢占+时间片不均衡
configUSE_PREEMPTION = 1
configUSE_TIME_SLICING = 0相同优先级不均分时间片,只有高优先级task退出执行状态才会进行相同优先级task调度调整。
-
Co-operative
只有当前执行task进入block态或者当前task调用taskYIELD(),才会发生任务切换。任务不能够抢占configUSE_PREEMPTION = 0
configUSE_TIME_SLICING = anyValue任务优先级失效,只有当前任务停止执行才会进行任务调度
2 队列管理 Queue Management
队列是FIFO结构,可用于任务间、任务与中断间的通信机制。
通信机制:通过一个或多个任务向队列写数据,另一个或多个任务从队列读数据,可以实现任务间的通信
- 队列可以保存指定长度的自定义结构
- 队列可以组成队列组,用于有特殊需求的场景
- 将队列和任务调度结合,可以实现UART数据栈和TCP/IP协议栈的处理功能
3 软件定时器 Software Timer Management
软件定时器是为了在指定时间执行某个回调函数
定时器原理:
- 对定时器的操作(启动、停止、重置)会通过定时器命令队列(timer command
quene)向RTOS后台任务(daemon task 默认创建)传递命令 - RTOS后台任务有两个功能:处理定时器命令、执行回调函数
- 由于RTOS后台任务受configTIMER_TASK_PRIORITY的控制,存在两种简单情况:
场景1
PS:定时器的起始时间是从加入定时器命令队列开始计算,也就是从上图的t2开始,而不是从t4开始计算
场景2:
定时器属性:
-
包括单词定时器和连续定时器
-
支持创建、删除、启动、重置、修改定时时间
-
有两种状态:静态 dormant(定时器未运行)、运行态 running(定时器工作中,达到定时时间后,会调用回调函数)参见下图:
4 中断管理 Interrupt Management
中断相较任务有更高的优先级(中断可以抢占任务,反之不行)
中断会对任务的执行时间、定时存在扰动,因此尽可能的减少中断服务程序(interrupt service routine)的执行时间,是很重要的。由此引申出:延迟中断执行(deferred interrupt processing)即将中断的处理挪到任务中来。
中断的上下文切换:中断处理中(任务A被中断,中断结束后应该继续执行任务A),如需在中断结束后执行任务B,那么就涉及任务的上下文切换。
延迟中断执行的实现:
0 中断处理前建立任务B,并监控信号量(semaphore)
1 中断处理中,发出信号线
2 中断处理后,任务B接收信号量,完成延迟的中断处理
3 继续执行任务A
5 资源管理 Resource Management
多任务会带来数据损坏(data corruption)
比如:
- 多任务对输出设备的使用
- 对变量的读、修改、写回
- 非原子操作
- 程序的重入
都会引入数据被其他任务“篡改”的风险。
解决方法:互斥锁(mutual exclusion == mutex)技术。就是对存在线程风险的数据加上“锁”,进行保护。
引入互斥锁又会导致其他问题:优先级颠倒(priority inversion)死锁(deadlock or deadly embrace)递归互斥。当然也会有对应的解决方法
6 事件组 Event Groups
使用场景:
某个任务需要等待多个事件
多个任务等待同一事件或事件组合
7 任务通知 Task Notifications
任务之间的通信可以借助通信对象:队列、事件组、信号量等
任务通知 机制(受控于 configUSE_TASK_NOTIFICATIONS)给task增加两个属性:通知状态和通知值