楼主

[嵌入式] 【分享帖】单片机中使用动态内存分配(堆区)的艺术

[复制链接]
顾问薅电机狂魔
2019-7-7 11:19:05 只看该作者

马上注册,玩转Robomaster!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
昨天跟队里某退役嵌入式大佬吃饭,聊到数据结构的问题。我说我们视觉组在Linux上已经很习惯动态内存分配了,但是电控现在还是很少用。大佬说,在单片机中尝试避免动态内存分配也是一种哲学。

我很惊讶,因为动态内存分配相当常见。例如,不能在编译期确定长度的数组或者需要动态变化的变长数组(`std::vector`),还有链表(`std::list`)都不可避免地要用到动态内存分配。

回校之后,我仔细阅读了单片机中使用动态内存分配的注意事项,发现其中确实大有学问。

使用静态还是动态内存分配,取决于程序员的偏好。这两种方法各有优缺点。动态内存分配在有些时候更简单,并且可以最大限度地减少应用程序的最大RAM使用量;而静态内存分配可以指定变量放在特定的内存位置,可以在链接时确定最大RAM占用空间,避免运行时内存分配错误(在单片机上找运行时的错误有时候还是蛮花时间的)[1]。

另一个担忧,来自于内存碎片[2]的管理。由于堆区可以不按顺序地`new`和`delete`,最后堆区会出现很多的碎片空间。例如图4中,虽然有32 KB的空闲空间,但你无法连续地放下32 KB的数据。


                               
登录/注册后可看帖子

尽管现在的内存已经足够大,操作系统和STM32用的标准库也会使用线程池[3]和分页来尽可能缓解这个问题。但彻底解决这个问题的唯一方法,就是完全不使用动态内存分配。

如果你用了FreeRTOS,直接使用标准的动态内存分配方法(C中的`malloc`、`free`函数,C++中的`new`和`delete`操作符)还要多一层顾虑,因为它们在FreeRTOS中不是线程安全的[4]。这一点让我非常惊讶,因为在其他几乎所有平台的实现中,它们都是线程安全的[5]。

为了解决堆内存分配的诸多困难,FreeRTOS将内存分配API保存在其可移植层中。可移植层位于实现核心RTOS功能的源文件之外,允许提供适合于正在开发的实时系统的特定于应用程序的实现。当RTOS内核需要RAM时,它会调用`pvPortMalloc()`而不是`malloc()`;当释放RAM时,它会调用`vPortFree()`而不是`free()`。FreeRTOS为这些函数提供了5种不同的实现。heap_4会自动合并大块的空闲存储块,heap_5允许跨越多个不连续的内存进行合并。下面只翻译跟线程安全有关的heap_3,其他还请自己阅读参考链接[4]。

heap_3.c实现了标准C库`malloc()`和`free()`函数的简单包装,保证了它的线程安全。在大多数情况下,这些函数将与您选择的编译器一起提供。

这个实现:

  • 需要链接器设置堆,并使用编译器库来提供`malloc()`和`free()`实现。
  • 具有不确定性。
  • 可能会大大增加RTOS内核代码的大小。
请注意,使用heap_3时,FreeRTOSConfig.h中的`configTOTAL_HEAP_SIZE`设置无效。



看完这些资料,不得不说视觉组用智能指针一把梭真的是好快乐。虽然有这么令人头秃的细节,但是大多数情况下,你不要搞出内存泄漏也很少会把堆区耗尽,而正常程序也很少跨线程操作动态内存。但是,如果有一天你真的不幸遇到堆区错误,希望这篇文章可以帮到你。

参考链接:



有错误请在留言区指正
没问题我找赛务要电机了


跳转到指定楼层
沙发

[嵌入式] 【分享帖】单片机中使用动态内存分配(堆区)的艺术

[复制链接]
英雄视觉菜狗
2019-7-7 14:35:36 只看该作者
底层开发大佬
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

触屏版 | 电脑版

Copyright © 2024 RoboMasters 版权所有 备案号 粤ICP备2022092332号

快速回复 返回顶部 返回列表