23

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

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

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

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

4

8 回答 8

24

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

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

于 2011-08-25T13:04:04.513 回答
13

You say malloc is failing and returning NULL:

The obvious cause which you should look at first is that your heap is "full" - i.e, the memory you've asked to malloc cannot be allocated, because it's not available.

There are two scenarios to bear in mind:

a: You have a 16 K heap, you've already malloced 10 K and you try and malloc a further 10K. Your heap is simply too small.

b: More commonly, you have a 16 k Heap, you've been doing a bunch of malloc/free/realloc calls and your heap is less than 50% 'full': You call malloc for 1K and it FAILS - what's up? Answer - the heap free space is fragmented - there isn't a contigous 1K of free memory that can be returned. C Heap managers can not compact the heap when this happens, so you're generally in a bad way. There are techniques to avoid fragmentation, but it's difficult to know if this is really the problem. You'd need to add logging shims to malloc and free so that you can get an idea of what dynamic memory operations are being performed.

EDIT:

You say all mallocs happen at startup, so fragmentation isn't the issue.

In which case, it should be easy to replace the dynamic allocation with static.

old code example:

char *buffer;

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

new code:

char buffer[BUFFSIZE];

Once you've done this everywhere, your LINKER should warn you if everything cannot fit into the memory available. Don't forget to reduce the heap size - but beware that some runtime io system functions may still use the heap, so you may not be able to remove it entirely.

于 2009-06-22T12:21:46.167 回答
3

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

于 2009-06-11T09:37:44.080 回答
3

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

于 2009-06-06T20:22:06.957 回答
2

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

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

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

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

于 2009-06-07T08:25:27.597 回答
1

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

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

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

于 2009-06-10T23:43:29.650 回答
0

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

于 2009-06-09T04:33:33.383 回答
0

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

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

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

于 2009-06-22T12:07:06.167 回答