FreeRTOS深入理解
系列文章目录
文章目录
- 系列文章目录
- 堆
- 栈
- 内存管理方案
- 任务调度
- 任务状态的内部原理
- 中断
- 临界区保护
- 任务句柄
- 任务调度器
- 通信方式
- 优先级反转
- 低功耗
- FreeRTOS和Linux的区别
- STM32的Flash和RAM
堆
malloc申请的堆内存空间,同时会存储下申请的大小,free释放的时候,会导致堆碎片化,这个时候空闲内存肯定是通过链表连接在一起的
栈
任务切换时,上下文保护,cpu寄存器只会压栈,以便后续恢复现场
PC(Program Counter 程序计数器):R15,存储下一个将要执行的指令的地址
SP(Stack Pointer 堆栈指针):R13,SP始终会指向栈的下一个空闲空间的地址
LR(Link Resigter 链接寄存器):R14,用于保存子程序或者是中断的返回地址
存储函数中定义的局部变量
保存函数调用时的地址,参数传递等信息
分静态分配和动态分配
// 静态定义任务栈数组和任务句柄
static StackType_t xTaskStack[ configMINIMAL_STACK_SIZE ];
static StaticTask_t xTaskBuffer;// 创建静态任务
TaskHandle_t xTaskCreateStatic(TaskFunction_t pxTaskCode, // 任务函数const char * const pcName, // 任务名称uint32_t ulStackDepth, // 栈深度(单位:字,如32位系统为4字节/字)void * const pvParameters, // 任务参数UBaseType_t uxPriority, // 任务优先级StackType_t * const puxStackBuffer, // 栈数组StaticTask_t * const pxTaskBuffer // 任务控制块
);
动态创建自动从堆中分配栈空间
**// 创建动态任务
BaseType_t xTaskCreate(TaskFunction_t pxTaskCode,const char * const pcName,configSTACK_DEPTH_TYPE usStackDepth, // 栈深度(单位:字)void * const pvParameters,UBaseType_t uxPriority,TaskHandle_t * const pxCreatedTask
);**
堆总大小定义
#define configTOTAL_HEAP_SIZE (16 * 1024) // 16KB堆空间
内存管理方案
???
FreeRTOSConfig.h 的 configUSE_MALLOC_FAILED_HOOK
方案 1(heap_1.c)
特点:仅支持内存分配(无释放),速度最快,无内存碎片。
适用场景:任务数量固定且无需删除的简单系统。
方案 2(heap_2.c)
特点:支持分配和释放,使用最佳匹配算法,但可能导致碎片化。
适用场景:任务创建和删除频繁,但栈大小相近的系统。
方案 3(heap_3.c)
特点:封装标准 C 库的malloc()和free(),线程安全。
适用场景:依赖标准库内存管理的系统。
方案 4(heap_4.c)
特点:支持合并相邻空闲块,减少碎片化,使用首次匹配算法。
适用场景:任务栈大小差异较大的复杂系统。
方案 5(heap_5.c)
特点:支持在不连续的内存区域(如 SRAM 和外部 RAM)分配堆。
适用场景:内存分布分散的系统
单片机的RAM,一部分用于编译时分配了全局变量和静态局部变量,剩下的才是堆和栈
任务调度
有三种方法:时间片轮转(同等优先级)、抢占式调度(高优先级抢占,响应快)、协程式调度(无抢占,低优先级任务不会被强制打断,适合共享资源无需锁的场景,变裸机开发?)
//抢占式+时间片轮转
#define configUSE_PREEMPTION 1 // 启用抢占式调度
#define configUSE_TIME_SLICING 1 // 启用时间片轮转(默认值)
#define configTICK_RATE_HZ 1000 // 设置时间片长度(如1ms)// 纯协作式调度
#define configUSE_PREEMPTION 0
#define configUSE_TIME_SLICING 0
任务状态:运行态、就绪态、阻塞态、挂起态
挂起类似暂停,需要vTaskSuspend进入挂起、调用vTaskResume来解挂
删除态官方文件没有,应该不算,删除对应创建
任务对应的协程,协程不能通信,用于RAM资源严重不足的情况,调用 xCoRoutineCreate() 即可创建协程。协程优先级和任务不混,两个同时存在时,任务优先级始终高于协程,所以只有在没有优先级高于空闲任务的任务可以执行时才会执行协程。
任务状态的内部原理
Task Control Block,TCB,任务控制块
包含了管理任务所需的关键信息:
任务状态,用于表明任务当前处于运行、就绪、阻塞或挂起等状态;
任务优先级,决定了任务在调度时的顺序;
任务栈指针,指向任务栈的顶部,用于保存任务上下文;
任务句柄,作为任务的唯一标识;
还有任务名称,方便调试和识别。
此外,TCB 中还可能包含任务的阻塞时间、等待事件的相关信息,以及指向链表节点的指针,用于将任务组织到不同优先级的就绪链表、阻塞链表或挂起链表中,从而实现操作系统对任务的高效管理和调度。
动态创建会分配两块内容,一块用于TCB、一块用于任务栈
任务调度机制核心依赖于任务链表(Task Lists),链表通过嵌入在 TCB 中的节点(ListItem_t)来管理任务。
时间片轮转的时候:
当调度器触发时间片轮转时:
- 定位当前任务优先级P:通过TCB获取当前任务优先级
- 访问优先级P对应的就绪链表:pxReadyTasksLists[P]
- 检查该优先级下的任务数:uxCurrentNumberOfTasks[P]
- 若任务数=1:无需轮转,当前任务继续执行
- 若任务数>1:执行时间片轮转
- 时间片计数器递减:
- 每个任务运行时,优先级P的时间片计数器(xSchedulerRunning等相关变量)递减
- 当计数器减为0时,触发轮转
- 链表遍历与任务切换:
- 将当前任务从链表头部移除,插入到链表尾部
- 取出链表新头部的任务作为下一个运行任务
- 执行上下文切换(保存当前任务寄存器,恢复新任务状态)
PendSV与操作系统中的SVC协同工作。SVC不能挂起,它将立即被执行;而PendSV可以暂时挂起异常,对于操作系统来说这很有用,它可以等待一个重要的任务执行完毕后再处理该异常。
RTOS上下文切换在PendSv(pendable system call,可挂起系统调用)异常中进行,主要指的任务切换
原因:
1.PendSv优先级非常低,不影响中断执行
2.可挂起,触发延迟执行,确保不影响中断响应,中断完成后,再压栈任务的相关参数
Cortex-M架构中,中断参数的参数压栈和上下文保护是硬件自动完成
任务阻塞是指任务因等待某个事件(如延时、信号量、队列消息)而暂时无法执行的状态。当任务阻塞时,系统会将其从就绪链表移至阻塞链表,并在事件满足后再移回调就绪链表
中断
Cortex-M使用的是8位的寄存器来配置中断的优先级,在STM32中,只使用了高4位来配置中断优先级,所以最大只有16级。
中断优先级要在5-15之间,宏定义设置的
抢占优先级:高优先级可以打断低优先级的中断
子优先级:相同抢占优先级的两个中断同时发生的时候,子优先级数值小的先执行(数值小优先级高)(子优先级的中断之间不会抢占)
建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理(设置成分组4)
中断数值越小,优先级越大
任务的数值越大,优先级越大,最大优先级数程序里设置了,0-31
临界区保护
任务和中断里面都可以设置临界区保护,只是函数不一样
适用于:
1.外设初始化
2.操作系统的代码有很多不能被打断
3.用户自己的需求
任务句柄
任务句柄内容有,
任务调度器
osKernelStart()里面有开启任务调度器vTaskStartScheduler() 实现过程:
创建空闲任务
如果使能软件定时器,就创建定时器任务
关闭中断,防止调度器开启前收到中断干扰,运行第一个任务时会打开中断
初始化全局变量,将任务调度器标志设置为已运行
调用函数xPortStartScheduler()
通信方式
队列、信号量、互斥锁、任务通知、事件标志组
信号量有二值信号量和计数信号量(获取和释放操作)
优先级反转
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
互斥信号量也叫互斥锁,优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
这样将优先级翻转的危害降到了最低,防止其他低优先级的任务一直执行,不
互斥锁不能用于中断,因为潜在死锁和休眠的风险,使用自旋锁可以
信号量只有非阻塞的能用,反正中断必须运行时间短
低功耗
FreeRTOS和Linux的区别
FreeRTOS | Linux |
---|---|
RTOS内核大小极简 | Linux庞大 |
内存需求很小 | 通常几十MB |
启动时间ms | 启动时间秒级 |
确定实时性,在严格的时间约束内,可预测地完成特定任务的能力 | 通用性、高吞吐量、多任务分时处理 |
开发工具轻量keil等 | 丰富如GCC、GDB |
STM32的Flash和RAM
SRAM是静态存储器,只要有电就能保持,所以适合高速缓存,读取后也不需要复写,KB-MB,L1、L2、L3、cache
DRAM是动态随机存取存储器,数据要周期性刷新才行,读取后要复写,但用到晶体管少,可以大量集成,用在主存储器,GB,内存条
srm32的flash就是ROM,(通常保存着text段、Code、Ro-data、Rw-data)
RAM(通常保存着堆、栈、bss段、data段、ZI-data、RW-data)
map文件会有
ZI-data:即 Zero Init-data, 0初始化的内存区的大小(该区域3个用途:0初始化的全局和静态变量+堆区+栈区)(RAM)。
RO-data:即 Read Only-data, 表示程序定义的常量,如 const 类型( FLASH)。
RW-data:即 Read Write-data, 非0初始化的全局和静态变量占用的RAM大小,同时还要占用等量的ROM大小用于存放这些非0变量的初值(FLASH+RAM)。
程序占用FLASH=Code + RO-data + RW-data 即map文件中ROM size
程序占用RAM = RW-data + ZI-data 即map文件中RW size
RW-data是非0初始化的数据,已初始化的数据需要被存储在掉电不会丢失的FLASH中,上电后会从FLASH搬移到RAM中。所以RW-data两边都占用一些
STM32F103C8T6是64KB Flash 20KB的RAM