使用malloc()?为什么不?
我遇到的许多嵌入式软件开发人员都提出了一个特别有趣的主题,即动态内存分配。–在需要时获取内存块。这种看似简单且常规的操作带来了很多问题。这些不限于嵌入式开发–许多桌面应用程序会出现内存泄漏,从而影响性能,并使系统重新启动很常见。但是,我担心嵌入式开发环境。
原因有很多 malloc() 通常不建议用于嵌入式应用程序:
- 该函数通常是不可重入的(线程友好的),因此将其与实时操作系统一起使用可能具有挑战性。
- 它的性能不是确定性的(可预测的),因此分配内存块所花费的时间可能非常可变,这在实时应用程序中是一个挑战。
- 内存分配可能会失败。
尽管这些都是有效的要点,但它们可能并不像看起来那样重要。
如果从多个线程调用函数,则重入仅是一个问题。写一个可重入的书是很可行的 malloc() 功能,但也可以使用标准版本,而无需重新输入。只需将所有内存分配活动本地化到单个任务即可。您甚至可以创建一个任务,其唯一功能是动态内存分配。其他任务将仅发送一条消息,请求分配或取消分配内存块。
确定性并不总是必需的。不是应用程序是实时的,应用程序不一定是实时的,而是需要确定性的。
分配失败可能是一个问题,但可以对其进行管理。这 malloc() 如果无法分配请求的内存,则该函数将返回空指针。必须检查此响应并采取适当的措施。如果故障是由于内存耗尽而造成的,则很可能是设计缺陷–没有足够的内存分配给堆。但是,分配失败的常见原因是堆碎片。有足够的可用内存,但是它不在连续的区域中。之所以出现这种碎片,是因为内存是以随机方式分配和释放的,从而导致分配和释放内存区域。有两种消除碎片的方法:
首先,如果应用程序允许,仅需确保使用遵循这种模式的代码按顺序完成分配和取消分配:
a = malloc(1000); b = malloc(100); c = malloc(5000); ... free(c); free(b); free(a);
当然,这通常是不可能的。因此,需要另一种选择。
事实证明,许多应用程序并不需要所有的灵活性, malloc() 负担得起。所需的内存块具有固定大小(或少量不同大小)。为固定大小的块编写内存分配器非常简单;这样就消除了碎片,并且可以根据需要轻松地确定性。毫不奇怪,大多数RTOS都有服务调用以这种方式分配内存块。
不管它的不可预测性,还有另一个问题 malloc() –速度通常很慢。这并不奇怪,因为该功能的功能非常复杂。基于块的分配器的内在简单性非常有效地解决了这个问题。
但是,如果应用程序确实确实需要在不可预测的时间随机大小的内存块怎么办?
在避免碎片和不确定性的同时,实现这种灵活性的一种方法是构建一个分配器,该分配器根据请求的内存块大小从多个“池”中选择块。选择池的块大小的一种好方法(如果您事先不知道所需的块大小)是使用几何级数,例如16、32、64、128字节。然后分配将像这样工作:
显然,某些分配将非常有效:16字节池中的16字节。有些会很好。 32字节池中的31字节。其他人会没事的。 16字节池中的9字节。还有其他人效率低下。 128字节池中的65字节。总体而言,这些效率低下的代价是要付出速度,确定性和消除分散带来的好处。
![]() |
相关内容:
要获得更多嵌入式产品, 订阅嵌入式’的每周电子邮件通讯.
有趣的是,但是您的池方案无法解决分配大小趋向于定期增长到给定大小的情况,因为较小的块一旦释放就不会合并。更好的替代方法是使用Buddy分配器,尤其是如果要坚持使用两个块sisze的能力,因为当释放小块时,它们倾向于合并小块。
顺便说一句,对于小尺寸的伙伴算法,与提出不使用两种尺寸的幂(总是高速缓存行大小的倍数)的池算法相结合,Linux内核非常有效地使用了这种伙伴算法来处理不可预测的分配。快速可靠的方式。