19

因此,在我明年将在大学学习的嵌入式系统单元中,我们将了解到动态数据结构在嵌入式系统程序中是一件坏事。但是讲义没有说明原因。

现在我正在研究一个中等规模的嵌入式系统\“LURC”控制器,主要是利用 AVR169MEGA 的“蝴蝶”演示板的外围设备。产生4个PWM信号来控制伺服和ESC。并提供 9 段 LCD 屏幕。

现在我想不出比队列更好的方式来存储指令,因为它们是通过 USART 串​​行接收的。尤其是对于我需要等到收到未知数​​量的数据的事情:例如要在 LCD 屏幕上显示的字符串。

那么为什么不在嵌入式系统的微控制器上使用动态数据结构呢?仅仅是您处于内存严重受限的环境中,并且必须确保您的 malloc 成功吗?

4

9 回答 9

25

在嵌入式系统中不使用 malloc(或等效项)的原因有很多。

  • 正如您所提到的,重要的是不要突然发现自己没有记忆。
  • 碎片化——嵌入式系统可以运行多年,碎片化会导致内存的严重浪费。
  • 不是真的需要。动态内存分配允许您在不同时间重用相同的内存来做不同的事情。嵌入式系统倾向于一直做同样的事情(启动时除外)。
  • 速度。动态内存分配要么相对较慢(并且随着内存碎片化而变慢),要么相当浪费(例如伙伴系统)。
  • 如果您打算为不同的线程和中断使用相同的动态内存,那么分配/释放例程需要执行锁定,这可能会导致服务中断不够快的问题。
  • 动态内存分配使调试变得非常困难,尤其是使用嵌入式系统上可用的一些有限/原始调试工具时。如果你静态分配东西,那么你就知道东西在哪里,这意味着检查某物的状态要容易得多。

最重要的是 - 如果您不动态分配内存,那么您就不会发生内存泄漏。

于 2009-11-12T23:00:56.450 回答
5

好吧,许多较小的微控制器没有 MMU 之类的东西,也没有像您可以使用的具有良好堆的操作系统。

对于那些这样做的人,只要您对所要求的内存量保持清醒的限制,我真的认为它没有什么大问题。

然而,许多嵌入式系统也是实时系统。如果您的应用程序对运行需要多长时间有严格的截止日期,那么您将在动态分配方面遇到麻烦。大多数堆实现使用的算法没有很好的运行时限制。在某些(可能很少见)情况下,它们的运行时间会比正常情况长得多。有一些实时堆实现,但它们的使用并不广泛。一般规则是在初始化后避免硬实时系统中的任何动态分配或释放。

于 2009-11-12T22:53:47.147 回答
5

这取决于我认为在过去 4 年中“嵌入”的含义有所扩大。

传统上,嵌入式设备上有微控制器,通常没有操作系统。没有受保护的内存,并且是单线程的。您必须非常小心分配内存,因为例如,当您只有 32KB 可用内存时,它很容易用完。所以一般来说,我们会用固定大小的缓冲区编写我们的代码,并且从不使用 malloc 或 at 如果它每次都使用过 - 非常谨慎。

在过去的几年里,我们看到本质上是单芯片 pc 或微型板,它们很容易与我们的旧 Pentium PC 一样强大。RAM 价格现在如此便宜且如此之小,以至于内存限制与以前完全不同。他们还经常运行嵌入式 linux 或 wince,所以现在我们能够更自由地使用动态内存。

这样就可以使用更广泛的语言,包括 Java、C++、许多脚本语言和其他提供缓冲区溢出保护和异常处理的语言以及其他更高级别的语言。所以真的,那些老问题不像以前那样了。

我怀疑所有这些新的可用硬件都会带来一系列新问题。

于 2009-11-12T23:04:45.230 回答
4

嵌入式环境中的动态内存本身并没有什么问题,尽管在嵌入式环境中它通常不会给你带来太多好处。

在我看来,使用环形缓冲区是一个非常好的主意(这是 I/O 驱动程序等非常常见的数据结构)。这样,如果由于某种原因您无法为队列提供服务,内存使用仍然是确定性的。

使用一些宏可以在编译时分配可变大小的结构。

例如 -

    //we exploit the fact that C doesn't check array indices to allow dynamic alloc of this struct
    typedef struct ring_buf_t {
        int element_sz,
            buffer_sz,
            head,
            tail;
        char data[0];
    } ring_buf_t;

   #define RING_BUF_ALLOC_SZ(element_sz,n_elements) (sizeof (ring_buf_t) + \
                                                      (element_sz) * (n_elements))

    char backing_buf[RING_BUF_ALLOC_SZ (sizeof(type_to_buffer), 16)];

    //ring_buf_init() casts backing buf ring_buf_t and initialises members...
    ring_buf_t *ring_buffer = ring_buf_init (element_sz, n_elemements, backing_buf);

;

这种模式是一个动态大小的缓冲区,具有保证的内存使用。当然,其他类型的数据结构(列表、队列等)也可以以相同的方式实现。

于 2009-11-13T03:34:06.130 回答
3

我的印象是,在嵌入式系统上,我确切地知道有多少内存可用,并且我被允许使用 100% 的内存;没有必要为其他(同时运行的)程序留一点,但也没有可用的虚拟内存给我 101%。所以对于一个队列,我可以很容易地计算出我有空间(比如)981 条记录;所以我为这些记录创建了一个数组,如果我需要第 982 条记录,我会被吓到,必须找到一种优雅地失败的方法。

于 2009-11-12T22:54:03.090 回答
0

我会说内存不足和malloc失败的问题。后者更成问题,因为您没有操作系统/接口来拯救系统免受此类故障的影响。使用一个功能可以使您可能无头运行的整个系统陷入急停(或者可能导致重置,仍然很糟糕),这是非常危险的。

于 2009-11-12T22:49:21.647 回答
0

嵌入式系统上的动态数据结构有点像 C++ 中的指针。指针(在 C++ 中)是邪恶的。但有时它们是唯一的选择;有时它们是较小的邪恶;有时完全避免它们是可以的。在有充分理由使用它们的情况下可以有“好”方法和“坏”方法来做到这一点。

与动态分配的数据相比,静态分配的变量和数组的分配和解除分配速度更快,访问速度也更快。看到这个答案

动态分配的(我的意思是malloc()ed 或类似的)数据也需要空间开销来跟踪分配。每个分配至少几个字节 - 这个空间在嵌入式系统上可能非常有价值!

内存泄漏是嵌入式系统上的一个问题,有时预计会运行数年。从这个角度来看,避免动态分配是谨慎的。

嵌入式设备通常具有相当可靠的规格。你知道传输速率是多少,你知道处理信息的速度,等等。在您的示例中,解决方案是使用固定大小的缓冲区作为循环队列。使缓冲区足够大以处理您的设备需要能够处理的内容(也许更多一点)。如果到达的数据过多,则可能是由于其他地方的故障或干扰,因此保持和尝试使用所有数据没有多大意义。

于 2009-11-12T23:02:26.380 回答
0

我不知道 Atmel MEGA169,但我认为与 169 相关的 MEGA168 只有1024 字节的 SRAM。它也只有 16k 的程序 ROM,与现代计算机相比相对较慢。因此它在内存、程序大小和速度方面受到限制。

根据我在 AVR 汇编器编程方面的经验,将尽可能多的功能塞进 PIC 需要付出努力。使用动态数据结构所需的开销(额外的内存使用,从 SRAM 拉取和推送数据所需的额外指令,跟踪哪个动态变量驻留在哪里,当变量“中间”被删除时移动内存块。 ..) 只是不能证明优点。

因此,即使编译器实现了它,我也会坚持使用静态数据结构来提高性能。

于 2009-11-12T23:05:48.667 回答
0

我不认为在嵌入式系统中使用动态分配是一个坏主意。我只想说,有些地方你需要避免。

  1. 小心你的HEAP尺寸。如果超出MAX_HEAP链接器文件中描述的限制,可能会发生硬件异常。
  2. 在系统启动后分配区域可能会导致您的时间相关任务延迟。所以,你最好在初始化阶段分配你想要的区域。这不是规则,只是一个建议:)
  3. 再想一想,如果您可以在没有动态分配的情况下解决您面临的问题。

另外,我认为动态分配是编程语言的优越特性之一。因此,如果没有它,我想不出设计一个结构良好的应用程序 :)

于 2021-03-06T13:45:09.443 回答