Forum: Mikrocontroller und Digitale Elektronik Problem mit Malloc


von Thomas Bergmüller (Gast)


Lesenswert?

Hallo,

ich habe momentan ein Problem mit malloc(), das ich einfach nicht gelöst 
bekomme.

Verwende es in einer Interruptroutine der USART am Atmega 88 bzw. 
Atmega32

Es handelt sich um eine Kommunikationslibrary zwischen PC und uC, die 
Datenrahmen unterschiedlicher Länge (zw. 5 und 262 Bytes) verarbeiten 
muss. Innerhalb dieses Protokolls gibt es einen Dateitransfer, der 
größere Dateien entsprechend auf mehrere Datenpakete aufteilt und im 
Empfänger muss das dann wieder zu einer Datenwurst zusammengesetzt 
werden.

Dabei sind unterschiedliche Protokolle implementiert (Daten, 
Datenbroadcast, Kommando, Kommandobroadcast, Filetransfer usw.)

Bis zu dem Zeitpunkt, an dem ich den Filetransfer implementierte, lief 
das ganze problemlos. Zur Bufferung der Daten (Empfang über USART) wurde 
immer sobald bekannt war wie lange das aktuelle Paket ist (wird 
mitübermittelt) mit malloc ein Speicher allokiert und dieser dann 
befüllt.

Beim Dateitransfer wird ein Ankündigungsframe mit einigen Informationen 
(Dateigröße, Dateiname) übertragen. Der uC antwortet dem PC daraufhin, 
ob er genügend Speicher frei hat um die Datei aufzunehmen usw.

Das ganze wird in einer Funktion abgefragt und die antwort versendet, 
danach wird in die ISR zurückgekehrt.


Nun zum eigentlichen Problem:
Der in der Funktion allokierte Speicher wird teilweise in den 
Speicherbereich von lokalen static-Variablen und sogar globalen 
Variablen gelegt, die entsprechend überschrieben werden.

Ich komm einfach nicht dahinter, warum das geschieht, hat das 
irgendetwas mit der internen Speicherverwaltung von malloc zu tun.

(Achtung Pseudocode)

Funktionierend:
1
ISR(USART)
2
{ 
3
    // Diverse Staticvariablen
4
    // ....
5
    // 
6
    // static unsigned char* workingDataPtr;
7
    // Warten bis bekannt ist wie lang das gerade empfangende Paket is
8
    workingDataPtr = (unsigned char*) malloc(FRAMELENGTH);
9
10
    // Wenn Paket empfangen und Daten stimmen
11
    free(global_validDataPtr); // Alte Daten löschen
12
    global_validDataPtr = workingDataPtr;
13
}

nicht funktionierend

1
function FileTransfer(unsigned char* receivedData)
2
{
3
   // Diverse Staticvariablen
4
   // .....
5
   // static unsigned char* workingFilePtr;
6
 
7
   // Wenn Ankündigungsframe mit Infos
8
    workingFilePtr = (unsigned char*) malloc(FILESIZE);
9
   
10
   // Wenn Dateidaten empfangen
11
   //Schreibe in allkoierten Speicher @ workingFilePtr
12
   for(i=0;i<framelänge; i++)
13
   {
14
      workingFilePtr = receivedData[i];
15
      workingFilePtr++;
16
   }
17
18
   // Wenn letztes Paket empfangen
19
   free(global_validFilePtr); // Altes File löschen
20
   global_validFilePtr = workingFilePtr; (wieder auf erstes Byte rückgesetzt versteht sich)
21
}
22
23
24
ISR(USART)
25
{ 
26
    // Diverse Staticvariablen
27
    // ....
28
    // static unsigned char* workingDataPtr;
29
    //
30
    // Warten bis bekannt ist wie lang das gerade empfangende Paket is
31
    workingDataPtr = (unsigned char*) malloc(FRAMELENGTH);
32
33
    // Wenn Filetransfer und ein korrektes Paket empfangen wurde
34
    // call FileTransfer(workingDataPtr);
35
}


Bei der nicht funktionierenden Variante geht alles was vor 
Implementierung des Filetransfers funktionierte ebenso. Nur sobald ich 
die Filetransferroutine aufrufe, überschreibt der dort allokierte 
Speicher (in workingFilePtr) diverse Staticvariablen in der ISR, der 
Filetransferroutine und auch globale Variablen, fast so, als wüsste die 
Funktion und daraus aufgerufenes malloc nicht, wo bereits Daten liegen.


Bin mit meinem Latein leider am Ende.


Bitte und Danke schonmal im Voraus!
Thomas

von Thomas Bergmüller (Gast)


Angehängte Dateien:

Lesenswert?

Im Anhang noch das Mapfile des gesamten Projekts...

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thomas Bergmüller schrieb:
> ich habe momentan ein Problem mit malloc(), das ich einfach nicht gelöst
> bekomme.
>
> Verwende es in einer Interruptroutine der USART am Atmega 88 bzw.
> Atmega32

Ick.  Ich bin gewiss der letzte, der dir von malloc() abraten würde,
schließlich habe ich die aktuelle avr-libc-Implementierung ja
geschrieben ;), aber von malloc() innerhalb einer ISR würde ich dir nun
wirklich abraten.  Erstens kann das Teil vergleichsweise lange laufen,
und zweitens musst du dir verdammt sicher sein, dass du keine race
condition bekommst: wenn du malloc() (oder free()) außerhalb der ISR
rufst, dann wird ein derartiger Aufruf vom Interrupt unterbrochen und
du rufst es in der ISR nochmal auf, dann ist der Teufel los.

von Thomas Bergmüller (Gast)


Lesenswert?

Ich rufe es "quasi" nur innerhalb der ISR

Also die ISR ruft entweder direkt oder über einen weiteren 
Funktionsaufruf hinweg (der Rücksprung erfolgt aber wieder in die ISR) 
malloc / free auf.

Das ganze passiert nur, wenn ein Datenpaket fertig empfangen und geprüft 
wurde, und so lange wartet der Sender auf ein Acknowledge, das erst 
gesendet wird, wenn ich mit Malloc fertig bin. Erst dann können neue 
Daten kommen.

Einziges was mir auf die Schnelle einfällt was den Ablauf unterbrechen 
könnte, sind Störungen am Bus (RS485).

Bin aber natürlich auch für Alternativen offen, am ehesten wäre dann ein 
fixer Empfangspuffer mit den 262 Bytes und für die übertragene Dateien 
(die ja von einigen Bytes bis eben Datenspeicher halb voll variieren 
können) sinnvoll oder?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Fester Puffer in der ISR.  Wenn du dynamische Datenmengen verwalten
können willst, kann ja der main context dann die empfangenen Daten
anhand ihrer tatsächlichen Länge in einem malloc()-Puffer umkopieren.

Damit hättest du eine kurze und schnelle ISR, die halt genau ein
Datenpaket empfangen kann, danach muss sie warten, bis diese Daten
außerhalb verarbeitet worden sind.

Bitte benutze das malloc() aus der aktuellen avr-libc.  Die Versionen
vor avr-libc-1.7.0 (also beispielsweise die des derzeit letzten WinAVRs)
hatten teilweise noch gravierende Bugs im malloc().

von Peter D. (peda)


Lesenswert?

Thomas Bergmüller schrieb:
> Also die ISR ruft entweder direkt oder über einen weiteren
> Funktionsaufruf hinweg (der Rücksprung erfolgt aber wieder in die ISR)
> malloc / free auf.

Dann macht malloc/free keinen Sinn. Das braucht man nur, wenn 
verschiedene Instanzen es ausführen, d.h. bei free die Instanz von 
malloc bereits beendet ist.
Ansonsten kannst Du einfach gleich ne lokale Variable mit der maximal 
benötigten Größe anlegen.

Ich würde allerdings vermeiden, im Interrupt riesige Monsterprotokolle 
zu parsen und sogar noch Callbacks auszuführen. Das Main und andere 
Interrupts haben dann sehr schlechte Karten, auchmal an die Reihe zu 
kommen.

Ich laß den UART-Interrupt nur alles in nen FIFO schmeißen und 
vielleicht noch das Ende des Frame erkennen. Die Auswertung macht dann 
das Main.
Der Sender kann sich die Größe des FIFO holen und ihn mit Frames füllen. 
Danach hat er auf Antwort zu warten, ehe er weiter sendet.


Peter

von Peter D. (peda)


Lesenswert?

Jörg Wunsch schrieb:
> Die Versionen
> vor avr-libc-1.7.0 (also beispielsweise die des derzeit letzten WinAVRs)
> hatten teilweise noch gravierende Bugs im malloc().

Autsch!

Was macht man dann als WINAVR-Nutzer und nicht des Compilerbaus fähiger?
Gibt es Workarounds für den WINAVR?


Peter

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:

> Was macht man dann als WINAVR-Nutzer und nicht des Compilerbaus fähiger?

Um eine Biblithek zu bauen, muss man nicht des Compilerbaus fähig
sein.  Es muss lediglich ein Compiler vorhanden sein.  Viel mehr
braucht man für die avr-libc nicht (sofern man nicht gerade noch die
Doku selbst bauen will).

Ansonsten kannst du dir allemal malloc.c und stdlib_private.h in
dein eigenes Projekt kopieren.

von Thomas Bergmüller (Gast)


Lesenswert?

Ok danke werd ich machen..

hab die ISR selbst auf fixe Buffer umgebaut, funktioniert prächtig.

unsigned char devComSpace1[256];
unsigned char devComSpace2[256];

In diesen zwei Bereichen (Adresse 0x87 und 0x18e ) werden die Daten 
gespeichert, in einem Space stehen jeweils gültige Daten, der andere 
wird als Buffer benutzt, wenn gültige Daten drinstehen, werden einfach 
Pointer geswapt

Das malloc-Problem besteht aber weiterhin...

Untenstehender Code zeigt die Routine, die empfangene Pakete in ein File 
schreiben soll.

Sie kann entweder mit pReceive (Parameter 3) = 
DEVCOM_FILETRANSFER_ANNOUNCED oder DEVCOM_FILETRANSFER_RECEIVING 
aufgerufen werden, entsprechend gabelt sich dann der Programmweg.

beim Announce wird der Speicher allokiert und die gesamten 
Static-Variablen, die während des Filetransfers benötigt werden (also 
bis alle Pakete da sind)
initialisiert.

Aufgrund des Return-Werts wird wieder an den PC Rückmeldung gegeben.

Danach bekommt der uC die eigentlichen Dateidaten und baut diese 
Zusammen bis zum letzten Frame, da sollte er dann den 
global_validFilePtr auf den allokierten Speicher setzten und vorher den 
alten Speicher (wo schon eine Datei stand... ) freigeben.

Das Problem: Das Malloc in der unten gezeigten Funktion liefert einen 
Pointer auf einen Speicherbereich bei 0x78, was bei einer im Beispiel 
absolut einprogrammierten Sollgröße von 256 Bytes definitiv in den Space 
1 reinragt und auch diverse Variablen (unter anderen 
global_validFilePtr) überschreibt.

Ist jetzt noch mit der alten libc...
1
unsigned char devCom_fileReceive(unsigned char* pData, unsigned char pUpperBound, unsigned char pReceiveStatus)
2
  {
3
  
4
    unsigned char i = 0;
5
    unsigned long packageNumber = 0;
6
    static unsigned long packageAmount = 0;      // Anzahl der zu erwartenden Datenpakete
7
    static unsigned char* tmpFilePtr = NULL;      // Working-Dataptr
8
    static unsigned long lastPackageNumber = 0;    // Letztes empfangenes Paket (nummer)
9
    static unsigned char* tmpFilePtrSave = NULL;  // Zeigt immer auf erstes Byte des allokierten Speichers  
10
11
12
     packageNumber= ((unsigned long)pData[0] << 24) |  ((unsigned long)pData[1] << 16) | ((unsigned long)pData[2] << 8) | ((unsigned long)pData[3] & 0xff);
13
     
14
15
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Vorbereiten auf neues File / Prüfen von Speicherplatz usw.
16
    if(pReceiveStatus == DEVCOM_FILETRANSFER_ANNOUNCED)
17
    {
18
      // TODO: Check Filename, Speichere entsprechend
19
      
20
      lastPackageNumber = 0;
21
      packageAmount = packageNumber; // Speichere Paketnummer, beim Ankündigungsframe ist die Paketnummer die Anzahl der nachfolgenden Pakete
22
23
      // Speicher so allokieren, als währen alle ganze Pakete: TODO: Kleinere Speicher...    
24
      free(tmpFilePtrSave); // ggf. alten nicht benutzten Speicher freigeben..
25
      
26
      tmpFilePtr = (unsigned char*) malloc(256);
27
      tmpFilePtrSave = tmpFilePtr;
28
29
      // Prüfe ob genügend Speicher vorhanden war
30
      if(tmpFilePtr == NULL)
31
      {
32
        return(DEVCOM_FILETRANSER_ERROR_NOT_ENOUGH_MEM);
33
      }
34
      else
35
      {
36
        return(DEVCOM_FILETRANSFER_ERROR_NOERROR);
37
      }
38
    }
39
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ FILE RECEIVING
40
    else
41
    {      
42
      if(packageNumber != (lastPackageNumber+1)) // Wenn es sich nicht um das erwartete nächste Paket handelt
43
      {
44
        return(DEVCOM_FILETRANSFER_ERROR_PACKAGE_NUMBER); // ABBRUCH!!
45
      }
46
      
47
      // Werte kopieren
48
      for(i = 0; i <= pUpperBound; i++) // hinter Rahmendaten platzieren
49
      {
50
        *tmpFilePtr = pData[i];                                // Nächstes Speicherelement
51
        tmpFilePtr++;
52
      }
53
  
54
      if(packageNumber >= packageAmount)
55
      {
56
        // File successfully received
57
         free((void*)global_validFilePtr);     // letzten Datensatz freigeben
58
        global_validFilePtr = tmpFilePtrSave;   // Datensatz aktualisieren
59
        global_newFileReceived = 1;
60
        global_lastReceivedFileSize = (unsigned long)((packageNumber-1)*252 + (unsigned long)i);      
61
        tmpFilePtrSave = NULL; 
62
      }    
63
      else
64
      {
65
        lastPackageNumber = packageNumber; // merken für nächsten Aufruf
66
      }
67
      
68
      // Wenn bis hierher ohne Return gekommen, war alles in Ordnung...
69
      return(DEVCOM_FILETRANSFER_ERROR_NOERROR);
70
    }  
71
  }

von Thomas Bergmüller (Gast)


Angehängte Dateien:

Lesenswert?

Debugfenster nach erfolgtem Dateitransfer von 256 Bytes (2 Pakete)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Thomas Bergmüller schrieb:

> Das Problem: Das Malloc in der unten gezeigten Funktion liefert einen
> Pointer auf einen Speicherbereich bei 0x78

Zeig mal die Kommandozeilen von Compiler und Linker, mit denen du
compiliert hast.

von Thomas Bergmüller (Gast)


Lesenswert?

hm... ich weiß zwar nicht wieso sich der Fehler so äußerte aber es läuft 
jetzt...

es stand definitiv in tmpFilePtrSave ein falscher Wert, was ich jetzt 
geändert habe hat damit nicht viel zu tun ^^

hab die Zählvariable i als unsigned char deklariert, wenn man die 
natürlich bis <= 255 zählen lässt, baut man sich eine Endlosschleife... 
Ich nehme an wenn ich dann auch noch da immer einen Filepointer erhöhe, 
dann zerschieß ich mir den ganzen Speicher mit den sich immer 
wiederholenden Daten :S

Wie dem auch sei, vielen Dank für die Hilfe!!

von Ulrich P. (uprinz)


Lesenswert?

Mal so ganz grundsätzlich:

In einer Funktion
{
- Paket mit malloc(size) allozieren
- Paket auf die Reise schicken (sprich be-/abarbeiten)
- Paket freigeben free(paket)
}

Im ISR
{
- IsrPtr = Paket (mit Kopie arbeiten)
- Nur IsrPtr für die Bearbeitung verwenden
}

Durch Semaphoren oder einfache überschaubare Mechanismen kann man beide 
Funktionen noch gegeneinander verriegeln:
- ISR beendet und deaktiviert sich ggf. selbst, wenn Paket==NULL
- ISR steuert ein (globales oder im Header des Pakets vorhandenes) Flag, 
welches der Funktion mitteilt, dass das Paket noch in Bearbeitung ist.
Durch diese Flags kann man die Funktion auch mehrfach aufrufen, sie 
meldet zurück, ob das Paket bearbeitet und freigegeben ist oder nicht.

Im thoretischen Beispiel des TO ist eine böse Falle drin, den hier 
werden global_validFilePtr und workingFilePtr nicht sauber gesetzt und 
free(global_validFilePtr) hat einen anderen Wert angenommen, als malloc 
für workingFilePtr herangezogen hatte.

Daher meine Aufforderung, immer nur mit Kopieen des Pointers zu 
arbeiten, dem per malloc() ein Speicher zugewiesen wurde und das 
Original wieder für free zu verwenden. Kostet ein paar Pointer im RAM, 
spart jede Menge Kopfschmerztabletten.

Gruß, Ulrich

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.