web-dev-qa-db-ja.com

組み込みシステムでのmallocの動作

現在、組み込みプロジェクト(STM32F103RB、CooCox CoIDE v.1.7.6 with arm-none-eabi-gcc 4.8 2013q4)に取り組んでおり、malloc()がプレーンC RAMがいっぱいのとき。

STM32には20kB = 0x5000バイトの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_(スタック)= _0x45C4_(ヒープ)

したがって、malloc()を18回実行するとき。 1つのアイテムは1024 = _0x400_バイトサイズです。

しかし、代わりに、uCは18回目以降にHardFault_Handler(void)を呼び出します(MemManager_Handler(void)でさえも)

malloc()の失敗を予測する方法についてのアドバイスはありますか?NULLの戻りを待つことはうまくいかないようです。

ありがとうございました。

28
Boern

mallocがチェックを行っているようには見えません。発生する障害は、ハードウェアが無効なアドレスへの書き込みを検出したために発生します。これは、おそらくmalloc自体から発生しています。

mallocがメモリを割り当てると、内部プールからチャンクを取得し、それをユーザーに返します。ただし、割り当て解除を完了するには、free関数の情報を保存する必要があります。通常、それはチャンクの実際の長さです。その情報を保存するために、mallocはチャンク自体の先頭から数バイトを取り、そこに情報を書き込み、独自の情報を書き込んだ場所を過ぎたアドレスを返します。

たとえば、10バイトのチャンクを要求したとします。 mallocは、たとえば0x3200..0x320Fのアドレスで利用可能な16バイトのチャンクを取得し、長さ(つまり16)をバイト1と2に書き込み、0x3202を返します。これで、プログラムは0x3202から0x320Bまでの10バイトを使用できます。他の4バイトも使用可能です-reallocを呼び出して14バイトを要求した場合、再割り当ては行われません。

重要な点は、mallocがメモリチャンクに返そうとしている長さを書き込むときです:書き込むアドレスは有効である必要があります。 18回目の反復の後、次のチャンクのアドレスは負である(非常に大きな正に変換される)ため、CPUは書き込みをトラップし、ハードフォールトをトリガーします。

ヒープとスタックが相互に大きくなる状況では、メモリの最後のバイトをすべて使用しながら、メモリ不足を検出する信頼できる方法はありません。これは非常に望ましいことです。 mallocは、割り当て後に使用するスタックの量を予測できないため、試してさえいません。それが、ほとんどの場合、バイトカウントがあなたにある理由です。

一般的に、スペースが数十キロバイトに制限されている組み込みハードウェアでは、「任意の」場所でのmalloc呼び出しを避けます。代わりに、事前に計算された制限を使用してすべてのメモリを事前に割り当て、それを必要とする構造に分割し、mallocを再度呼び出さないでください。

21
dasblinkenlight

ほとんどの場合、プログラムは不正なメモリアクセスのためにクラッシュします。これは、ほとんどの場合legal memory accessの間接的な(後続の)結果ですが、意図しないものです実行します。

例(これは、システムで何が起きているかについての私の推測でもあります):

ほとんどの場合、ヒープはスタックの直後から始まります。ここで、mainにスタックオーバーフローがあるとします。次に、mainで実行する操作の1つは、当然のことながら正当な操作ですが、ヒープの先頭をいくつかの「ジャンク」データでオーバーライドします。

その結果、次にヒープからメモリを割り当てようとすると、次に使用可能なメモリチャンクへのポインタが無効になり、最終的にメモリアクセス違反が発生します。

そのため、まず、スタックサイズを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」セクションを参照して、問題を理解するか、またはさらに支援が必要な場合は、ここに出力してください。

PDATE:

上記のすべてに加えて、ヒープのベースアドレスが正しく定義されていることを確認してください。プロジェクト設定内でハードコーディングされている可能性があります(通常、データセクションとスタックの直後)。ただし、実行時にプログラムの初期化段階で決定することもできます。一般に、データセクションのベースアドレスとプログラムのスタック(プロジェクトのビルド後に作成されたマップファイル内)をチェックし、ヒープがそれらのいずれとも重複しないことを確認する必要があります。

かつて、ヒープのベースアドレスが一定のアドレスに設定されていたことがありました。しかし、その後、プログラムにグローバル変数を追加することで、データセクションのサイズを徐々に増やしました。スタックはデータセクションの直後にあり、データセクションが大きくなるにつれて「前方に」移動したため、どちらにも問題はありませんでした。しかし、最終的に、ヒープはスタックの「上部」に割り当てられました。そのため、ある時点で、ヒープ操作がスタック上の変数をオーバーライドし始め、スタック操作がヒープの内容をオーバーライドし始めました。

4
barak manos

_arm-none-eabi-*_ toolchainディストリビューションには、newlib Cライブラリが含まれています。組み込みシステム用にnewlibが設定されている場合、ユーザープログラムは_sbrk()関数を提供正しく動作するように。

malloc()は、_sbrk()のみに依存して、ヒープメモリの開始位置と終了位置を特定します。 _sbrk()への最初の呼び出しはヒープの開始を返し、後続の呼び出しは必要に応じて_-1_を返しますメモリ量が利用できない場合、malloc()NULLをアプリケーションに返します。 _sbrk()は壊れているように見えます。これは、使用可能なメモリよりも多くのメモリを割り当てることができるようだからです。 _-1_を返すように修正できるはずですbeforeヒープがスタックと衝突することが予想されます。

3
berendi

標準の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アドレスを定義し、頻繁に割り当てる必要があるブロックサイズにブロック番号を手動で選択しました。

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

一般に、最初にメモリを事前に計算してから、iのように与えます。

3
Jayesh Bhoi

ここでは、ヒープが小さすぎてberendiの以前の答えに基づいて割り当てることができない場合に、malloc()がNULLを返すように「強制」する方法を見つけることができます。スタックの最大量を推定し、これに基づいて、最悪の場合にスタックが開始できるアドレスを計算できました。

#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
}
0
K. Simon