Boe*_*ern 28 c malloc memory-management out-of-memory stm32
我目前正在开发一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4),我试图了解当RAM满时如何malloc()表现C.
我的STM32有20kB = 0x5000Bytes的RAM,0x200用于堆栈.
#include <stdlib.h>
#include "stm32f10x.h"
struct list_el {
char weight[1024];
};
typedef struct list_el item;
int main(void)
{
item * curr;
// allocate until RAM is full
do {
curr = (item *)malloc(sizeof(item));
} while (curr != NULL);
// I know, free() is missing. Program is supposed to crash
return 0;
}
Run Code Online (Sandbox Code Playgroud)
一旦堆太小而无法分配,我希望malloc()能够返回NULL:
0x5000(RAM) - 0x83C(bss) - 0x200(堆栈)= 0x45C4(堆)
所以当执行malloc()第18次时.一项是1024 = 0x400字节大.
但相反,uC HardFault_Handler(void)在第18次之后调用(甚至不是MemManager_Handler(void))
有没有人建议如何预测malloc()失败 - 因为等待NULL回报似乎不起作用.
谢谢.
das*_*ght 21
看起来malloc根本不做任何检查.您得到的错误来自硬件检测到写入无效地址,这可能来自malloc自身.
当malloc分配内存,它从它的内部池块,并返回给你.但是,它需要为free函数存储一些信息才能完成释放.通常,这是块的实际长度.为了保存该信息,malloc从块本身的开头取几个字节,在那里写入信息,然后将地址返回到已写入自己信息的地点.
例如,假设您要求一个10字节的块.malloc会抓住一个可用的16字节块,比如地址0x3200..0x320F,将长度(即16)写入字节1和2,然后返回0x3202给你.现在你的程序可以使用十个字节0x3202来0x320B.其他四个字节也可用 - 如果您调用realloc并要求14个字节,则不会重新分配.
当malloc将长度写入将要返回给您的内存块时,关键点就出现了:它写入的地址需要有效.似乎在第18次迭代之后,下一个块的地址是负的(转换为非常大的正值),因此CPU捕获写入,并触发硬故障.
在堆和堆栈相互增长的情况下,没有可靠的方法来检测内存不足,同时让你使用内存的每个最后一个字节,这通常是一件非常理想的事情.malloc无法预测分配后您将使用多少堆栈,因此它甚至都没有尝试.这就是为什么在大多数情况下字节计数在你身上.
通常,在嵌入式硬件上,当空间限制为几十千字节时,可以避免malloc在"任意"位置进行调用.相反,您使用一些预先计算的限制预先分配所有内存,并将其分配给需要它的结构,而不再调用malloc.
你的程序很可能崩溃,因为一个非法的内存访问,这是几乎总是一个间接的(后续)结果合法内存访问,而是一个你不打算执行。
例如(这也是我对您系统上发生的事情的猜测):
您的堆很可能在堆栈之后立即开始。现在,假设您在main. 然后,您在 中执行的main操作之一(就您而言这自然是合法操作)用一些“垃圾”数据覆盖堆的开头。
结果是,下次尝试从堆分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。
因此,首先,我强烈建议您将堆栈大小从 0x200 字节增加到 0x400 字节。这通常在链接器命令文件中定义,或通过 IDE 在项目的链接器设置中定义。
如果您的项目在 IAR 上,那么您可以在icf文件中更改它:
define symbol __ICFEDIT_size_cstack__ = 0x400
Run Code Online (Sandbox Code Playgroud)
除此之外,我建议你在你的 中添加代码HardFault_Handler,以便在崩溃之前重建调用堆栈和寄存器值。这可能允许您跟踪运行时错误并找出它发生的确切位置。
在文件“startup_stm32f03xx.s”中,确保您有以下代码:
EXTERN HardFault_Handler_C ; this declaration is probably missing
__tx_vectors ; this declaration is probably there
DCD HardFault_Handler
Run Code Online (Sandbox Code Playgroud)
然后,在同一个文件中,添加以下中断处理程序(所有其他处理程序所在的位置):
PUBWEAK HardFault_Handler
SECTION .text:CODE:REORDER(1)
HardFault_Handler
TST LR, #4
ITE EQ
MRSEQ R0, MSP
MRSNE R0, PSP
B HardFault_Handler_C
Run Code Online (Sandbox Code Playgroud)
然后,在文件“stm32f03xx.c”中,添加以下 ISR:
void HardFault_Handler_C(unsigned int* hardfault_args)
{
printf("R0 = 0x%.8X\r\n",hardfault_args[0]);
printf("R1 = 0x%.8X\r\n",hardfault_args[1]);
printf("R2 = 0x%.8X\r\n",hardfault_args[2]);
printf("R3 = 0x%.8X\r\n",hardfault_args[3]);
printf("R12 = 0x%.8X\r\n",hardfault_args[4]);
printf("LR = 0x%.8X\r\n",hardfault_args[5]);
printf("PC = 0x%.8X\r\n",hardfault_args[6]);
printf("PSR = 0x%.8X\r\n",hardfault_args[7]);
printf("BFAR = 0x%.8X\r\n",*(unsigned int*)0xE000ED38);
printf("CFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED28);
printf("HFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED2C);
printf("DFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED30);
printf("AFSR = 0x%.8X\r\n",*(unsigned int*)0xE000ED3C);
printf("SHCSR = 0x%.8X\r\n",SCB->SHCSR);
while (1);
}
Run Code Online (Sandbox Code Playgroud)
如果printf在这个特定的 Hard-Fault 中断发生的执行点不能使用,那么把上面的所有数据保存在一个全局缓冲区中,这样你就可以在到达while (1).
然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf上的“Cortex-M 故障异常和寄存器”部分以了解问题,如果需要进一步帮助,请在此处发布输出.
更新:
除了上述所有内容之外,请确保正确定义了堆的基地址。它可能在项目设置中进行了硬编码(通常在数据部分和堆栈之后)。但它也可以在运行时确定,在程序的初始化阶段。通常,您需要检查数据部分的基地址和程序堆栈(在构建项目后创建的映射文件中),并确保堆不重叠其中任何一个。
我曾经遇到过将堆的基地址设置为常量地址的情况,这很好。但是后来我通过向程序添加全局变量来逐渐增加数据部分的大小。堆栈位于数据部分之后,随着数据部分变大,它“向前移动”,因此它们中的任何一个都没有问题。但最终,堆被分配到堆栈的“顶部”部分。所以在某个时刻,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。
工具arm-none-eabi-* 链发行版包括newlib C 库。当newlib为嵌入式系统配置时,用户程序必须提供一个_sbrk()函数才能使其正常工作。
malloc()仅依赖于_sbrk()找出堆内存的开始位置和结束位置。第一次调用_sbrk()返回堆的开头,如果所需的内存量不可用,后续调用应返回-1malloc(),然后依次返回NULL到应用程序。你_sbrk()看起来很糟糕,因为它显然可以让你分配比可用内存更多的内存。您应该能够修复它,以便它在堆与堆栈发生冲突-1 之前返回。