FreeRTOS 堆内存管理

        每次创建任务,队列,互斥锁,软件定时器,信号量或事件组时,RTOS内核都需要RAM, RAM可以从RTOS堆自动动态分配(或者指定静态分配)。当RTOS内核需要RAM时,可以调用堆分配释放标准函数 pvPortMalloc() 及 vPortFree()进行分配和释放。

  • malloc() 和 free() 函数也可以动态创建对象,但它们并非是线程安全的,且不是确定性的(执行时间因调用而异),另外也占用了宝贵的代码空间,且在嵌入式系统上并不总是可用的(很多嵌入式工程可能使用newlib-nano做libc 或no-libc,甚至malloc 未实现)。
    • 非线程安全:多个任务同时执行malloc和free时,可能会同时修改heap链表,所以malloc 和 free并非线程安全,可能会导致内存重叠,崩溃,hardFault;
    • 不确定性:malloc 要实现通常要先遍历free block list, 遍历时,若能很快找到合适的block,执行时间就会很快,否则,时间就会很长,最差情况可能要扫描整个heap,因而malloc 的执行时间是不确定性的,这对于实时系统是不可接受的;
      • 实时系统必须要保证内存块的分配过程在可预测的确定时间内完成,否则实时任务对外部事件的响应也将变得不可确定。
    • 代码空间:嵌入式系统资源非常有限,但标准malloc实现很复杂,需要支持best fit, block split, block merge, free list, fragmentation等功能,代码可能占用2KB~10KB,而FreeRTOS heap 却很小,所以尽量不要使用malloc和free, 而使用占用空间较小的堆分配释放;

        FreeRTOS 将内存分配 API 保留在其可移植层,可移植层在实现核心 RTOS 功能的源文件之外, 在可移植层选择适用于该系统的堆实现(合适的内存管理算法)

    • 可移植层实现的原因:不同系统对动态内存的需求完全不同(RAM大小,实时要求),若FreeRTOS内核只提供一种malloc 算法,无法满足所有系统,因而,FreeRTOS提供统一调用接口,但具体实现不固定(可移植层中自由选择分配释放算法:heap_1, heap_2等)。
      • 这样kernel driver可以适用于不同的平台,更改平台时,只需要在可移植层选择适配该平台的内存管理算法;
    • FreeRTOS包含5个内存分配实现示例(Source/Portable/MemMang 目录下):
      • heap_1: 最简单,不允许释放内存(不太用)
        • 该方案适用于:应用程序从未删除任务,队列等任何对象;
        • 该方案时钟具有确定性,不会导致内存碎片化;
      • heap_2:允许释放内存,但不会合并相邻的空闲块(不太用,heap_4实现是该方案的首选)
      • heap_3:简单包装了标准 malloc() 和 free(),以保证线程安全
        • 该包装器只是使malloc 和 free 函数线程安全,但依然不具有确定性,且可能会大大增加RTOS 内核代码大小;
      • heap_4:合并相邻的空闲块以避免碎片化。包含绝对地址放置选项
        • 使用第一适应算法,且将相邻的空闲内存块合并成单个大内存块,因而与heap_2相比,导致空间严重碎片化的可能性小得多,但该方案依然具有不确定性(但比malloc实现更有效)
        • 与 heap_5先比,实现简单稳定,性能较好,但只能使用单一连续内存区域;
      • heap_5:如同 heap_4,能够跨越多个不相邻内存区域的堆
        • 允许使用多个不连续的内存区域组成一个逻辑heap,与heap_4相比,RAM利用率更高,支持非连续内存,但初始化复杂,代码复杂度增大,因而只有当RAM不连续,才会使用heap_5, 若RAM连续,系统简单,更推荐heap_4;

    堆栈使用和堆栈溢出检查

            每个任务都拥有其独立维护的堆栈。堆栈溢出是应用程序不稳定的一个很常见的原因,因此FreeRTOS提供两个可选机制,用于协助检测并纠正出现的堆栈溢出问题(使用 cconfigCHECK_FOR_STACK_OVERFLOW  来配置)。堆栈溢出检查会增加上下文切换的开销,因此建议只在开发或测试阶段使用此检查。

    • configCHECK_FOR_STACK_OVERFLOW 设置为0: 完全不做栈溢出检查;
    • configCHECK_FOR_STACK_OVERFLOW 设置为非0时,应用程序必须提供堆栈溢出钩子函数,并具备以下原型
    /* xTask 和 pcTaskName 参数分别将违规任务的句柄和名称传递给该钩子函数 */
    void vApplicationStackOverflowHook( TaskHandle_t xTask,
                                        char *pcTaskName );
    
    • configCHECK_FOR_STACK_OVERFLOW 设置为1: 在task切换时,检查栈指针是否越界(SP是否仍处于有效堆栈空间内),该方式开销小,实现简单,但只能检测严重溢出,若是轻微溢出(未破坏SP(stack pointer)则检测不到)
    • configCHECK_FOR_STACK_OVERFLOW 设置为2:task初始化时,在stack边界填充特定值(如0xA5), 当任务切换时会检测stack边界 是否为特定字符,该方式能检测渐进式溢出,更为可靠,但需要额外的内存访问及性能开销;
    • configCHECK_FOR_STACK_OVERFLOW 设置为3: 该方法仅适用于选定的端口,该方法将启用ISR堆栈检查,检测到ISR堆栈溢出时,会触发断言,但请注意这种情况下不会调用堆栈溢出钩子函数。

    参考

    内存管理

    FreeRTOS 堆内存管理

    Logo

    openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

    更多推荐