文章目录
FreeRTOS
FreeRTOS 特点
- FreeRTOS是一个小型的、开源的实时操作系统, FreeRTOS提供了 多任务调度、时间片管理、共享资源、消息队列、内存管理 等功能,非常适合资源受限的微控制器和小型处理器。
- 主要特点:确定性的响应时间 。
启动FreeRTOS系统时,需要哪些配置?
- 配置FreeRTOS内核通常包括定义堆栈大小、设置时钟滴答心跳、定义任务优先级、选择调度策略等。
任务
- 创建任务的步骤
创建任务需要指定任务所需的栈大小,任务优先级,与任务绑定的回调函数。
TCB 任务控制块
- 任务控制块(Task Control Block) :是一个结构体,用于记录任务的元信息,包括:
任务优先级
任务状态(就绪、阻塞、挂起等)
指向任务栈顶的指针(pxTopOfStack)
任务名称、事件链表等管理信息 - TCB 本身不存储上下文数据,但它通过栈顶指针管理任务栈的位置。
任务优先级
- 优先级的取值范围是:0~(configMAX_PRIORITIES -- 1),数值越大优先级越高。
四种任务状态
- Running 运行态 :当任务处于实际运行状态被称之为运行态,即 CPU 的使用权被这个任务占用。
- Ready 就绪态 :处于就绪态的任务是指那些 能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同优先级或更高优先级的任务正在运行 。
一个任务要能够运行前必须先进入就绪态。 - Blocked 阻塞态 :由于等待信号量,消息队列,事件标志组等而处于阻塞的状态 被称之为阻塞态,另外任务调用vTaskDelay() 延迟函数也会处于阻塞态。
- Suspended 挂起态 :类似阻塞态,通过手动调用函数 vTaskSuspend()对指定任务进行挂起,挂起后这个任务 不被执行 ,只有手动调用函数 xTaskResume()才可以将这个任务从挂起态恢复。
ldle 空闲任务
- 优先级最低 ,为 0 。
- 空闲任务 要么处于就绪态,要么处于运行态 ,永远不会阻塞。
任务切换
- 保存当前任务上下文到栈
硬件相关操作:
保存 CPU 寄存器(如程序计数器 PC、状态寄存器、通用寄存器)到当前任务的 **栈(Stack)**中。 - 更新任务状态:
若任务因阻塞或让出 CPU 被切换,其状态会被标记为就绪、阻塞或挂起。 - 选择下一个运行任务
调度器从就绪队列中选择最高优先级任务。
若使用抢占式调度,直接选择优先级最高的任务。
若使用时间片轮转,同优先级任务按轮转顺序切换。 - 恢复新任务上下文
硬件相关操作:
从新任务TCB中的栈顶指针,找到新任务的栈,从栈中恢复寄存器值,跳转到任务代码的断点继续执行。
调度算法
- 抢占式调度 (可配置):如果在 就绪列表中如果有更高优先级的任务,当前运行的任务将被打断,调度器将切换到更高优先级的任务,这确保了关键任务能够及时响应。
- 时间片流转(可配置):如果 就绪列表中有多个任务有相同的高优先级,调度器会让这些任务相继执行,每个任务都执行一个"时间片" ,任务在时间片起始时刻进入运行态。
- 空闲任务让步用户任务(可配置):
- 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务。
- 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊。
在任务回调函数的末尾添加 vTaskDelay()*延迟函数 可以令该任务进入阻塞态,将CPU控制权交给其他任务,在指定时间后会任务状态会转变为就绪态等待下一次运行。
任务间的通信
队列
-
队列 是FreeRTOS中的一种中间件,是一种先进先出(FIFO)的数据结构,一个队列可以被多个任务写入或者读取,它允许 任务到任务、任务到中断、中断到任务 直接传输数据。
-
创建队列时要制定队列长度、每个数据大小(以字节为单位)。
| 读队列 | 写队列 |
|---|---|
| 任务读队列时,如果队列没有数据,则该任务可以进入阻塞状态 (还可以指定阻塞的时间,时间到之后它会进入就绪态)。 如果队列有数据了,则该阻塞的任务会变为就绪态。 | 任务要写队列时,如果队列满了,该任务也可以进入阻塞状态 (还可以指定阻塞的时间,时间到之后它会进入就绪态)。 如果队列有空间了,则该阻塞的任务会变为就绪态。 |
- 多任务读取/写入队列时,策略:优先级最高>等待时间最久 :当队列被多个任务读取/写入 时,只会有一个任务会被解除阻塞,这个任务就是 所有等待任务中优先级最高的任务 ,而如果所有等待任务的优先级相同,那么 被解除阻塞的任务将是等待最久的任务。
信号量
-
信号量
- 信号
起通知作用。 - 量
可以用来表示资源的数量。- 量没有限制时就是 计数型信号量 。
- 当量只有0或1时就是 二进制信号量 。
- 信号
-
操作:
事件产生时给出信号量+1,处理事件时要先获取信号量-1。
访问资源时先获取信号量-1,用完资源后要给出信号量+1。 -
信号量分类
二值信号量 计数信号量 递归信号量 用于 锁定资源 ,类似于互斥锁,它们只能有两个状态:获取或释放。初始值为0 可以用于 管理多个相同的资源 ,或者用来 同步多个任务。 是计数信号量的一种特殊形式,允许同一个任务多次获取同一个资源。 -
信号量和队列的对比
| 队列 | 信号量 |
|---|---|
| 可以容纳 多个数据 创建队列时有两部分内存:队列结构体,存储数据的空间 | 只有计数值 ,无法容纳其他数据集。 创建信号量时,只需分配信号量结构体。 |
| 生产者:没有空间存入数据时可以阻塞 | 生产者:用于不阻塞,计数值达到最大时返回失败 |
| 消费者:没有数据时可以阻塞 | 消费者:没有资源时可以阻塞 |
互斥量/互斥锁
- 互斥量 ,也被称为 互斥锁,是一种 特殊的二进制信号量。
用于控制在两个或多个任务访问共享资源:任务在获取到互斥量后,可以拥有对共享资源的权限,在这个任务处理完毕后,必须返还互斥量 ,使得共享资源交由别的任务处理。即: 谁进入房间上锁,就只能由谁开锁 。互斥量的 初始值为1。
处理流程:
- 互斥量初始值为1。
- 任务A想访问临界资源,先获得并占有互斥量(互斥量变为0),然后开始访问。
- 任务B也想访问临界资源,也要先获得互斥量:被别人占有了,于是阻塞。
- 任务A使用完毕,释放互斥量(互斥量变为1);任务B被唤醒、得到并占有互斥量(互斥量变为0),然后开始访问临界资源任务B使用完毕,释放互斥量。
- 互斥量存在的 优先级翻转问题:低优先级占有共享资源运行的时候,如果有一个高优先级想要访问共享资源,此时由于互斥量这个高优先级会进入阻塞状态,此后在低优先级释放资源后,若有一个中等优先级的任务要获取该资源则中等优先级的任务将先一步运行,导致一种中等优先级比高等优先级优先运行的现象。
可通过 优先级继承 解决:也就是 低优先级获取互斥量占有共享资源的时候,若等待访问共享资源的任务中有比当前任务要高的高优先级任务,则将当前任务的优先级设置为同等的高优先级,互斥量持有者在归还互斥量时,优先级会自动设置为其原来的优先级。
同步
临界区
- FreeRTOS 设置了 进入临界区和离开临界区的两个函数 ,两个函数中间的代码称为 临界区 ,在进入临界区后,系统的 中断将被屏蔽(包括任务调度的滴答中断),这将导致临界区的代码仅仅被当前任务处理/修改 。
挂起任务
- 可以通过挂起任务实现资源的共享。也就是说在要保护不被修改的代码前将一些可能要 对保护区代码有修改的任务挂起 ,等到保护区代码执行完毕,再将这些任务恢复。这种方式下保护区的代码 不会被其他任务所打断,但是中断还是使能的,所以需要结合实际情况适用。
守护任务
- 守护任务也是FreeRTOS实现共享资源访问的一种手段,其它任务要访问该资源只能间接地通过守护任务提供的服务。
中断处理
- 在FreeRTOS中,中断处理程序内应避免执行复杂的操作或阻塞调用,尽可能快速地执行并返回,以保证其他任务的实时性。
如果需要执行长时间运行的任务,应使用 延迟处理任务 :也就是 中断服务例程结束后第一个要运行的任务,通过在中断处理程序中快速处理少部份工作,并且利用信号量使得延迟处理任务解除阻塞,从而将大部分需要处理的工作在延迟处理任务中完成 。这样就可以让 → \rightarrow → 任务与中断同步 。
处理流程:
- 中断产生。中断服务例程启动,给出二值信号量+1以使延迟处理任务解除阻塞。
- 当中断服务例程退出时,延迟处理任务得到执行,延迟处理任务做的第一件事便是获取信号量-1。
- 延迟处理任务完成处理后,试图再次获取信号量但是此时信号量无效,任务将切入阻塞待等待事件发生。
- 利用的BASEPRI寄存器,可以设置屏蔽低于某一阈值的中断。
锁
死锁
假设有2个互斥量M1、M2,2个任务A、B:
A获得了互斥量M1
B获得了互斥量M2
A还要获得互斥量M2才能运行,结果A阻塞
B还要获得互斥量M1才能运行,结果B阻塞
A、B都阻塞,再无法释放它们持有的互斥量
死锁发生!
即:互相等待对方释放资源,
自我死锁
任务A获得了互斥锁M
它调用一个库函数
库函数要去获取同一个互斥锁M,于是它阻塞:任务A休眠,等待任务A来释放互斥锁!
死锁发生!
即:已经获得了锁,内部的某个处理还需要获取同一个锁,自我阻塞。
内存管理
| 内存管理方法 | 特点 |
|---|---|
| heap1 | 支持申请内存,不支持释放内存底层是一个数组,当申请内存时,又会将数组细分为更小的内存块。 |
| heap2 | 支持申请内存、释放内存 通过一种最优算法分配接近请求大小的内存块。但是不会把相邻的内存碎片合并成一个更大的内存块,所以 会产生内存碎片。适用于重复创建删除具有相同堆栈大小的任务的应用程序。 |
| heap3 | 支持申请内存、释放内存(调用了C语言标准库里面的malloc和free函数)在调用这两个函数之前会先关闭FreeRTOS的调度器,这会使得系统整体更加安全。 |
| heap4 | 支持申请内存、释放内存 能够 使相邻的内存进行合并,减少内存碎片 的现象。 |
| heap5 | 支持申请内存、释放内存 在 heap4的基础上能够管理多个非连续内存 。 |
软件定时器
- 软件定时器 是FreeRTOS使用FreeRTOS的调度器和时钟滴答来模拟的一种 "闹钟 " 机制。任务可以创建一个软件定时器,并将其与任务函数关联,当定时器到期时,关联的任务将被添加到就绪列表中,实现如下功能:
- 只运行一次函数
- 周期性运行函数