2009-06-06 13 views
15

Ho riscontrato un problema in un programma C in esecuzione su un microcontrollore AVR (ATMega328P). Credo che sia dovuto a una collisione stack/heap, ma mi piacerebbe poterlo confermare.Come posso visualizzare l'utilizzo della memoria (SRAM) di un programma AVR?

Esiste un modo per visualizzare l'utilizzo di SRAM dallo stack e dall'heap?

Nota: il programma è compilato con avr-gcc e utilizza avr-libc.

Aggiornamento: Il problema reale che sto avendo è che l'implementazione di malloc sta fallendo (restituendo NULL). Tutto lo malloc si verifica all'avvio e tutti gli free si verificano alla fine dell'applicazione (che in pratica non è mai stata poiché la parte principale dell'applicazione si trova in un ciclo infinito). Quindi sono sicuro che la frammentazione non è il problema.

+3

Wow. Devi essere l'unica persona ad aver mai usato malloc su un atmega. Sono sorpreso che lavorano affatto! Il mai usato per essere incluso. – Myforwik

risposta

8

Tu dici malloc è in crisi e il ritorno NULL:

La causa ovvio che si dovrebbe guardare in un primo momento è che il vostro heap è "pieno" - vale a dire, la memoria che hai chiesto di malloc non può essere allocato, perché non è disponibile

Ci sono due scenari da tenere a mente:

una: Si dispone di un mucchio di 16 K, hai già malloced 10 K e si cerca di malloc un ulteriore 10K. Il tuo heap è semplicemente troppo piccolo.

b: Più comunemente, hai un heap di 16 k, hai fatto un sacco di chiamate malloc/free/realloc e il tuo heap è inferiore al 50% 'pieno': chiami malloc per 1K e FAILS - che cosa succede? Risposta: lo spazio libero dell'heap è frammentato - non vi è un contiguo 1K di memoria libera che può essere restituito. I gestori di heap non riescono a compattare l'heap quando questo accade, quindi in genere si sta male. Esistono tecniche per evitare la frammentazione, ma è difficile sapere se questo è davvero il problema. È necessario aggiungere gli shim di registrazione a malloc e gratuitamente in modo da poter avere un'idea di quali operazioni di memoria dinamica vengono eseguite.

EDIT:

Dici tutti mallocs avvengono in fase di avvio, quindi la frammentazione non è il problema.

In tal caso, dovrebbe essere facile sostituire l'allocazione dinamica con statico.

vecchio esempio di codice:

char *buffer; 

void init() 
{ 
    buffer = malloc(BUFFSIZE); 
} 

nuovo codice:

char buffer[BUFFSIZE]; 

Una volta fatto questo in tutto il mondo, il tuo LINKER dovrebbe mettere in guardia voi, se non tutto può andare bene nella memoria disponibile.Non dimenticare di ridurre le dimensioni dell'heap, ma fai attenzione che alcune funzioni del sistema di runtime io possono ancora utilizzare l'heap, quindi potresti non essere in grado di rimuoverlo completamente.

+0

+1 È molto probabile che sia un Tutti i mallocing avvengono all'avvio e tutta la liberazione avviene alla fine dell'applicazione (che in pratica non è mai stata poiché la parte principale dell'applicazione è in un ciclo infinito). Quindi sono certo che la frammentazione non è il problema. –

+1

è un punto molto importante! Per favore, smetti di lasciar cadere questi piccoli suggerimenti nei commenti e aggiungi solo più dettagli possibili alla domanda stessa! – Artelius

+0

@Artelius - Aggiunti questo alla domanda Grazie –

2

L'approccio normale sarebbe quello di riempire la memoria con uno schema noto e quindi di verificare quali aree vengono sovrascritte.

+0

Questa è una cosa molto hacky da fare. Personalmente raccomanderei l'approccio avr-size + freeRam() piuttosto che ricorrere a misure così drastiche ... –

2

Se si utilizzano sia stack che heap, può essere un po 'più complicato. Spiegherò cosa ho fatto quando non viene utilizzato nessun heap. Come regola generale, tutte le società per cui ho lavorato (nel dominio del software C incorporato) hanno evitato l'uso dell'heap per piccoli progetti embedded, per evitare l'incertezza della disponibilità della memoria heap. Utilizziamo invece variabili dichiarate staticamente.

Un metodo consiste nel riempire la maggior parte dell'area dello stack con uno schema noto (ad esempio 0x55) all'avvio. Di solito questo è fatto da un piccolo pezzetto di codice all'inizio dell'esecuzione del software, all'inizio di main(), o forse anche prima che main() inizi, nel codice di avvio. Fare attenzione a non sovrascrivere la piccola quantità di stack in uso a quel punto, naturalmente. Quindi, dopo aver eseguito il software per un po ', ispezionare il contenuto dello spazio di stack e vedere dove l'0x55 è ancora intatto. Il modo in cui "ispezioni" dipende dall'hardware di destinazione. Supponendo che tu abbia un debugger connesso, puoi semplicemente fermare il micro running e leggere la memoria.

Se si dispone di un debugger che può eseguire un breakpoint di accesso alla memoria (un po 'più elaborato rispetto al solito breakpoint di esecuzione), è possibile impostare un punto di interruzione in una particolare posizione dello stack, ad esempio il limite più lontano dello spazio di stack. . Ciò può essere estremamente utile, poiché mostra anche esattamente quale bit di codice è in esecuzione quando raggiunge tale limite di utilizzo dello stack. Ma richiede che il debugger supporti la funzione di breakpoint di accesso alla memoria e spesso non si trova nei debugger "low-end".

Se si utilizza anche heap, può essere un po 'più complicato perché potrebbe essere impossibile prevedere dove collidere stack e heap.

0

Se è possibile modificare il codice per l'heap, è possibile eseguire il riempimento con un paio di byte aggiuntivi (difficili con risorse così basse) su ciascun blocco di memoria. Questi byte potrebbero contenere un modello noto diverso dallo stack. Questo potrebbe darti un'idea se entra in collisione con lo stack vedendolo apparire all'interno dello stack o viceversa.

+0

Il modello potrebbe essere controllato nella funzione gratuita, ma sarà ancora difficile scoprire quando si è verificato l'errore. Si noti inoltre che a volte lo spazio extra di stack è riservato per le variabili locali che potrebbero non essere utilizzate (dipende dal compilatore/codice). In tal caso i pattern potrebbero rimanere inalterati mentre l'heap è ancora danneggiato. – Ron

1

Supponendo che si stia utilizzando solo uno stack (quindi non un RTOS o altro) e che lo stack si trovi alla fine della memoria, in crescita, mentre l'heap inizia dopo l'area BSS/DATA, crescendo. Ho visto implementazioni di malloc che controllano effettivamente lo stackpointer e falliscono in caso di collisione. Potresti provare a farlo.

Se non si è in grado di adattare il codice malloc, è possibile scegliere di mettere lo stack all'inizio della memoria (utilizzando il file linker). In generale è sempre una buona idea conoscere/definire la dimensione massima della pila. Se lo metti all'inizio, avrai un errore nella lettura oltre l'inizio della RAM.L'Heap sarà alla fine e probabilmente non potrà crescere oltre la fine se si tratta di una decente implementazione (restituirà invece NULL). La buona notizia è che hai 2 casi di errore separati per 2 problemi separati.

Per trovare la dimensione massima dello stack, è possibile riempire la memoria con un motivo, eseguire l'applicazione e vedere quanto è andata, vedere anche la risposta di Craig.

+0

L'implementazione di malloc ha esito negativo (restituisce NULL). Il problema che ho è che non sono sicuro che sia una collisione che lo sta causando ... –

+0

Una collisione causerebbe in genere cose molto strane come il ritorno alla funzione sbagliata o la modifica dei dati o l'esecuzione improvvisa al di fuori della RAM/FLASH e avendo un errore di indirizzamento. Se la tua applicazione sembra "normale", è probabile che non si verifichi una collisione. Per eseguire il debug di questo, impostare un punto di interruzione in cui viene restituito il NULL (o se è possibile eseguire il debug della funzione malloc stessa, l'impostazione del punto di interruzione è ancora meglio). A quel punto, controlla lo stackpointer e verifica se la collisione è comparsa. Inoltre, il tuo heap è definito come "qualsiasi memoria non viene utilizzata" oppure puoi impostare la dimensione dell'heap? – Ron

+0

Purtroppo non ho il lusso di un debugger ... –

3

Non utilizzare l'heap/allocazione dinamica su destinazioni incorporate. Soprattutto con un processore con risorse così limitate. Piuttosto ridisegnare la tua applicazione perché il problema si ripresenterà man mano che il tuo programma cresce.

0

Su sistemi operativi Unix come una funzione di libreria denominata sbrk() con un parametro di 0 consente di accedere all'indirizzo più in alto della memoria heap allocata dinamicamente. Il valore restituito è un puntatore void * e potrebbe essere confrontato con l'indirizzo di una variabile allocata dello stack arbitrario.

Utilizzare il risultato di questo confronto con cautela. A seconda della CPU e dell'architettura di sistema, lo stack può essere in crescita da un indirizzo alto arbitrario mentre l'heap allocato si sposta dalla memoria low-bound.

A volte il sistema operativo ha altri concetti per la gestione della memoria (ad esempio OS/9) che posiziona heap e stack in diversi segmenti di memoria nella memoria libera. Su questi sistemi operativi, in particolare per i sistemi embedded, è necessario definire in anticipo i requisiti di memoria massima delle applicazioni per consentire al sistema di allocare segmenti di memoria di dimensioni corrispondenti.

+2

L'ATMega328P non esegue Linux/BSD (ha solo 2K di RAM e non eseguo affatto un sistema operativo) e sì, lo stack si riduce e l'heap cresce. –

+0

La linea AVR non ha il concetto di blocchi protetti dalla memoria, quindi solide sicure da CPU "grandi" non sempre sono possibili.Ad esempio in x86 la linea 286 ha parzialmente e 386 ha la piena implementazione di aree di memoria protette. 'di SO di nuova generazione, con Windows completo (non DOS), Linux, Minix ecc .... probabilmente la maggior parte protetta su 286 era OS/2, totalmente sicuro (kernel) è impossibile su AVR –

17

È possibile controllare l'utilizzo statico RAM utilizzando avr-size utilità, come misura descritto in
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=62968,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=82536,
http://www.avrfreaks.net/index.php?name=PNphpBB2&file=viewtopic&t=95638,
e http://letsmakerobots.com/node/27115

avr-size -C -x Filename.elf

(documentazione avr-size: http://ccrma.stanford.edu/planetccrma/man/man1/avr-size.1.html)

Segue un esempio di come impostare questo su un IDE: Su Code :: Blocks, Progetto -> opzioni di generazione -> pre/post costruire passaggi -> post-generazione passi, comprendono:

avr-size -C $(TARGET_OUTPUT_FILE) o
avr-size -C --mcu=atmega328p $(TARGET_OUTPUT_FILE)

uscita esempio alla fine di costruzione:

AVR Memory Usage 
---------------- 
Device: atmega16 

Program: 7376 bytes (45.0% Full) 
(.text + .data + .bootloader) 

Data:   81 bytes (7.9% Full) 
(.data + .bss + .noinit) 

EEPROM:  63 bytes (12.3% Full) 
(.eeprom) 

dati è l'uso SRAM, ed è solo l'importo che il compilatore conosce al momento della compilazione. È inoltre necessario spazio per le cose create al runtime (in particolare l'utilizzo dello stack).

Per controllare l'utilizzo dello stack (RAM dinamica), da http://jeelabs.org/2011/05/22/atmega-memory-use/

Ecco una piccola funzione di utilità che determina la quantità di RAM è attualmente inutilizzata:

int freeRam() { 
    extern int __heap_start, *__brkval; 
    int v; 
    return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
} 

Ed ecco uno schizzo utilizzando il codice:

void setup() { 
    Serial.begin(57600); 
    Serial.println("\n[memCheck]"); 
    Serial.println(freeRam()); 
} 

La funzione freeRam() restituisce quanti byte esiste tra la fine di th e heap e l'ultima memoria allocata nello stack, quindi è efficace la quantità di stack/heap che può crescere prima di collidere.

È possibile controllare il ritorno di questa funzione attorno al codice che si sospetta possa causare collisione stack/heap.

+2

+1 - Grazie per i link! Si noti che la funzione 'freeRam()' come definita qui fallisce nel caso in cui la memoria liberata viene trattenuta nella 'lista libera' di malloc. Questo è probabilmente un problema nella pratica se la memoria viene frequentemente assegnata e deallocata dinamicamente - probabilmente uno scenario raro su un AVR (ad esempio, nel mio caso sto allocando dinamicamente memoria durante l'inizializzazione del programma solo così 'freeRam()' il codice funziona per me). –

+0

Hai qualche suggerimento sulla funzione freeRam? Se compilo con O3 non funziona più bene. Sto visualizzando il valore dopo un itoa. (Debug funziona bene O1) – BennX

+0

@BennX L'ho usato solo a O0 e O1, e ha funzionato bene. Ma probabilmente sei a conoscenza del fatto che il debugging su O3 è più difficile, quindi ti suggerirei di passare a un livello di ottimizzazione più basso mentre il tuo programma non è solido, nonostante tu abbia probabilmente bisogno di commentarne alcune parti se usi microcontrollori di piccole dimensioni . – mMontu