嵌入式系统上的malloc行为

我目前正在开发一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4),我正在尝试理解当RAM满时malloc()在纯C上的行为。

我的STM32有20kB = 0x5000Bytes的RAM,0x200用于堆栈。

 #include  #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; } 

我希望malloc()在堆太小而无法分配时返回NULL

0x5000 (RAM) – 0x83C (bss) – 0x200 (堆栈)= 0x45C4 (堆)

所以当第18次执行malloc()时。 一项是1024 = 0x400字节大。

但是相反,uC在第18次之后调用HardFault_Handler(void) (甚至不是HardFault_Handler(void) MemManager_Handler(void)

有没有人建议如何预测malloc()失败 – 因为等待NULL返回似乎不起作用。

谢谢。

它看起来并不像malloc正在进行任何检查。 你得到的错误来自硬件检测到写入无效地址,这可能来自malloc本身。

malloc分配内存时,它会从内部池中获取一个块,并将其返回给您。 但是,它需要为free函数存储一些信息才能完成释放。 通常,这是块的实际长度。 为了保存该信息, malloc从块本身的开头获取几个字节,在那里写入信息,并返回通过其编写自己信息的地点的地址。

例如,假设您要求一个10字节的块。 malloc将获取一个可用的16字节块,比如地址0x3200..0x320F ,将长度(即16)写入字节1和2,然后将0x3202返回给您。 现在你的程序可以使用从0x32020x320B十个字节。 其他四个字节也可用 – 如果您调用realloc并请求14个字节,则不会重新分配。

malloc将长度写入它将要返回给你的内存块时,关键点就出现了:它写入的地址需要有效。 看起来在第18次迭代之后,下一个块的地址是负的(转换为非常大的正值),因此CPU捕获写入,并触发硬故障。

在堆和堆栈相互增长的情况下,没有可靠的方法来检测内存不足,同时让你使用内存的每个最后一个字节,这通常是一个非常理想的事情。 malloc无法预测分配后您将使用多少堆栈,因此它甚至都没有尝试。 这就是为什么在大多数情况下字节计数在你身上。

通常,在嵌入式硬件上,当空间限制为几十千字节时,可以避免在“任意”位置调用malloc 。 相反,您使用一些预先计算的限制预先分配所有内存,并将其分配给需要它的结构,并且永远不再调用malloc

您的程序很可能因非法内存访问而崩溃,这几乎总是合法内存访问的间接(后续)结果,但您不打算执行此操作。

例如(这也是我对你系统上发生的事情的猜测):

你的堆最有可能在堆栈之后开始。 现在,假设你在main有一个堆栈溢出。 然后你在main执行的一个操作,就你而言自然是一个合法的操作,用一些“垃圾”数据覆盖堆的开头。

作为后续结果,下次尝试从堆中分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。

首先,我强烈建议您将堆栈大小从0x200字节增加到0x400字节。 这通常在链接器命令文件中定义,或通过IDE在项目的链接器设置中定义。

如果您的项目是IAR,那么您可以在icf文件中更改它:

 define symbol __ICFEDIT_size_cstack__ = 0x400 

除此之外,我建议您在HardFault_Handler添加代码,以便在崩溃之前重建调用堆栈并注册值。 这可能允许您跟踪运行时错误并找出确切的位置。

在文件’startup_stm32f03xx.s’中,确保您有以下代码:

 EXTERN HardFault_Handler_C ; this declaration is probably missing __tx_vectors ; this declaration is probably there DCD HardFault_Handler 

然后,在同一文件中,添加以下中断处理程序(所有其他处理程序所在的位置):

  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 

然后,在文件’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); } 

如果在执行此特定硬故障中断时无法在执行时使用printf ,则将所有上述数据保存在全局缓冲区中,以便在到达while (1)后查看它。

然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf上的“Cortex-M故障exception和寄存器”部分以了解问题,或者如果您需要进一步的帮助,请在此处发布输出。

更新:

除上述所有内容外,请确保正确定义堆的基址。 它可能在项目设置中进行硬编码(通常在数据部分和堆栈之后)。 但它也可以在运行时,程序的初始化阶段确定。 通常,您需要检查数据部分的基地址和程序堆栈(在构建项目后创建的映射文件中),并确保堆中的任何一个都不重叠。

我曾经有一个案例,其中堆的基地址被设置为一个常量地址,这可以开始。 但后来我通过向程序添加全局变量逐渐增加了数据部分的大小。 堆栈位于数据部分的正后方,随着数据部分变大,它“向前移动”,因此其中任何一个都没有问题。 但最终,堆被“分配”在堆栈的“顶部”。 所以在某些时候,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。

使用标准的c malloc很难区分,而且我认为malloc看起来很麻烦。 因此,您可以使用RAM地址实现一些自定义malloc来管理内存。

我不确定这可能对你有所帮助,但我在我的控制器相关项目中做了一些自定义malloc ,如下所示

 #define LENGTH_36_NUM (44) #define LENGTH_52_NUM (26) #define LENGTH_64_NUM (4) #define LENGTH_128_NUM (5) #define LENGTH_132_NUM (8) #define LENGTH_256_NUM (8) #define LENGTH_512_NUM (18) #define LENGTH_640_NUM (8) #define LENGTH_1536_NUM (6) #define CUS_MEM_USED (1) #define CUS_MEM_NO_USED (0) #define CALC_CNT (0) #define CALC_MAX (1) #define __Ram_Loc__ (0x20000000) ///This is my RAM address #define __TOP_Ram_Loc__ (0x20000000 + 0x8000 -0x10) //Total 32K RAM and last 16 bytes reserved for some data storage typedef struct _CUS_MEM_BLOCK_S { char used; int block_size; char *ptr; char *next; } cus_mem_block_s; static struct _MEM_INFO_TBL_S { int block_size; int num_max; cus_mem_block_s *wm_head; int calc[2]; } memInfoTbl[] = { {36, LENGTH_36_NUM , 0, {0,0} }, {52, LENGTH_52_NUM , 0, {0,0} }, {64, LENGTH_64_NUM , 0, {0,0} }, {128, LENGTH_128_NUM , 0, {0,0} }, {132, LENGTH_132_NUM , 0, {0,0} }, {256, LENGTH_256_NUM , 0, {0,0} }, {512, LENGTH_512_NUM , 0, {0,0} }, {640, LENGTH_640_NUM , 0, {0,0} }, {1536,LENGTH_1536_NUM, 0, {0,0} }, }; #define MEM_TBL_MAX (sizeof(memInfoTbl)/sizeof(struct _MEM_INFO_TBL_S)) BOOL MemHeapHasBeenInitialised = FALSE; 

这个基本上宏定义了RAM地址,并且手动为块大小手动选择了更多的块号,这经常需要分配,像36个字节需要我更多,所以我需要更多的数字。

这是mem init的init函数

 void cus_MemInit(void) { int i,j; cus_mem_block_s *head=NULL; unsigned int addr; addr = __Ram_Loc__; for(i=0; iused =CUS_MEM_NO_USED; head->block_size = memInfoTbl[i].block_size; head->ptr = (char *)(addr + sizeof(cus_mem_block_s)); addr += (memInfoTbl[i].block_size + sizeof(cus_mem_block_s)); head->next =(char *)addr; head = head->next; if(head > __TOP_Ram_Loc__) { printf("%s:error.\n",__FUNCTION__); return; } } } head->ptr = 0; head->block_size = 0; head->next = __Ram_Loc__; MemHeapHasBeenInitialised=TRUE; } 

这一个用于分配

 void* CUS_Malloc( int wantedSize ) { void *pwtReturn = NULL; int i; cus_mem_block_s *head; if(MemHeapHasBeenInitialised == FALSE) goto done_exit; for(i=0; iptr) { if(head->used == CUS_MEM_NO_USED) { head->used = CUS_MEM_USED; pwtReturn = head->ptr; goto done; } head = head->next; } goto done; } } done: if(pwtReturn) { for(i=0; iblock_size) { memInfoTbl[i].calc[CALC_CNT]++; if(memInfoTbl[i].calc[CALC_CNT] > memInfoTbl[i].calc[CALC_MAX] ) memInfoTbl[i].calc[CALC_MAX]=memInfoTbl[i].calc[CALC_CNT]; break; } } } done_exit: return pwtReturn; } 

这个是免费的

 void CUS_Free(void *pm) { cus_mem_block_s *head; char fault=0; if( (pm == NULL) || (MemHeapHasBeenInitialised == FALSE) ) goto done; if( (pm < __RamAHB32__) && (pm > __TOP_Ram_Loc__) ) { printf("%s:over memory range\n",__FUNCTION__); goto done; } head = pm-sizeof(cus_mem_block_s); if(head->used) head->used = CUS_MEM_NO_USED; else { printf("%s:free error\n",__FUNCTION__); fault=1; } if(fault) goto done; int i; for(i=0;iblock_size) { memInfoTbl[i].calc[CALC_CNT]--; goto done; } } done:; } 

毕竟你可以使用上面的function

 void *mem=NULL; mem=CUS_Malloc(wantedsize); 

然后还可以按如下方式观察您使用的内存

 void CUS_MemShow(void) { int i; int block_size; int block_cnt[MEM_TBL_MAX]; int usedSize=0, totalSize=0; cus_mem_block_s *head; if(MemHeapHasBeenInitialised == FALSE) return; memset(block_cnt, 0, sizeof(block_cnt)); head = memInfoTbl[0].wm_head; i=0; block_size = head->block_size; vTaskSuspendAll(); while( head->ptr !=0) { if(head->used == CUS_MEM_USED ) { block_cnt[i]++; usedSize +=head->block_size; } usedSize += sizeof(cus_mem_block_s); totalSize += (head->block_size+ sizeof(cus_mem_block_s)); /* change next memory block */ head = head->next; if( block_size != head->block_size) { block_size = head->block_size; i++; } } xTaskResumeAll(); usedSize += sizeof(cus_mem_block_s); totalSize+= sizeof(cus_mem_block_s); dprintf("----Memory Information----\n"); for(i=0; i 

通常先预先计算内存然后按照我的方式给出。