(嵌入式八股)No.5 FreeRTOS(2)
5 内存堆栈管理和管理算法(heap1-heap5)(最好看一下源码)(掌握)
这个图要记住(再复习一下):
了解:
我们在C程序中定义的函数、全局变量、静态变量经过编译链接后,分别以section的形式存储在可执行文件的代码段、数据段和BSS段中。当程序运行时,可执行文件首先被加载到内存中,各个section分别加载到内存中对应的代码段、数据段和BSS段中。需要动态链接的动态库也被加载到内存中,完成代码的链接和重定位操作,以保证程序的正常运行。
5.1 RTOS自带的内存管理算法:
heap_1(了解)
- 特点:最简单的实现,只支持静态分配,不支持 free()。
- 实现方式:简单的线性分配(bump pointer),只增长,不回收。
- 优缺点:✅ 简单,可靠,没有碎片;❌ 不支持释放内存,无法适应动态变化的内存需求。
- 适用场景:固定任务和对象数量的系统,内存需求可在启动时确定。
解读:
它只实现了pvPortMalloc,没有实现vPortFree。
如果你的程序不需要删除内核对象,那么可以使用heap_1:
- 实现最简单
- 没有碎片问题
- 一些要求非常严格的系统里,不允许使用动态内存,就可以使用heap_1
它的实现原理很简单,首先定义一个大数组:
/* Allocate the memory for the heap. */
#if ( configAPPLICATION_ALLOCATED_HEAP == 1 )
/* The application writer has already defined the array used for the RTOS
heap - probably so it can be placed in a special segment or address. */
extern uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#else
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];
#endif /* configAPPLICATION_ALLOCATED_HEAP */
然后,对于pvPortMalloc调用时,从这个数组中分配空间。
FreeRTOS在创建任务时,需要2个内核对象:task control block(TCB)、stack。
heap_2(了解)
- 特点:支持动态分配和释放内存(malloc() + free()),采用 首次适应(First Fit)算法。它支持vPortFree。
- 实现方式:内存块链表维护空闲和已分配块;释放内存时将块加入空闲链表,可被后续分配复用。
- 优缺点:✅ 可以动态分配和释放内存;❌ 长期运行可能产生 碎片,不适合实时性要求高的系统。
- 适用场景:允许动态内存但任务数量和对象数量变化不频繁的系统。
Heap_2之所以还保留,只是为了兼容以前的代码。新设计中不再推荐使用Heap_2。建议使用Heap_4来替代Heap_2,更加高效。
解读:
最佳匹配算法:(熟悉)
- 假设heap有3块空闲内存:5字节、25字节、100字节
- pvPortMalloc想申请20字节
- 找出最小的、能满足pvPortMalloc的内存:25字节
- 把它划分为20字节、5字节返回这20字节的地址剩下的5字节仍然是空闲状态,留给后续的pvPortMalloc使用
与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。
但是,如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。所以它适合这种场景:频繁地创建、删除任务,但是任务的栈大小都是相同的(创建任务时,需要分配TCB和栈,TCB总是一样的)。
虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、free。
heap_3(了解)
- 特点:直接调用 C 库的 malloc() / free()。
- 实现方式:FreeRTOS 不管理堆空间,而是封装标准库函数。
- 优缺点:✅ 使用标准库实现,方便移植;❌ 受限于标准库,可能产生碎片和不可预测延迟;❌ 在裸机或无标准库的环境不适用。
- 适用场景:使用现成 RTOS + C 库的系统,快速开发或非严格实时系统。
heap_4(这个重点看一下源码)
- 特点:支持 动态分配 + 释放 + 内存块合并(coalescence)。
- 实现方式:内存空闲块释放时,会合并相邻空闲块,减少碎片;(注意合并是按相邻地址合并的)适合频繁申请和释放小块内存。
- 优缺点:✅ 支持动态分配和释放,碎片比 heap_2 少;✅ 可用于实时性较高的系统,但仍存在延迟不可完全确定。
- 适用场景:需要动态分配和释放内存,并且任务较多,碎片控制重要的嵌入式系统。
heap_5
- 特点:在 heap_4 基础上,支持 多个不连续内存区域。
- 实现方式:可以定义多个内存区域作为堆(xHeapRegion[]),heap_5 会管理它们;由链表管理每个区域内存块可自由分配,释放时可合并。
- 优缺点:✅ 适合多段 RAM 或外部 SRAM 的系统;✅ 支持动态分配、释放、碎片整理;❌ 实现最复杂,开销稍大。
- 适用场景:MCU 内存分布不连续,或者需要管理多个 SRAM 区域的系统。
总结:
6 任务间通信(必考)
在多任务系统中:
- 一个任务可能产生数据(如传感器采集),另一个任务消费数据(如处理或发送)。
- 为保证 数据一致性和同步,任务间不能直接共享全局变量(容易产生竞态条件)。
- RTOS 提供 线程安全的数据交换和同步机制。
6.1 FreeRTOS 常用任务间通信方式
① 队列(Queue)(这个地方看一下队列的底层实现还有读写队列的函数实现)(信号量的本质其实就是队列,所以看队列的就足够了)
概念:FIFO(先进先出)缓冲区,用于任务间传递消息或数据块。
特点:
可以在任务间、任务与中断间传递数据;
自动处理同步(阻塞/等待);
支持多任务同时发送和接收。
xQueueSend(queue, &data, portMAX_DELAY); // 发送数据 xQueueReceive(queue, &recvData, portMAX_DELAY); // 接收数据
② 信号量(Semaphore)
- 概念:二值或计数信号,用于任务间同步或互斥。
- 类型:二值信号量(Binary Semaphore):事件通知、任务同步。计数信号量(Counting Semaphore):控制资源数量。互斥量(Mutex):特殊二值信号量,用于共享资源保护,支持 优先级继承。
xSemaphoreGive(sem); // 释放信号量 xSemaphoreTake(sem, portMAX_DELAY); // 获取信号量
③ 事件组(Event Group)
- 概念:多个任务共享一个事件位,每个位表示一个事件状态。
- 用途:等待多个事件组合;用于任务间复杂同步。
xEventGroupSetBits(eventGroup, BIT_0); // 设置事件 xEventGroupWaitBits(eventGroup, BIT_0 | BIT_1, pdTRUE, pdFALSE, portMAX_DELAY); // 等待事件
④ 任务通知(Task Notification)
- 概念:每个任务自带一个 32 位通知值,用于任务间通信或同步。
- 特点:最轻量、最快速的通信方式;可以用作信号量、计数器或事件标志;无需额外内存分配。
xTaskNotifyGive(taskHandle); // 发送通知 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 等待通知
⑤ 任务间通信的注意事项
6.2 互斥量(必考)(这个底层实现也去看一下)
定义:
互斥量(Mutex)是一种非常重要的同步机制,用于保护共享资源,确保同一时间只有一个任务可以访问该资源。互斥量主要用于解决多任务环境下的资源竞争问题。
Mutex(互斥量) 是一种 二值信号量(Binary Semaphore),用于保护共享资源,防止多个任务同时访问产生冲突。
它的核心作用是 “同一时刻只允许一个任务访问共享资源”。
与二值信号量的区别:
互斥量的一个重要特性是优先级继承(Priority Inheritance)。当高优先级任务等待低优先级任务持有的互斥量时,RTOS会临时提升低优先级任务的优先级,使其尽快释放互斥量。这样可以避免优先级反转问题,确保系统的实时性。(这个地方可以去看一下源码!!!)
互斥量的注意事项
- 避免死锁不要在持有互斥量的情况下调用可能导致任务阻塞的函数(如osDelay、osSemaphoreAcquire等)。避免嵌套锁(即在一个任务中多次获取同一个互斥量)。
面试必考:
优先级继承:当低优先级任务持有互斥量,高优先级任务等待时,低优先级任务暂时继承高优先级,防止优先级反转。
什么是优先级反转?
优先级反转是指一个高优先级任务被低优先级任务阻塞,而低优先级任务又因为其他原因(如等待资源)无法及时完成,从而导致高优先级任务无法按预期运行的现象。
优先级反转的典型场景
假设系统中有三个任务:
- 高优先级任务(Task H):需要访问共享资源。
- 中优先级任务(Task M):正在访问共享资源。
- 低优先级任务(Task L):与Task M共享其他资源。
如果Task M正在访问共享资源(通过互斥量保护),而Task H需要访问该资源,Task H会被阻塞。此时,如果Task L就绪并开始运行,它会抢占Task M,因为Task L的优先级高于Task M。这会导致Task M无法及时完成对共享资源的访问,从而导致Task H被阻塞更长时间。这种现象就是优先级反转。
7 FreeRTOS中断优先级配置(重要)
7.1 NVIC基础知识
NVIC的全称是Nested vectoredinterrupt controller,即嵌套向量中断控制器。
- 硬件基础:M3/M4内核为每个中断提供了一个8位优先级寄存器,理论上可配置256个优先级。
- 厂商裁剪:芯片厂商(如ST)会根据实际应用需求,只使用这8位中的一部分以简化设计。STM32F1/F4系列只使用了高4位 [7:4],低4位 [3:0] 读为0且无效。
- 结果:因此,STM32F1/F4实际上只有 2^4 = 16级可编程中断优先级(0-15,数值越小优先级越高)。
详细规则与解析:
- 默认配置系统复位后,默认使用 优先级分组0(即只有子优先级,没有抢占优先级)。这意味着所有中断默认都不能嵌套,因此在初始化时必须根据需求重新配置分组(如之前提到的Group 4)。
- 中断嵌套的唯一条件只有当新中断的抢占优先级比当前正在执行的中断的抢占优先级更高(数值更小)时,才会发生嵌套。子优先级与嵌套无关。
- 子优先级的本质它只是一个 “仲裁器” ,用于处理同时 pending 的中断。它不赋予任何中断打断另一个中断的权力。一个正在执行的低子优先级中断,会阻塞同抢占级的高子优先级中断。
- 最高优先级中断(不可配置)Reset、NMI、HardFault 这三个异常拥有固定的、负数值的优先级,意味着它们的优先级天然高于任何普通中断,且用户无法修改。
- 系统中断与外部中断的关系重要澄清:诸如 PendSV、SVC、SysTick 这样的系统中断,和 SPI、USART 这样的外部中断,在NVIC中的地位是平等的。它们的优先级都在同一个NVIC寄存器中设置,遵循相同的优先级规则。系统中断的优先级并不天生比外部中断高;它们的优先级高低完全由用户分配的优先级数值决定。
7.2 使用FreeRTOS时如何配置外设NVIC
官方强烈建议将STM32F1/F4系列的NVIC优先级分组设置为Group 4,这意味着仅使用0-15共16级抢占优先级,而完全取消了子优先级。这种配置极大地简化了中断管理,因为任何高抢占优先级的中断都能直接抢占低优先级的中断,行为清晰直观;但务必注意,此分组必须在系统初始化时一次性设置完成,且整个程序运行期间严禁再次修改,否则会导致不可预知的系统错误。
static void TIM_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
/* 抢占优先级设置,优先级分组为4的情况下,抢占优先级可设置范围0-15 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
/* 子优先级设置,优先级分组为4的情况下,子优先级无效,取数值0即可 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
在这里继续强调下这一点,在NVIC分组为4的情况下,抢占优先级可配置范围是0-15,那么数值越小,抢占优先级的级别越高,即0代表最高优先级,15代表最低优先级。
7.3 FreeRTOS配置选项中NVIC相关配置
FreeRTOSConfig.h配置文件中设置到NVIC中断的有如下几个选项:
/* Cortex-M specific definitions. */
#ifdef __NVIC_PRIO_BITS
/* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4 /* 15 priority levels */
#endif
/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01
/* Interrupt priorities used by the kernel port layer itself. These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configPRIO_BITS 4
此宏定义用于配置STM32的8位优先级设置寄存器实际使用的位数。STM32F103,STM32F407和STM32F429都是使用的4位。
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0x0f
此宏定义是用来配置FreeRTOS用到的SysTick中断和PendSV中断的优先级。在NVIC分组设置为4的情况下,此宏定义的范围就是0-15,即专门配置抢占优先级。这里配置为了0x0f,即SysTick和PendSV都是配置为了最低优先级,实际项目中也建议大家配置最低优先级即可。
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x01
此宏定义比较重要,定义了受FreeRTOS管理的最高优先级中断。简单的说就是允许用户在这个中断服务程序里面调用FreeRTOS的API的最高优先级。
7.4 不受FreeRTOS管理中断的深入讨论
在讲解不受FreeRTOS管理的中断前,需理解中断延迟——即从中断触发到其服务程序第一条指令执行的时间,这是衡量RTOS实时性的关键指标。FreeRTOS内核中开关全局中断的操作会增大此延迟,因为在此期间触发的中断必须等待中断重开才能响应,严重影响实时性。为解决此矛盾,FreeRTOS并未采用像uCOS那样开关所有中断(使用PRIMASK寄存器)的方法,而是创新地使用BASEPRI寄存器来仅屏蔽优先级低于某阈值的中断(即受FreeRTOS管理的中断),而允许更高优先级的中断(即不受管理的)随时响应,从而确保了紧急中断的极速执行,兼顾了系统内核保护与硬实时需求。
对寄存器basepri我们举一个例子,帮助大家理解,比我们配置寄存器basepri的数值为16,所有优先级数值大于等于16的中断都会被关闭,优先级数值小于16的中断不会被关闭。对寄存器basepri寄存器赋值0,那么被关闭的中断会被打开。这个就是FreeRTOS开关中断的实现方案。
8 低功耗
RTOS 提供 任务调度和睡眠管理机制,可以通过管理空闲任务实现低功耗。
空闲任务(Idle Task):当没有就绪任务时,调度器会运行空闲任务。
- FreeRTOS 默认提供 vApplicationIdleHook(),可在空闲任务中进入低功耗模式。
低功耗实现依赖 MCU 芯片的节能模式(Sleep、Stop、Standby 等)。
8.1 典型低功耗机制
空闲挂起(Idle Hook)
- 在空闲任务中调用 MCU 的 低功耗指令:
- void vApplicationIdleHook(void) { __WFI(); // Wait For Interrupt,CPU 睡眠等待中断}
- 原理:CPU 停止执行,等待中断唤醒(如 SysTick、外部中断)。
Tickless Idle(无滴答低功耗)
- FreeRTOS 提供 Tickless Idle 模式,在空闲期间 停止 SysTick,减少频繁唤醒。
- 实现方式:调度器预测空闲时间;设置 MCU 定时器唤醒时间;进入低功耗模式;定时器中断或外部事件唤醒系统。
- 优点:大幅度降低空闲期间功耗,适合低功耗应用。
外部中断唤醒
- MCU 可配置 GPIO、外部传感器或通信中断唤醒 CPU;
- 保证系统 低功耗等待事件,并在事件到来时及时响应。
任务优先级与低功耗策略
- 高优先级任务唤醒时,CPU 立即运行;
- 空闲期间,低优先级任务挂起,CPU 可进入低功耗模式;
- 配合 Tickless Idle,可精确控制唤醒时机。
8.2 总结
在 RTOS 中实现低功耗,主要通过:
- 空闲任务挂起 CPU(Idle Hook + WFI/WFE);
- Tickless Idle 模式减少系统时钟唤醒;
- 外部中断或事件唤醒保持响应性;
- 合理任务调度,最大化空闲时间。
核心思想:空闲时让 CPU 停止运行,只在需要时唤醒,从而降低整体功耗。
#大厂面试问八股多还是项目多?##实习##嵌入式软件八股#从入门到上岸,一站式搞定求职! 本硕纯机械,无竞赛无论文,后转行嵌入式软件开发(因为课题组师哥转嵌入式拿到30Woffer之后狠狠心动),秋招最终收获35W+offer可以为27届或者28届的的UU们提供参考,可以关注一下!!!

