【问题标题】:How can I visualise the memory (SRAM) usage of an AVR program?如何可视化 AVR 程序的内存 (SRAM) 使用情况?
【发布时间】:2010-10-31 21:59:52
【问题描述】:

我在 AVR 微控制器 (ATMega328P) 上运行的 C 程序中遇到了问题。我相信这是由于堆栈/堆冲突,但我希望能够确认这一点。

有什么方法可以可视化堆栈和堆的 SRAM 使用情况?

注意:程序用avr-gcc编译,使用avr-libc。

更新: 我遇到的实际问题是 malloc 实现失败(返回 NULL)。所有mallocing 都发生在启动时,所有freeing 都发生在应用程序结束时(实际上从来没有,因为应用程序的主要部分处于无限循环中)。所以我确信碎片化不是问题。

【问题讨论】:

  • 哇。您一定是唯一一个曾经在 atmega 上使用过 malloc 的人。我很惊讶他们的工作!甚至从未被包括在内。
  • 这里介绍了一些有用的 AVR 特定 malloc 内容:nongnu.org/avr-libc/user-manual/malloc.html

标签: debugging memory embedded avr avr-gcc


【解决方案1】:

您可以使用avr-size 实用程序检查 RAM 静态使用情况,如
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=62968,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82536,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=95638,
中所述 和http://letsmakerobots.com/node/27115

avr-size -C -x Filename.elf

(avr 大小的文档:http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html

以下示例说明如何在 IDE 上进行设置: 在 Code::Blocks, Project -> Build options -> Pre/post build steps -> Post-build steps, 包括:

avr-size -C $(TARGET_OUTPUT_FILE)
avr-size -C --mcu=atmega328p $(TARGET_OUTPUT_FILE)

构建结束时的示例输出:

AVR Memory Usage
----------------
Device: atmega16

Program:    7376 bytes (45.0% Full)
(.text + .data + .bootloader)

Data:         81 bytes (7.9% Full)
(.data + .bss + .noinit)

EEPROM:       63 bytes (12.3% Full)
(.eeprom) 

数据是你的SRAM使用量,它只是编译器的数量 在编译时知道。您还需要空间来容纳在 运行时(尤其是堆栈使用)。

要检查堆栈使用情况(动态 RAM), 来自http://jeelabs.org/2011/05/22/atmega-memory-use/

这是一个小实用函数,用于确定 RAM 的大小 目前未使用:

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

这是使用该代码的草图:

void setup () {
    Serial.begin(57600);
    Serial.println("\n[memCheck]");
    Serial.println(freeRam());
}

freeRam() 函数返回在堆的末尾和堆栈上最后分配的内存之间存在多少字节,因此它实际上是堆栈/堆在它们碰撞之前可以增长多少。

您可以围绕您怀疑可能导致堆栈/堆冲突的代码检查此函数的返回。

【讨论】:

  • +1 - 感谢您的链接!请注意,此处定义的 freeRam() 函数在释放的内存被保存在 malloc“空闲列表”中的情况下会失败。如果内存经常被动态分配和解除分配,这在实践中可能是一个问题——这可能是 AVR 上罕见的情况(例如,在我的情况下,我只在程序初始化期间动态分配内存,所以 freeRam() 代码有效对我来说)。
  • 你对freeRam函数有什么提示吗?如果我用 O3 编译它就不能正常工作了。我在 itoa 之后显示值。 (调试工作正常 O1)
  • @BennX 我只在 O0 和 O1 使用过,效果很好。但是您可能知道在 O3 进行调试更难,所以我建议在您的程序不是坚如磐石的情况下更改为较低级别的优化——尽管如果您使用小型微控制器,您可能需要注释其中的某些部分.
【解决方案2】:

你说 malloc 失败并返回 NULL:

您应该首先查看的明显原因是您的堆“已满” - 即您要求 malloc 的内存无法分配,因为它不可用。

要记住两种情况:

a:你有一个 16K 的堆,你已经 malloc 了 10K,你尝试再 malloc 10K。你的堆太小了。

b:更常见的是,你有一个 16k 的堆,你一直在做一堆 malloc/free/realloc 调用,你的堆不到 50% '满':你调用 malloc 来获取 1K 并且它失败 -这是怎么回事?答案 - 堆可用空间是碎片化的 - 没有可以返回的连续 1K 可用内存。发生这种情况时,C 堆管理器无法压缩堆,因此您通常情况不佳。有一些技术可以避免碎片化,但很难知道这是否真的是问题所在。您需要向 malloc 和 free 添加日志填充程序,以便了解正在执行的动态内存操作。

编辑:

你说所有 malloc 都发生在启动时,所以碎片不是问题。

在这种情况下,用静态替换动态分配应该很容易。

旧代码示例:

char *buffer;

void init()
{
  buffer = malloc(BUFFSIZE);
}

新代码:

char buffer[BUFFSIZE];

一旦你在所有地方都这样做了,你的 LINKER 应该会在所有内容都无法放入可用内存时发出警告。不要忘记减小堆大小 - 但要注意一些运行时 io 系统函数可能仍然使用堆,因此您可能无法完全删除它。

【讨论】:

  • +1 这很可能是 a)。所有 mallocing 都发生在启动时,所有 freeing 都发生在应用程序的末尾(实际上从来没有,因为应用程序的主要部分处于无限循环中)。所以我确信碎片化不是问题。
  • 这是很重要的一点!请不要再在 cmets 中添加这些小提示,而是在问题本身中添加尽可能多的细节!
  • @Artelius - 已将此添加到问题中。谢谢。
【解决方案3】:

通常的方法是用已知模式填充内存,然后检查哪些区域被覆盖。

【讨论】:

  • 这是一件很老套的事情。我个人建议使用 avr-size 命令 + freeRam() 方法,而不是采取如此激烈的措施......
  • @MatthiasHryniszak 我理解你为什么认为这是“hacky”,但这实际上是一种非常有用的技术。它不是万无一失的,但有可能捕获freeRam() 方法无法捕获的高堆栈使用率(尤其是在涉及中断时)。
【解决方案4】:

不要在嵌入式目标上使用堆/动态分配。特别是对于资源如此有限的处理器。而是重新设计您的应用程序,因为随着程序的增长,问题会再次出现。

【讨论】:

    【解决方案5】:

    如果您同时使用堆栈和堆,则可能会有点棘手。我将解释当没有使用堆时我做了什么。作为一般规则,我工作过的所有公司(在嵌入式 C 软件领域)都避免将堆用于小型嵌入式项目——以避免堆内存可用性的不确定性。我们改用静态声明的变量。

    一种方法是在启动时用已知模式(例如 0x55)填充大部分堆栈区域。这通常由软件执行早期的一小段代码完成,或者在 main() 开始时,或者甚至在 main() 开始之前,在启动代码中。当然,请注意不要覆盖当时正在使用的少量堆栈。然后,在运行软件一段时间后,检查堆栈空间的内容,看看 0x55 在哪里仍然完好无损。您如何“检查”取决于您的目标硬件。假设您连接了调试器,那么您可以简单地停止微运行并读取内存。

    如果您有一个可以执行内存访问断点的调试器(比通常的执行断点更花哨),那么您可以在特定堆栈位置设置断点,例如堆栈空间的最远限制。这可能非常有用,因为它还可以准确地向您显示在达到堆栈使用程度时正在运行的代码位。但它要求您的调试器支持内存访问断点功能,并且通常在“低端”调试器中找不到。

    如果您还使用堆,那么它可能会稍微复杂一些,因为可能无法预测堆栈和堆会在哪里发生冲突。

    【讨论】:

      【解决方案6】:

      假设您只使用一个堆栈(所以不是 RTOS 或任何东西)并且堆栈位于内存的末尾,正在向下增长,而堆在 BSS/DATA 区域之后开始,正在增长。我已经看到了 malloc 的实现,它实际上检查了堆栈指针并在发生冲突时失败。你可以尝试这样做。

      如果您无法调整 malloc 代码,您可以选择将堆栈放在内存的开头(使用链接器文件)。一般来说,知道/定义堆栈的最大大小总是一个好主意。如果将其放在开头,则在超出 RAM 开头的范围内读取时会出现错误。堆将在最后,如果它是一个体面的实现(将返回 NULL 代替),它可能不会增长到最后。好消息是您知道 2 个单独的问题有 2 个单独的错误案例。

      要找出最大堆栈大小,您可以用模式填充内存,运行应用程序并查看它走了多远,另请参阅 Craig 的回复。

      【讨论】:

      • malloc 实现失败(返回 NULL)。我遇到的问题是我不确定是不是碰撞导致它......
      • 冲突通常会导致非常奇怪的事情,例如返回错误的函数或数据更改或突然在 RAM/FLASH 之外执行并出现寻址错误。如果您的应用程序看起来“正常”,则可能不是冲突。要对此进行调试,请在返回 NULL 的位置设置断点(或者,如果您可以调试 malloc 函数本身,则在此处设置断点会更好)。此时,检查堆栈指针并查看是否出现了冲突。另外,您的堆是否定义为“未使用的任何内存”或者您可以设置堆大小?
      • 你能把东西打印到例如串行线?如果是这样,您仍然可以在发生时将信息打印到串行线路。为了使它更容易,您可以围绕 malloc 函数编写一个包装器,并从您的代码而不是 malloc 调用它。在包装器中,检查 NULL 返回值并打印堆栈指针。如果您无法访问打印功能,那么您现在如何调试?
      • @Ron - 我可以打印到串行线路。你会建议我如何打印堆栈指针?
      • 我以前使用的一个丑陋的技巧是创建一个局部变量数组。例如,在“printStack”函数的范围内创建“char buffer[8]”。这个缓冲区是在堆栈上分配的。然后使用 for 循环打印此缓冲区,而不是仅读取 8 个字节,您可以读取 256 个字节或您想要的数量。读取超出“缓冲区”的边界将导致读取堆栈。要知道堆栈指针的地址,请打印缓冲区的地址。这不是 100% 正确,但它会给你一个想法。我知道,它很丑,但它有效。
      【解决方案7】:

      如果您可以编辑堆的代码,则可以在每个内存块上用几个额外的字节填充它(在如此低的资源上很棘手)。这些字节可能包含与堆栈不同的已知模式。这可能会给您一个线索,如果它与堆栈发生冲突,则可以看到它出现在堆栈中,反之亦然。

      【讨论】:

      • 可以在free函数中检查模式,但仍然很难找出错误发生的时间。另请注意,有时会为可能不使用的局部变量保留额外的堆栈空间(取决于编译器/代码)。在这种情况下,模式可以保持不变,而堆仍然损坏。
      【解决方案8】:

      在类 Unix 操作系统上,一个名为 sbrk() 的库函数,其参数为 0,允许您访问动态分配的堆内存的最高地址。返回值是一个 void * 指针,可以与任意堆栈分配变量的地址进行比较。

      应谨慎使用此比较的结果。根据 CPU 和系统架构,堆栈可能会从任意高地址向下增长,而分配的堆将从低限内存向上移动。

      有时操作系统有其他内存管理概念(即 OS/9),将堆和堆栈放置在空闲内存的不同内存段中。在这些操作系统上——尤其是对于嵌入式系统——你需要定义你的最大内存需求 提前申请,以使系统能够分配匹配大小的内存段。

      【讨论】:

      • ATMega328P 不运行 Linux/BSD(它只有 2K 的 RAM,我根本没有在其上运行操​​作系统),是的,堆栈变小了,堆变大了。
      • AVR 行没有内存保护块的概念,因此来自“大”CPU 的安全解决方案并不总是可行的。例如在 x86 行中,第 286 行有部分,而 386 完全实现了受保护的内存区域. 386 年给了新一代操作系统的“复兴”,包括完整的 Windows(非 DOS)、Linux、Minix 等......可能在 286 上受保护最多的是 OS/2,完全安全的操作系统(内核)在 AVR 上是不可能的
      猜你喜欢
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多