首页 文章

嵌入式系统上的malloc行为

提问于
浏览
26

我试图了解当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;
}

一旦堆太小而无法分配,我希望 malloc() 返回 NULL

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

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

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

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

谢谢 .

3 回答

  • 4

    它看起来不像 malloc 正在进行任何检查 . 您得到的故障来自硬件检测到写入无效地址,这可能来自 malloc 本身 .

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

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

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

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

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

  • 3

    您的程序很可能因为 illegal memory access 而崩溃,这几乎总是 legal memory access 的间接(后续)结果,但您不打算执行 .

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

    你的堆最有可能在堆栈之后开始 . 现在,假设您在 main 中有堆栈溢出 . 然后,您在 main 中执行的其中一个操作(就您而言自然是合法操作)将使用一些"junk"数据覆盖堆的开头 .

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

    首先,我强烈建议您将堆栈大小从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 Fault Exceptions and Registers'部分以了解问题,或者如果您需要进一步的帮助,请在此处发布输出 .

    UPDATE:

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

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

  • 20

    使用标准 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; 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;
    }
    

    这一个用于分配

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

    这个是免费的

    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:;
    
    }
    

    毕竟你可以使用上面的功能

    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<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");
    }
    

    通常先预先计算内存然后按照我的要求给出 .

相关问题