在嵌入式系统上使用动态数据结构有多糟糕?

时间:2009-11-12 22:40:09

标签: list stack queue avr

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

现在我正在开发一种中等规模的嵌入式系统LURC控制器,大多数只是利用了AVR169MEGA的“Butterfly”演示板的外围设备。 产生4个PWM信号来控制伺服和ESC。并提供9 seg液晶屏。

现在我想不出有什么更好的方式来存储指令,因为它们是收到的样品USART序列,而不是队列。 特别是我需要等待收到未知数​​据量的东西:例如,液晶显示屏上显示的字符串。

那你为什么不在嵌入式系统的微控制器上使用动态数据结构呢? 是否只是因为你受到严重的内存限制环境,并且必须确保你的mallocs成功了?

9 个答案:

答案 0 :(得分:21)

有许多理由不在嵌入式系统中使用malloc(或等效的)。

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

最重要的是 - 如果你不动态分配内存,那么就不会出现内存泄漏。

答案 1 :(得分:5)

嗯,许多小型微控制器没有像MMU那样的东西,也没有像你这样好的堆栈的操作系统。

对于那些那样做的人,只要你对你所要求的记忆量保持理智,我就不会发现它存在很大的问题。

但是,许多嵌入式系统也是实时系统。如果您的应用程序有很长的运行时间,那么您将无法进行动态分配。大多数堆实现都使用没有非常有限运行时的算法。在某些(可能是罕见的)情况下,他们将花费更长的时间来运行比正常情况。有一些实时堆实现,但它们没有广泛使用。一般规则是在初始化后避免在硬实时系统中进行任何动态分配或解除分配。

答案 2 :(得分:4)

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

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

在过去几年中,我们看到的是基本上单片机或微型电路板,它们与我们的旧式奔腾电脑一样强大。 RAM价格现在非常便宜,而且内存限制与它们完全不同。他们还经常运行嵌入式linux或wince,所以现在我们可以更自由地使用动态内存。

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

我怀疑所有这些新的可用硬件都出现了一系列新问题。

答案 3 :(得分:4)

嵌入式环境本身的动态内存没有任何问题,但通常它在嵌入式环境中并不会给您带来太大的影响。

在我看来,使用ring-buffers是一个非常好的主意(这是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);

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

答案 4 :(得分:3)

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

答案 5 :(得分:0)

我会说缺乏内存和malloc失败的问题。后者更是一个问题,因为你没有操作系统/接口来拯救系统免于这样的故障。使用一个可以使你的整个系统无法正常运行的功能非常危险(或者可能导致重置,但仍然很糟糕)。

答案 6 :(得分:0)

嵌入式系统上的动态数据结构有点像C ++中的指针。指针(用C ++表示)是邪恶的。但有时它们是唯一的选择;有时它们是较小的邪恶;有时完全避免它们是可以的。如果 是使用它们的充分理由,那么可以采用“好”方式和“坏”方法来实现这一目标。

与动态分配的数据相比,静态分配的变量和数组分配和释放更快,访问速度更快。请参阅this answer

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

内存泄漏是嵌入式系统上的大量问题,有时可能会出现这种问题。从这个角度来看,避免动态分配是谨慎的。

嵌入式设备通常具有相当可靠的规格。您知道传输速率是多少,您知道处理信息的速度有多快,等等。在您的示例中,解决方案是使用固定大小的缓冲区作为circular queue。使缓冲区足够大,以处理您的设备需要处理的内容(可能还需要更多)。如果太多数据到达,可能是由于其他地方的故障或干扰,所以没有太多的意义并且试图使用所有数据。

答案 7 :(得分:0)

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

根据我在AVR汇编程序编程方面的经验,需要尽可能多地将尽可能多的功能填充到PIC中。使用动态数据结构所需的开销量(额外的内存使用,从SRAM中提取和推送数据所需的额外指令,跟踪哪个动态变量驻留在哪里,当'之间的变量'被移除时移动内存块... ..)只是没有理由的优点。

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

答案 8 :(得分:0)

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

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

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