许多嵌入式系统不使用堆。许多确实使用堆的人使用必须按顺序搜索的串行堆,因此可能非常慢。大多数其他嵌入式系统可能使用广泛可用的dlmalloc,它以无法更改的固定结构为代价提供快速操作。
一个名为eheap的新嵌入式堆在这些极端情况之间提供了一个中间地带。它为在 RTOS 上运行或独立运行的嵌入式系统提供高性能、适应性和安全性。eheap 旨在为特定的嵌入式系统轻松定制。它符合 malloc()、free()、realloc() 和 calloc() 的 ANSI C 标准,并为堆控制和安全提供附加功能。
这是关于 eheap 的三篇文章系列中的第一篇:
• eheap 第 1 部分:配置
• eheap 第 2 部分:增强调试
• eheap 第 3 部分:自我修复
eheap 是一种 bin 类型的堆,类似于 dlmalloc。假设读者熟悉这种类型的堆结构,或者对这些堆如何操作的细节不感兴趣。因此,这篇文章和后续文章将讨论 eheap 的独特之处,而不是它的工作原理。对于那些想要更详细信息的人,请参阅eheap 与 dlmalloc。
嵌入式系统特性
eheap 设计用于以下方面:
• 范围广泛的 RAM 大小,从非常小到大。
• 预计永远运行。
• 需要高性能和确定性操作。
• 高优先级任务必须能够抢占并快速运行。
• 小代码量通常是必要的。
• 每个嵌入式系统的堆要求范围相对较窄。
• 嵌入式系统有大量空闲时间。
• 嵌入式系统对自我修复的需求不断增长。
bin 配置
与其他 bin 类型的堆一样,eheap 有一个用于小块的小型 bin 数组 (SBA) 和一个用于大块的上部 bin 数组 (UBA)。SBA 就像一组块池,但速度不快。与 dlmalloc 不同,UBA 中的 bin 大小是连续 2 字节的幂(256、512 等),eheap 允许选择 bin 大小以适合应用程序。eheap bin 大小由一个恒定的bin 大小数组确定,由程序员指定。对于小 bin,bin 大小是 bin 的块大小。[1] 对于大 bin,bin 大小是 bin 中的最小块大小。一个 bin 的最大块大小是下一个 bin 的大小 – 8.[2]
为了安全起见,bin 大小数组可以位于 ROM 中,也可以位于本地 RAM 中以获得更高的性能。堆初始化函数使用它来创建 eheap 所需的 bin 和所有相关变量。可以通过更改 bin 大小数组中的值、重新编译和重新启动应用程序来快速重新配置堆。这种快速重新配置支持一种启发式方法来调整 eheap 以获得特定系统的最佳性能。
小型系统示例
小型系统可能有一个 bin 大小的数组,如图 1 所示。
[图 1]
对于这个系统,SBA 由 bin 0、1 和 2 组成,分别保存大小为 24、32 和 40 的块;bin3 包含从 48 到 120 的 10 个尺寸;bin4 是一个只包含大小为 128 的小 bin;bin 5 包含从 136 到 248 的 14 个尺寸;和 bin6 ,顶部 bin,包含从 264 开始的所有大小。-1 标记大小数组的结尾。在这个特定的嵌入式系统中,对小块的需求是有限的,对中等块有一些需求,而对 264 字节以上的大块的需求很少。请注意,为 128 字节的块添加了一个小 bin,这些块在此系统中经常使用。
上垃圾箱搜索
eheap 和 dlmalloc 在 SBA 方面是相似的。两者都针对非常快速的访问进行了优化,这是面向对象程序通常需要的。主要区别在于 UBA,其中 dlmalloc 在 free() 操作期间构建树以指导 malloc() 操作期间的最佳分配。树节点链接巧妙地存储在块本身中。
对于 eheap,每个 bin 都有一个适合该 bin 的空闲块列表。在 free() 操作期间,如果块大于 bin 的第一个块,则将其放在 bin 空闲列表的末尾;否则,将其放在 bin 空闲列表的开头。在空闲时间,大的 bin 空闲列表按增加的块大小进行排序。在 malloc() 操作期间,会在大的 bin 空闲列表中搜索第一个足够大的块,这也是最合适的。
eheap 最适合具有偏好块大小并且不使用广泛的块大小的系统。如上例所示,可以在 UBA 中放入一个小 bin 以提供常用尺寸。实现此结果的另一种方法是使大的 bin 大小等于经常使用的块大小。那么最适合的将始终是 bin 中的第一个块,然后是更大的尺寸。
在某种程度上,大型垃圾箱就像已排序的迷你串行堆一样运行。过多的搜索时间可能会受到箱大小与系统需求的明智选择以及对合并的明智使用的限制,如下所述。堆设计的一个原则是处理更大的块需要更长的时间,因此更长的 malloc() 时间不会降低平均性能。当然, malloc() 不能花费太长时间以致更高优先级的任务错过它们的截止日期。
图 2 显示了一个 bin 堆示例。
【图2】
在这种情况下,没有SBA也没有UBA,只有top bin。此堆接近串行类型堆,但仍比它具有优势。
图 3 显示了一个没有 SBA 堆的示例。
[图 3]
用 C 编写的嵌入式系统可能对小块几乎没有用处,或者,块池可能更适合特定系统中的小块。在这种情况下,bin 0 以 24 开头,因为必须覆盖 24 字节及以上的所有大小,但 bin 0 可能仅用于 64 字节及更大的块。 [3]
图 4 显示了一个没有 UBA 堆的示例。
[图 4]
在这个堆中,SBA 覆盖大小为 24 到 64 的块,所有其他块进入 bin 6,覆盖 72 个字节及以上。
几乎无限的堆配置是可能的。目前,为了获得最佳性能,eheap 限制为 32 个 bin。如果更多的 bin 被证明对某些嵌入式系统有益,则可以增加此限制。
Donor chunk
eheap 有一个可选的donor chunk (dc),它类似于dlmalloc 的指定牺牲块(dvc),但操作方式不同并且服务于不同的目的:
• 快速小块分配的初始源。
• 将小块分离到下堆,将大块分离到上堆。
• 基于缓存的系统的小块本地化。
如果启用,则 dc 的空间将由程序员在顶部块 (tc) 下方分配。通常,它只是总初始可用空间的一小部分,它是 dc 和 tc 的组合。如果用于分配小块的选定 SBA 箱为空,则从 dc 中分割该块,而不是从下一个较大的占用箱中取出它。这不仅更快,而且还避免了减少更大的垃圾箱。由于 dc 最初是堆的较低部分,因此小块将来自较低的堆。这改善了小块分配的本地化,并且还倾向于按块大小隔离堆,以帮助减少使用中的小块,以免阻塞大块的合并。
在堆使用一段时间后,小块分配将主要来自 SBA 箱,很少来自 dc。此时可以关闭使用 dc 并将剩余的内容释放到垃圾箱中。或者,可以继续使用 dc。当下面和相邻的已释放块合并到其中时,它会增长,并且随着从中分配块,它会缩小。这将提高空 SBA 箱的性能。
块合并和泄漏箱
dlmalloc 和 eheap 之间的一个重要区别是 dlmalloc 总是合并相邻的空闲块,而 eheap 允许延迟合并。eheap 合并由其cmerge 模式控制,应用程序可以打开或关闭该模式。在堆初始化之后它是关闭的。
延迟合并的原因是块合并会产生泄漏的 bin。例如,假设一个 24 字节的块被释放到 bin 0,而物理上相邻的 48 字节的空闲块驻留在 bin 3。如果启用合并,这些块将被合并成一个 72 字节的块,该块将被放入bin 6。如果应用程序需要 24 和 48 字节的块而不是 72 字节的块,这不利于获得最佳性能。此外,在 free() 操作期间,将 48 字节块出列并合并它需要额外的时间。如果后续 malloc() 获取 72 字节块,将其拆分为 24 和 48 字节块以获取其中任何一个,然后将剩余部分重新排队,则这是浪费时间。
与可能使用多种块大小的一般处理不同,嵌入式系统倾向于使用有限的块大小。因此,泄漏箱对于嵌入式系统来说不是最佳的。
cmerge可以直接控制也可以自动控制。如果自动模式amerge为 ON,则当可用空间低于下限时, cmerge 将打开,当可用空间高于上限时,它将关闭。限制由配置常数决定。或者,可以使用空闲块的数量、平均空闲块大小或 bin 中的总空间,这取决于在特定系统中最有效的方法。
概括
eheap 是一种新的嵌入式堆,可以通过调整 bin 大小数组,决定是否使用 dc 以及它应该多大,以及决定是否使用延迟合并以及如果使用,如何控制它。这些选项允许将eheap调整到特定系统,以实现最佳系统性能,而不会出现碎片导致分配失败。eheap 可免费用于非商业用途。
审核编辑:郭婷
-
嵌入式
+关注
关注
5081文章
19073浏览量
304321 -
RTOS
+关注
关注
22文章
810浏览量
119531
发布评论请先 登录
相关推荐
评论