malloc-Verhalten auf einem eingebetteten System

Lesezeit: 13 Minuten

Benutzer-Avatar
Börn

Ich arbeite derzeit an einem eingebetteten Projekt (STM32F103RB, CooCox CoIDE v.1.7.6 mit arm-none-eabi-gcc 4.8 2013q4) und versuche zu verstehen, wie malloc() verhält sich auf Ebene C wenn der RAM voll ist.

Mein STM32 hat 20kB = 0x5000Bytes RAM, 0x200 werden für den Stack verwendet.

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

Ich würde erwarten malloc() zurückgeben NULL sobald der Heap zu klein zum Zuordnen ist:

0x5000 (RAM) – 0x83C (bss) – 0x200 (Stapel) = 0x45C4 (Haufen)

Also beim Ausführen der malloc() zum 18. mal. Ein Element ist 1024=0x400 Bytes groß.

Aber stattdessen ruft die uC die an HardFault_Handler(void) nach dem 18. Mal (nicht einmal die MemManager_Handler(void))

Hat jemand einen Rat, wie man a prognostiziert malloc() Fehler – seit dem Warten auf a NULL Rücksendung scheint nicht zu funktionieren.

Vielen Dank.

  • Ich habe keine Antworten, aber danke für eine interessante und gut artikulierte Frage. (+1)

    – NPE

    15. März 2014 um 10:52 Uhr

  • Ich habe auch keine Antworten, aber dies würde auf die der C-Bibliothek hindeuten malloc() Funktion hat einen Fehler.

    – Licht

    15. März 2014 um 10:56 Uhr

  • Benutzt du uClibc?

    – Michael Foukarakis

    15. März 2014 um 11:03 Uhr

  • Gibt es eine Möglichkeit, die Größe des verbleibenden Speichers abzurufen?

    – Déjà-vu

    15. März 2014 um 11:04 Uhr

  • @BernhardSchlegel uClibc ist eine bestimmte Implementierung der Standard-C-Bibliothek, Sie könnten Ihre C-Bibliothek mit Debug-Symbolen kompilieren und dann einen Debugger verwenden, um einzugreifen malloc und sehen Sie genau, welche Zeile den Aufruf des Hardfault-Handlers verursacht. Sie können GCC mit verschiedenen Implementierungen der C-Bibliothek verwenden, sodass die Aussage, dass Sie GCC verwenden, nicht wirklich sagt, welche Implementierung der C-Bibliothek Sie verwenden. Wir können nur davon ausgehen, dass Sie die Standardeinstellung verwenden.

    – Etienne

    15. März 2014 um 16:38 Uhr


Es sieht nicht danach aus malloc macht überhaupt keine Kontrollen. Der Fehler, den Sie erhalten, kommt von der Hardware, die einen Schreibvorgang an eine ungültige Adresse erkennt, von der wahrscheinlich ausgegangen wird malloc selbst.

Wann malloc weist Speicher zu, nimmt einen Teil aus seinem internen Pool und gibt ihn an Sie zurück. Es müssen jedoch einige Informationen für die gespeichert werden free Funktion, um die Aufhebung der Zuordnung abzuschließen. Normalerweise ist das die tatsächliche Länge des Chunks. Um diese Informationen zu speichern, malloc nimmt ein paar Bytes vom Anfang des Chunks selbst, schreibt die Informationen dort und gibt Ihnen die Adresse hinter der Stelle zurück, an der es seine eigenen Informationen geschrieben hat.

Angenommen, Sie haben nach einem 10-Byte-Chunk gefragt. malloc würde einen verfügbaren 16-Byte-Chunk, sagen wir, bei Adressen abrufen 0x3200..0x320Fschreiben Sie die Länge (dh 16) in die Bytes 1 und 2 und kehren Sie zurück 0x3202 zurück zu dir. Jetzt kann Ihr Programm zehn Bytes verwenden 0x3202 zu 0x320B. Die anderen vier Bytes sind auch verfügbar – wenn Sie anrufen realloc und nach 14 Bytes fragen, würde es keine Neuzuweisung geben.

Der entscheidende Punkt kommt wann malloc schreibt die Länge in den Speicherabschnitt, den er Ihnen zurückgeben wird: Die Adresse, an die er schreibt, muss gültig sein. Es scheint, dass nach der 18. Iteration die Adresse des nächsten Blocks negativ ist (was zu einem sehr großen positiven Ergebnis führt), sodass die CPU den Schreibvorgang abfängt und den harten Fehler auslöst.

In Situationen, in denen der Heap und der Stack aufeinander zuwachsen, gibt es keine zuverlässige Möglichkeit, einen Speichermangel zu erkennen und gleichzeitig das letzte Byte des Speichers zu nutzen, was oft sehr wünschenswert ist. malloc kann nicht vorhersagen, wie viel Stack Sie nach der Zuweisung verwenden werden, also versucht es es nicht einmal. Deshalb liegt die Bytezählung in den meisten Fällen bei Ihnen.

Im Allgemeinen vermeiden Sie es auf eingebetteter Hardware, wenn der Speicherplatz auf einige Dutzend Kilobyte begrenzt ist malloc Anrufe an “beliebigen” Orten. Stattdessen weisen Sie Ihren gesamten Speicher im Voraus zu, indem Sie einige vorberechnete Grenzen verwenden, und verteilen Sie ihn auf Strukturen, die ihn benötigen, und rufen Sie ihn nie auf malloc wieder.

  • Die letzte erfolgreiche Zuordnung gibt 0x20004908 zurück – was meiner Meinung nach schon nicht möglich sein sollte. Der Grund, warum ich Strukturen verwende, ist, dass ich Strukturen von einer SD-Karte mit variabler Größe (100 Byte bis 2 kByte) gelesen habe.

    – Börn

    15. März 2014 um 12:14 Uhr


Benutzer-Avatar
barak manos

Ihr Programm stürzt höchstwahrscheinlich wegen eines ab illegaler Speicherzugriffwas fast immer ein indirektes (späteres) Ergebnis von a ist legaler Speicherzugriffaber eine, die Sie nicht ausführen wollten.

Zum Beispiel (was auch meine Vermutung ist, was auf Ihrem System passiert):

Ihr Heap beginnt höchstwahrscheinlich direkt nach dem Stack. Angenommen, Sie haben einen Stapelüberlauf main. Dann eine der Operationen, die Sie in ausführen mainwas für Sie natürlich eine legale Operation ist, überschreibt den Anfang des Heaps mit einigen “Junk” -Daten.

Wenn Sie das nächste Mal versuchen, Speicher aus dem Heap zuzuweisen, ist der Zeiger auf den nächsten verfügbaren Speicherabschnitt daher nicht mehr gültig, was schließlich zu einer Speicherzugriffsverletzung führt.

Daher empfehle ich dringend, zunächst die Stack-Größe von 0x200 Bytes auf 0x400 Bytes zu erhöhen. Dies wird normalerweise in der Linker-Befehlsdatei oder über die IDE in den Linker-Einstellungen des Projekts definiert.

Wenn sich Ihr Projekt auf IAR befindet, können Sie es in ändern icf Datei:

define symbol __ICFEDIT_size_cstack__ = 0x400

Abgesehen davon schlage ich vor, dass Sie Code in Ihre hinzufügen HardFault_Handler, um den Call-Stack zu rekonstruieren und Werte vor dem Absturz zu registrieren. Auf diese Weise können Sie möglicherweise den Laufzeitfehler nachverfolgen und genau herausfinden, wo er aufgetreten ist.

Stellen Sie in der Datei „startup_stm32f03xx.s“ sicher, dass Sie den folgenden Code haben:

EXTERN  HardFault_Handler_C        ; this declaration is probably missing

__tx_vectors                       ; this declaration is probably there
    DCD     HardFault_Handler

Fügen Sie dann in derselben Datei den folgenden Interrupt-Handler hinzu (wo sich alle anderen Handler befinden):

    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

Fügen Sie dann in der Datei „stm32f03xx.c“ die folgende ISR hinzu:

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

Wenn Sie nicht verwenden können printf An dem Punkt in der Ausführung, an dem dieser spezielle Hard-Fault-Interrupt auftritt, speichern Sie stattdessen alle oben genannten Daten in einem globalen Puffer, damit Sie sie nach Erreichen von anzeigen können while (1).

Lesen Sie dann den Abschnitt „Cortex-M-Fehlerausnahmen und -register“ unter http://www.keil.com/appnotes/files/apnt209.pdf um das Problem zu verstehen, oder veröffentlichen Sie die Ausgabe hier, wenn Sie weitere Hilfe benötigen.

AKTUALISIEREN:

Stellen Sie außerdem sicher, dass die Basisadresse des Heapspeichers richtig definiert ist. Es ist möglicherweise in den Projekteinstellungen fest codiert (normalerweise direkt nach dem Datenabschnitt und dem Stapel). Sie kann aber auch zur Laufzeit, in der Initialisierungsphase Ihres Programms, ermittelt werden. Im Allgemeinen müssen Sie die Basisadressen des Datenabschnitts und des Stacks Ihres Programms (in der nach dem Erstellen des Projekts erstellten Map-Datei) überprüfen und sicherstellen, dass der Heap keinen von beiden überlappt.

Ich hatte einmal einen Fall, in dem die Basisadresse des Heaps auf eine konstante Adresse gesetzt war, was zunächst in Ordnung war. Aber dann habe ich die Größe des Datenbereichs schrittweise erhöht, indem ich dem Programm globale Variablen hinzugefügt habe. Der Stapel befand sich direkt nach dem Datenabschnitt und “bewegte sich vorwärts”, als der Datenabschnitt größer wurde, sodass es mit keinem von ihnen Probleme gab. Aber schließlich wurde der Haufen “oben auf” einem Teil des Stapels zugewiesen. Irgendwann begannen also Heap-Operationen, Variablen auf dem Stack zu überschreiben, und Stack-Operationen begannen, den Inhalt des Heaps zu überschreiben.

  • Der Ausdruck, nach dem Sie suchen, lautet “Stapel-Heap-Kollision”. Sehr seltene Bedingung auf einem modernen Full-Service-Betriebssystem, aber sie waren früher ein Problem auf vielen Plattformen und sind immer noch ein Problem in eingeschränkteren Umgebungen.

    – dmckee — Ex-Moderator-Kätzchen

    15. März 2014 um 17:44 Uhr

  • @dmckee: Danke für die Terminologie. Ich habe dieses Problem bei der Verwendung von ThreadX OS erlebt, das Ihnen die first unused memory Adresse in einer Callback-Funktion (dh während der Laufzeit) und ermöglicht Ihnen, den Heap an dieser Adresse zuzuweisen. Das Problem trat auf, weil ich stattdessen eine konstante Adresse verwendete, vorausgesetzt, dass sie “gut genug” war.

    – barak manos

    15. März 2014 um 17:50 Uhr


Das arm-none-eabi-* Werkzeugkette Vertrieb umfasst die neuelib C-Bibliothek. Wenn newlib für ein eingebettetes System konfiguriert wird, muss das Benutzerprogramm bieten ein _sbrk() Funktion damit es richtig funktioniert.

malloc() stützt sich ausschließlich auf _sbrk() um herauszufinden, wo der Heap-Speicher beginnt und wo er endet. Der allererste Anruf bei _sbrk() gibt den Anfang des Heaps und nachfolgende Aufrufe zurück sollte zurückkehren -1 wenn die erforderliche Speichermenge nicht verfügbar ist, dann malloc() würden wiederum zurückkehren NULL zur Bewerbung. Dein _sbrk() sieht kaputt aus, weil Sie anscheinend mehr Speicher zuweisen können, als verfügbar ist. Sie sollten in der Lage sein, es zu beheben, damit es zurückkehrt -1 Vor Es wird erwartet, dass der Haufen mit dem Stapel kollidiert.

Benutzer-Avatar
Jayesh Bhoi

Standard verwenden c malloc es ist sehr schwer zu unterscheiden und malloc Das scheint aus meiner Sicht fehlerhaft zu sein. So können Sie den Speicher verwalten, indem Sie einige benutzerdefinierte implementieren malloc Verwenden Sie Ihre RAM-Adresse.

Ich bin mir nicht sicher, ob Ihnen das helfen kann, aber ich habe einige Gewohnheiten gemacht malloc In meinem Controller-bezogenen Projekt ist es wie folgt

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

Dieses Makro definiert im Grunde die RAM-Adresse und hat manuell mehr Blocknummern für die Blockgröße ausgewählt, die häufig zugewiesen werden müssen. 36 Bytes erforderten mehr, also nehme ich mehr Nummern dafür.

Dies ist die Init-Funktion für Mem 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;
}

Dieser zur Zuteilung

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

Dieses hier kostenlos

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

}

Schließlich können Sie die obige Funktion wie verwenden

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

Dann können Sie auch Ihren verbrauchten Speicher wie folgt beobachten

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

Im Allgemeinen habe ich den Speicher zuerst vorberechnet und dann gegeben, wie ich ihn habe.

Hier finden Sie, wie ich malloc() “zwingen” könnte, NULL zurückzugeben, wenn der Heap zu klein für die Zuweisung ist, basierend auf Berendis vorheriger Antwort. Ich habe die maximale Menge an STACK geschätzt und daraus die Adresse berechnet, an der der Stack im schlimmsten Fall beginnen kann.

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

1376460cookie-checkmalloc-Verhalten auf einem eingebetteten System

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy