简体   繁体   English

嵌入式系统上的malloc行为

[英]malloc behaviour on an embedded system

I'm currently working on an embedded project (STM32F103RB, CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4) and I'm trying to understand how malloc() behaves on plain C when the RAM is full. 我目前正在开发一个嵌入式项目(STM32F103RB,CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4),我正在尝试理解当RAM满时malloc()在纯C上的行为。

My STM32 has 20kB = 0x5000Bytes of RAM, 0x200 are used for the stack. 我的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;
}

I would expect malloc() to return NULL as soon as the heap is too small for allocating: 我希望malloc()在堆太小而无法分配时返回NULL

0x5000 (RAM) - 0x83C (bss) - 0x200 (stack) = 0x45C4 (heap) 0x5000 (RAM) - 0x83C (bss) - 0x200 (堆栈)= 0x45C4 (堆)

So when executing the malloc() for the 18th time. 所以当第18次执行malloc()时。 One item is 1024= 0x400 Bytes large. 一项是1024 = 0x400字节大。

But instead the uC calls the HardFault_Handler(void) after the 18th time (not even the MemManager_Handler(void) ) 但是相反,uC在第18次之后调用HardFault_Handler(void) (甚至不是HardFault_Handler(void) MemManager_Handler(void)

Does anybody have an advice how to forecast a malloc() failure - since waiting for a NULL return doesn't seem to work. 有没有人建议如何预测malloc()失败 - 因为等待NULL返回似乎不起作用。

Thank you. 谢谢。

It does not look like malloc is doing any checks at all. 它看起来并不像malloc正在进行任何检查。 The fault that you get comes from hardware detecting a write to an invalid address, which is probably coming from malloc itself. 你得到的错误来自硬件检测到写入无效地址,这可能来自malloc本身。

When malloc allocates memory, it takes a chunk from its internal pool, and returns it to you. malloc分配内存时,它从内部池中获取一个块,并将其返回给您。 However, it needs to store some information for the free function to be able to complete deallocation. 但是,它需要为free函数存储一些信息才能完成释放。 Usually, that's the actual length of the chunk. 通常,这是块的实际长度。 In order to save that information, malloc takes a few bytes from the beginning of the chunk itself, writes the info there, and returns you the address past the spot where it has written its own information. 为了保存该信息, malloc从块本身的开头获取几个字节,在那里写入信息,并返回地址,超过它写入自己信息的地点。

For example, let's say you asked for a 10-byte chunk. 例如,假设您要求一个10字节的块。 malloc would grab an available 16-byte chunk, say, at addresses 0x3200..0x320F , write the length (ie 16) into bytes 1 and 2, and return 0x3202 back to you. malloc将获取一个可用的16字节块,比如地址0x3200..0x320F ,将长度(即16)写入字节1和2,然后将0x3202返回给您。 Now your program can use ten bytes from 0x3202 to 0x320B . 现在你的程序可以使用从0x32020x320B十个字节。 The other four bytes are available, too - if you call realloc and ask for 14 bytes, there would be no reallocation. 其他四个字节也可用 - 如果您调用realloc并请求14个字节,则不会重新分配。

The crucial point comes when malloc writes the length into the chunk of memory that it is about to return to you: the address to which it writes needs to be valid. malloc将长度写入它将要返回给你的内存块时,关键点就出现了:它写入的地址需要有效。 It appears that after the 18-th iteration the address of the next chunk is negative (which translates to a very large positive) so CPU traps the write, and triggers the hard fault. 似乎在第18次迭代之后,下一个块的地址是负的(转换为非常大的正值),因此CPU捕获写入,并触发硬故障。

In situations when the heap and the stack grow toward each other there is no reliable way to detect an out of memory while letting you use every last byte of memory, which is often a very desirable thing. 在堆和堆栈相互增长的情况下,没有可靠的方法来检测内存不足,同时让你使用内存的每个最后一个字节,这通常是一件非常理想的事情。 malloc cannot predict how much stack you are going to use after the allocation, so it does not even try. malloc无法预测分配后你将要使用多少堆栈,因此它甚至都没有尝试。 That is why the byte counting in most cases is on you. 这就是为什么在大多数情况下字节计数在你身上。

In general, on embedded hardware when the space is limited to a few dozen kilobytes, you avoid malloc calls in "arbitrary" places. 通常,在嵌入式硬件上,当空间限制为几十千字节时,可以避免在“任意”位置调用malloc Instead, you pre-allocate all your memory upfront using some pre-calculated limits, and parcel it out to structures that need it, and never call malloc again. 相反,您使用一些预先计算的限制预先分配所有内存,并将其分配给需要它的结构,而不再调用malloc

Your program most likely crashes because of an illegal memory access , which is almost always an indirect (subsequent) result of a legal memory access , but one that you did not intend to perform. 你的程序很可能崩溃,因为一个非法的内存访问 ,这是几乎总是一个合法的内存访问的间接的(后续)的结果,而是一个你不打算执行。

For example (which is also my guess as to what's happening on your system): 例如(这也是我对你系统上发生的事情的猜测):

Your heap most likely begins right after the stack. 你的堆最有可能在堆栈之后开始。 Now, suppose you have a stack-overflow in main . 现在,假设你在main有一个堆栈溢出。 Then one of the operations that you perform in main , which is naturally a legal operation as far as you're concerned, overrides the beginning of the heap with some "junk" data. 然后,您在main执行的其中一个操作(就您而言自然是合法操作)将使用一些“垃圾”数据覆盖堆的开头。

As a subsequent result, the next time that you attempt to allocate memory from the heap, the pointer to the next available chunk of memory is no longer valid, eventually leading to a memory access violation. 作为后续结果,下次尝试从堆中分配内存时,指向下一个可用内存块的指针不再有效,最终导致内存访问冲突。

So to begin with, I strongly recommend that you increase the stack size from 0x200 bytes to 0x400 bytes. 首先,我强烈建议您将堆栈大小从0x200字节增加到0x400字节。 This is typically defined within the linker-command file, or through the IDE, in the project's linker settings. 这通常在链接器命令文件中定义,或通过IDE在项目的链接器设置中定义。

If your project is on IAR, then you can change it in the icf file: 如果您的项目是在IAR上,那么您可以在icf文件中更改它:

define symbol __ICFEDIT_size_cstack__ = 0x400

Other than that, I suggest that you add code in your HardFault_Handler , in order to reconstruct the call-stack and register values prior to the crash. 除此之外,我建议您在HardFault_Handler添加代码,以便在崩溃之前重建调用堆栈并注册值。 This might allow you to trace the runtime error and find out exactly where it happened. 这可能允许您跟踪运行时错误并找出确切的位置。

In file 'startup_stm32f03xx.s', make sure that you have the following piece of code: 在文件'startup_stm32f03xx.s'中,确保您有以下代码:

EXTERN  HardFault_Handler_C        ; this declaration is probably missing

__tx_vectors                       ; this declaration is probably there
    DCD     HardFault_Handler

Then, in the same file, add the following interrupt handler (where all other handlers are located): 然后,在同一个文件中,添加以下中断处理程序(所有其他处理程序所在的位置):

    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

Then, in file 'stm32f03xx.c', add the following ISR: 然后,在文件'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);
}

If you can't use printf at the point in the execution when this specific Hard-Fault interrupt occurs, then save all the above data in a global buffer instead, so you can view it after reaching the while (1) . 如果在执行此特定硬故障中断时无法在执行时使用printf ,则将所有上述数据保存在全局缓冲区中,以便在到达while (1)后查看它。

Then, refer to the 'Cortex-M Fault Exceptions and Registers' section at http://www.keil.com/appnotes/files/apnt209.pdf in order to understand the problem, or publish the output here if you want further assistance. 然后,请参阅http://www.keil.com/appnotes/files/apnt209.pdf上的“Cortex-M故障异常和寄存器”部分以了解问题,或者如果您需要进一步的帮助,请在此处发布输出。

UPDATE: 更新:

In addition to all of the above, make sure that the base address of the heap is defined correctly. 除上述所有内容外,请确保正确定义堆的基址。 It is possibly hard-coded within the project settings (typically right after the data-section and the stack). 它可能在项目设置中进行硬编码(通常在数据部分和堆栈之后)。 But it can also be determined during runtime, at the initialization phase of your program. 但它也可以在运行时,程序的初始化阶段确定。 In general, you need to check the base addresses of the data-section and the stack of your program (in the map file created after building the project), and make sure that the heap does not overlap either one of them. 通常,您需要检查数据部分的基地址和程序堆栈(在构建项目后创建的映射文件中),并确保堆中的任何一个都不重叠。

I once had a case where the base address of the heap was set to a constant address, which was fine to begin with. 我曾经有过这样一种情况,即堆的基地址被设置为一个常量地址,这一点很好。 But then I gradually increased the size of the data-section, by adding global variables to the program. 但随后我通过向程序添加全局变量逐渐增加了数据部分的大小。 The stack was located right after the data-section, and it "moved forward" as the data-section grew larger, so there were no problems with either one of them. 堆栈位于数据部分的正后方,随着数据部分变大,它“向前移动”,因此它们中的任何一个都没有问题。 But eventually, the heap was allocated "on top of" part of the stack. 但最终,堆被“分配”在堆栈的“顶部”。 So at some point, heap-operations began to override variables on the stack, and stack-operations began to override the contents of the heap. 所以在某些时候,堆操作开始覆盖堆栈上的变量,堆栈操作开始覆盖堆的内容。

Using standard c malloc it's very hard to distinguish and malloc is seems buggy from my view. 使用标准的c malloc很难区分,而且我认为malloc看起来很麻烦。 So you can manage memory by implementing some custom malloc using your RAM address. 因此,您可以使用RAM地址实现一些自定义malloc来管理内存。

I am not sure may this help you but i have done some custom malloc in my controller related project it's as follows 我不确定这可能对你有所帮助,但我在我的控制器相关项目中做了一些自定义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;

This basically macro defines for RAM address and have manually chose more block number for block size which frequently require to allocate,Like 36 bytes required me more so i take more number for it. 这个基本上宏定义了RAM地址,并且手动为块大小手动选择了更多的块号,这通常需要分配,像36个字节需要我更多,所以我需要更多的数字。

This is init function for mem init 这是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; i<MEM_TBL_MAX; i++) 
    {
        head = (char *)addr;
        memInfoTbl[i].wm_head = head;
        for(j=0;j<memInfoTbl[i].num_max; j++)
        {
            head->used =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;
}

This one for allocation 这一个用于分配

void* CUS_Malloc( int wantedSize )
{
    void *pwtReturn = NULL;
    int i;
    cus_mem_block_s *head;

    if(MemHeapHasBeenInitialised == FALSE) 
            goto done_exit;

    for(i=0; i<MEM_TBL_MAX; i++)
    {
        if(wantedSize <= memInfoTbl[i].block_size)
        {
            head = memInfoTbl[i].wm_head;
            while(head->ptr)
            {
                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; i<MEM_TBL_MAX; i++)
        {
            if(memInfoTbl[i].block_size == head->block_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;
}

This one for free 这个是免费的

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;i<MEM_TBL_MAX;i++)
    {
        if(memInfoTbl[i].block_size == head->block_size)
        {
            memInfoTbl[i].calc[CALC_CNT]--;
            goto done;
        }
    }
 done:;

}

After all you can use above function like 毕竟你可以使用上面的功能

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

Then can also watch your used memory as follows 然后还可以按如下方式观察您使用的内存

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<MEM_TBL_MAX; i++) {
        printf("block %d used=%d/%d (max %d)\n",
                    memInfoTbl[i].block_size, block_cnt[i], 
                    memInfoTbl[i].num_max,
                    memInfoTbl[i].calc[CALC_MAX]);
    }

    printf("used memory=%d\n",usedSize);
    printf("free memory=%d\n",totalSize-usedSize);
    printf("total memory=%d\n",totalSize);
    printf("--------------------------\n");
}

In general have pre-calculated the memory first then give as i have. 通常先预先计算内存然后按照我的要求给出。

The arm-none-eabi-* toolchain distribution includes the newlib C library . arm-none-eabi-* 工具链分发包括newlib C库 When newlib is configured for an embedded system, then the user program must provide an _sbrk() function for it to work properly. 当为嵌入式系统配置newlib时,用户程序必须提供_sbrk()函数才能使其正常工作。

malloc() relies solely on _sbrk() to figure out where the heap memory starts, and where it ends. malloc()完全依赖于_sbrk()来确定堆内存的起始位置和结束位置。 The very first call to _sbrk() returns the start of the heap, and subsequent calls should return -1 if the required amount of memory is not available, then malloc() would in turn return NULL to the application. 第一次调用_sbrk()返回堆的开头,如果所需的内存量不可用,后续调用应返回-1 ,然后malloc()将返回NULL给应用程序。 Your _sbrk() looks broken, because it apparently lets you allocate more memory than there is available. 你的_sbrk()看起来很_sbrk() ,因为它显然可以让你分配比可用内存更多的内存。 You should be able to fix it so that it returns -1 before the heap is expected to collide with the stack. 您应该能够修复它,以便在堆预期与堆栈冲突之前返回-1

Here you can find how I could "force" malloc() to return NULL, if the heap is too small for allocating based on berendi's previous answer. 在这里你可以找到我如何“强制”malloc()返回NULL,如果堆太小,无法根据berendi的先前答案进行分配。 I estimated the maximum amount of STACK and based on this I could calculate the address where the stack can start in worst case. 我估计了最大STACK数量,基于此我可以计算堆栈在最坏情况下可以启动的地址。

#define STACK_END_ADDRESS       0x20020000
#define STACK_MAX_SIZE              0x0400
#define STACK_START_ADDRESS   (STACK_END_ADDRESS - STACK_MAX_SIZE)

void * _sbrk_r(
   struct _reent *_s_r,
   ptrdiff_t nbytes)
{
   char  *base;     /*  errno should be set to  ENOMEM on error */

   if (!heap_ptr) { /*  Initialize if first time through.       */
      heap_ptr = end;
   }
   base = heap_ptr; /*  Point to end of heap.           */
   #ifndef STACK_START_ADDRESS
      heap_ptr += nbytes;   /*  Increase heap.              */
      return base;      /*  Return pointer to start of new heap area.   */
   #else
      /* End of heap mustn't exceed beginning of stack! */        
      if (heap_ptr <= (char *) (STACK_START_ADDRESS - nbytes) ) {  
         heap_ptr += nbytes;    /*  Increase heap.              */
         return base;       /*  Return pointer to start of new heap area.   */
      } else {
         return (void *) -1;         /*   Return -1 means that memory run out  */
      }
   #endif // STACK_START_ADDRESS
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM