我在AVR微控制器(ATMega328P)上运行的C程序中遇到了问题。我相信这是由于堆栈/堆冲突,但我希望能够证实这一点。
有没有办法可以看到堆栈和堆的SRAM使用情况?
注意:该程序是使用avr-gcc编译的,并使用avr-libc。
更新:我遇到的实际问题是malloc实现失败(返回NULL
)。所有malloc
都在启动时发生,并且所有free
都在应用程序结束时发生(实际上这是因为应用程序的主要部分处于无限循环中)。所以我确定碎片不是问题。
答案 0 :(得分:21)
您可以使用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-size documentation:http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html)
按照如何在IDE上设置它的示例: 在Code :: Blocks,Project - >构建选项 - >前/后构建步骤 - >构建后的步骤包括:
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 :(得分:11)
你说malloc失败并返回NULL:
首先要注意的一个明显原因是你的堆已经“满” - 即你要求malloc的内存无法分配,因为它不可用。
有两种情况需要牢记:
a:你有一个16 K的堆,你已经使用了10 K的malloc,你尝试另外10K的malloc。你的堆太小了。
b:更常见的是,你有一个16 k的堆,你一直在做一堆malloc / free / realloc调用你的堆不到50%'满':你调用malloc为1K并且它失败 - 这是怎么回事?答案 - 堆空闲空间是碎片化的 - 没有可以返回的1K空闲内存。 C堆管理器在发生这种情况时无法压缩堆,因此通常情况不好。有一些技术可以避免碎片,但很难知道这是否真的是问题所在。您需要向malloc和free添加日志记录填充程序,以便您可以了解正在执行的动态内存操作。
编辑:
你说所有的malloc都是在启动时发生的,所以碎片不是问题所在。
在这种情况下,应该很容易用static替换动态分配。
旧代码示例:
char *buffer;
void init()
{
buffer = malloc(BUFFSIZE);
}
新代码:
char buffer[BUFFSIZE];
一旦你到处都这样做了,你的LINKER应该警告你,如果一切都不能适应可用的内存。不要忘记减小堆大小 - 但要注意一些运行时io系统函数可能仍然使用堆,因此您可能无法完全删除它。
答案 2 :(得分:3)
通常的方法是用已知模式填充内存,然后检查哪些区域被覆盖。
答案 3 :(得分:2)
如果你同时使用堆栈和堆,那么它可能会有点棘手。我将解释当没有堆使用时我做了什么。作为一般规则,我所工作的所有公司(在嵌入式C软件领域)都避免将堆用于小型嵌入式项目 - 以避免堆内存可用性的不确定性。我们使用静态声明的变量。
一种方法是在启动时用已知模式(例如0x55)填充大部分堆栈区域。这通常由软件执行早期的一小段代码完成,或者在main()的开头,或者甚至在main()开始之前,在启动代码中。当然,注意不要覆盖正在使用的少量堆栈。然后,在运行软件一段时间后,检查堆栈空间的内容并查看0x55仍然完好无损的位置。 “检查”的方式取决于您的目标硬件。假设您已连接调试器,则可以直接停止微运行并读取内存。
如果你有一个可以执行内存访问断点的调试器(比通常的执行断点更加花哨),那么你可以在特定的堆栈位置设置断点 - 例如堆栈空间的最大限制。这可能非常有用,因为它还可以显示在达到堆栈使用范围时正在运行的代码位。但它需要您的调试器支持内存访问断点功能,而且通常在“低端”调试器中找不到它。
如果您还在使用堆,那么它可能会更复杂一些,因为可能无法预测堆栈和堆将发生冲突的位置。
答案 4 :(得分:2)
不要在嵌入式目标上使用堆/动态分配。特别是对于具有如此有限资源的处理器。而是重新设计您的应用程序,因为随着程序的增长,问题将再次发生。
答案 5 :(得分:1)
假设您只使用一个堆栈(因此不是RTOS或其他任何东西),并且堆栈位于内存的末尾,逐渐减少,而堆在BSS / DATA区域之后开始生长。我已经看到malloc的实现实际上检查了堆栈指针并且在碰撞时失败了。你可以尝试这样做。
如果您无法调整malloc代码,您可以选择将堆栈放在内存的开头(使用链接器文件)。一般来说,了解/定义堆栈的最大大小总是一个好主意。如果你把它放在开头,你会在读取超出RAM开头时出错。如果它是一个不错的实现(将返回NULL),Heap将在最后并且可能不会超出结束。好的是你知道有2个单独的错误案例可以解决2个不同的问题。
要查找最大堆栈大小,您可以使用模式填充内存,运行应用程序并查看其进度,请参阅Craig的回复。
答案 6 :(得分:0)
如果你可以编辑堆的代码,你可以在每个内存块上用几个额外的字节(在这么低的资源上很棘手)填充它。这些字节可以包含与堆栈不同的已知模式。如果它看到它出现在堆栈中,反之亦然,这可能会给你一个线索,如果它与堆栈发生冲突。
答案 7 :(得分:0)
在Unix操作系统上,名为sbrk()且参数为0的库函数允许您访问动态分配的堆内存的最顶层地址。返回值是一个void *指针,可以与任意堆栈分配变量的地址进行比较。
应谨慎使用此比较结果。根据CPU和系统架构,堆栈可能会从任意高地址开始增长,而分配的堆将从低端内存向上移动。
有时,操作系统还有其他内存管理概念(即OS / 9),它将堆和堆栈放在空闲内存中的不同内存段中。在这些操作系统上 - 尤其是嵌入式系统 - 您需要定义您的最大内存要求 提前应用程序使系统能够分配匹配大小的内存段。