我遇到的许多嵌入式软件开发人员提出的一个我觉得特别有趣的话题是动态内存分配——在需要时获取内存块。这种看似简单和常规的操作会带来大量问题。这些并不局限于嵌入式开发——许多桌面应用程序都会出现内存泄漏,这会影响性能,并且会使系统重新启动很常见。但是,我担心嵌入式开发环境。
通常不建议将malloc()用于嵌入式应用程序的原因有很多:
- 该函数通常不可重入(线程友好),因此将其与实时操作系统一起使用可能具有挑战性。
- 它的性能不是确定性的(可预测的),因此分配一个内存块所花费的时间可能是非常可变的,这在实时应用程序中是一个挑战。
- 内存分配可能会失败。
尽管这些都是有效的观点,但它们可能并不像看起来那么重要。
仅当从多个线程调用函数时,重入才是一个问题。编写一个可重入的malloc()函数是非常可行的,但也可以使用标准版本以使重入变得不必要。只需将所有内存分配活动本地化为单个任务。您甚至可以创建一个唯一功能是动态内存分配的任务;其他任务将简单地发送一条消息,请求分配或释放内存块。
并不总是需要确定性。并非应用程序是实时的,并且那些不一定需要对其操作的所有部分确定性的应用程序。
分配失败可能是个问题,但可以管理。如果malloc()函数无法分配所请求的内存,则它会返回一个空指针。必须检查此响应并采取适当的措施。如果失败是由于内存耗尽,很可能是设计缺陷——没有为堆分配足够的内存。然而,分配失败的一个常见原因是堆碎片。有足够的可用内存,但它不在连续区域中。这种碎片的出现是因为内存以随机方式分配和释放,从而导致内存的分配和空闲区域。有两种方法可以消除碎片:
首先,如果应用程序允许,只需确保使用遵循这种模式的代码按顺序完成分配和释放:
a = malloc(1000); b = malloc(100); c = malloc(5000); ... 免费(c); 免费(乙); 免费(一);
当然,这通常是不可能的。因此,需要另一种选择。
事实证明,许多应用程序并不需要malloc()提供的所有灵活性。所需的内存块具有固定大小(或少量不同大小)。为固定大小的块编写内存分配器非常简单;这消除了碎片化,如果需要,可以很容易地确定性。毫不奇怪,大多数 RTOS 都有以这种方式分配内存块的服务调用。
不管它的不可预测性如何, malloc()还有另一个问题——它往往相当慢。这并不奇怪,因为该函数的功能非常复杂。基于块的分配器的内在简单性非常有效地解决了这个问题。
但是,如果应用程序在不可预测的时间确实需要随机大小的内存块怎么办?
实现这种灵活性同时避免碎片和不确定性的一种方法是构建一个分配器,根据请求的内存块大小从多个“池”中选择块。为池选择块大小的一个好方法(如果您事先不知道需要的块大小)是使用几何系列,如 16、32、64、128 字节。然后分配将像这样工作:
显然,一些分配会非常有效:16 字节池中的 16 字节。有些会非常好;来自 32 字节池的 31 个字节。其他人会没事的;来自 16 字节池的 9 个字节。还有一些效率低下;来自 128 字节池的 65 个字节。总体而言,这些低效率是为速度、确定性和消除碎片化的好处付出的小代价。
审核编辑:汤梓红
-
嵌入式
+关注
关注
5068文章
19008浏览量
302989 -
应用程序
+关注
关注
37文章
3240浏览量
57594 -
内存分配
+关注
关注
0文章
16浏览量
8295
发布评论请先 登录
相关推荐
评论