Cosa succede quando i microcontrollori esauriscono la RAM?


12

Potrebbe essere solo una coincidenza, ma ho notato che i microcontrollori che ho usato sono stati riavviati quando hanno esaurito la RAM (Atmega 328 se specifico dell'hardware). È quello che fanno i microcontrollori quando esauriscono la memoria? In caso contrario, cosa succede allora?

Perché come? Il puntatore dello stack è certamente aumentato ciecamente a un intervallo di memoria non allocato (o sottoposto a roll-over), ma cosa succede allora: esiste un qualche tipo di protezione che lo fa riavviare o è (tra gli altri effetti) il risultato della sovrascrittura di critico dati (che presumo diversi dal codice che penso sia eseguito direttamente da Flash)?

Non sono sicuro che questo dovrebbe essere qui o su Stack Overflow, per favore fatemi sapere se questo dovrebbe essere spostato, anche se sono abbastanza sicuro che l'hardware abbia un ruolo in questo.

Aggiornare

Devo sottolineare che sono particolarmente interessato all'effettivo meccanismo alla base della corruzione della memoria (è il risultato del rotolamento di SP -> dipende dalla mappatura della memoria degli uC ecc.)?


8
Alcuni micro verranno ripristinati se si tenta di accedere a indirizzi non validi. È una funzione preziosa implementata nell'hardware. Altre volte potrebbe finire per saltare in un posto arbitrario (supponiamo che tu abbia ostruito l'indirizzo di ritorno per un ISR), forse eseguendo dati piuttosto che codice se l'architettura lo consente, e forse restando intrappolato in un ciclo che il cane da guardia lo mette in evidenza di.
Spehro Pefhany,

2
Un processore non può esaurire la RAM, non ci sono istruzioni che lo faranno esaurire la RAM. A corto di RAM è interamente un concetto di software.
user253751

Risposte:


14

In generale, lo stack e l'heap si bloccano l'un l'altro. A quel punto tutto diventa disordinato.

A seconda dell'MCU, una o più cose possono (o succederanno) accadere.

  1. Le variabili vengono corrotte
  2. Lo stack viene danneggiato
  3. Il programma si corrompe

Quando succede, inizi ad avere comportamenti strani - le cose non fanno quello che dovrebbero. Quando succede 2, si scatena l'inferno. Se l'indirizzo di ritorno nello stack (se ce n'è uno) è danneggiato, allora dove tornerà la chiamata corrente è la supposizione di chiunque. In quel momento fondamentalmente l'MCU inizierà a fare cose casuali. Quando 3 accade di nuovo, chissà cosa sarebbe successo. Questo succede solo quando si esegue il codice dalla RAM.

In generale quando lo stack viene danneggiato è tutto finito. Proprio quello che succede dipende dall'MCU.

Potrebbe essere che il tentativo di allocare la memoria in primo luogo non riesca, quindi la corruzione non si verifica. In questo caso la MCU potrebbe sollevare un'eccezione. Se non è installato un gestore eccezioni, molto spesso l'MCU si arresterà (un equivalente di while (1);. Se è installato un gestore, potrebbe riavviarsi in modo pulito.

Se l'allocazione della memoria procede, o se tenta, fallisce e continua senza memoria allocata, allora ti trovi nel regno di "chissà?". L'MCU potrebbe finire per riavviarsi da solo attraverso la giusta combinazione di eventi (interruzioni causate dal ripristino del chip, ecc.), Ma non vi è alcuna garanzia che ciò accada.

Ciò che di solito può esserci un'alta probabilità di accadere, tuttavia, se è abilitato, è il timer di watchdog interno (se presente) che scade e riavvia il chip. Quando il programma fallisce completamente in questo tipo di crash, le istruzioni per reimpostare il timer generalmente non verranno eseguite, quindi scadrà e si ripristinerà.


Grazie per la risposta, è un eccellente riepilogo degli effetti. Forse avrei dovuto precisare che vorrei maggiori dettagli sull'attuale meccanismo di quelle corruzioni: l'intera RAM è allocata allo stack e all'heap, in modo tale che il puntatore dello stack passi sopra e sovrascriva variabili / indirizzi precedenti? O dipende meno dalla mappatura della memoria di ciascun micro? Opzionalmente (probabilmente è un argomento in sé), sarei interessato a imparare come sono implementati quei gestori hardware.
Mister Mystère, il

1
Dipende principalmente dal compilatore e dalla libreria C standard in uso. A volte dipende anche dalla configurazione del compilatore (script linker, ecc.).
Majenko,

Potresti approfondire questo, forse con un paio di esempi?
Mister Mystère, l'

Non proprio no. Alcuni sistemi allocano lo spazio finito per segmenti diversi, altri no. Alcuni usano gli script del linker per definire i segmenti, altri no. Scegli un microcontrollore che ti interessa e fai qualche ricerca su come funzionano le allocazioni di memoria.,
Majenko

12

Una vista alternativa: i microcontrollori non esauriscono la memoria.

Almeno, non quando programmato correttamente. La programmazione di un microcontrollore non è esattamente come la programmazione generale, per farlo correttamente devi essere consapevole dei suoi vincoli e programmare di conseguenza. Ci sono strumenti per aiutare a garantire questo. Cercali e imparali - almeno come leggere gli script e gli avvisi del linker.

Tuttavia, come affermano Majenko e altri, un microcontrollore programmato male può esaurire la memoria e quindi fare qualsiasi cosa, incluso il loop infinito (che almeno dà al timer del watchdog la possibilità di ripristinarlo. Hai abilitato il timer del watchdog, vero? )

Regole di programmazione comuni per i microcontrollori evitano questo: ad esempio, tutta la memoria viene allocata nello stack o allocata staticamente (a livello globale); "nuovo" o "malloc" sono vietati. Così è la ricorsione, in modo che la massima profondità di annidamento delle subroutine possa essere analizzata e mostrata per adattarsi allo stack disponibile.

Pertanto, la memoria massima richiesta può essere calcolata quando il programma viene compilato o collegato e confrontata con la dimensione della memoria (spesso codificata nello script del linker) per il processore specifico a cui si sta eseguendo il targeting.

Quindi il microcontrollore potrebbe non esaurire la memoria, ma il programma potrebbe. E in quel caso, ci riesci

  • riscriverlo, più piccolo o
  • scegli un processore più grande (spesso sono disponibili con dimensioni di memoria diverse).

Un insieme comune di regole per la programmazione dei microcontrollori è MISRA-C , adottato dall'industria automobilistica.

La migliore pratica a mio avviso è quella di utilizzare il sottoinsieme SPARK-2014 di Ada. Ada si rivolge effettivamente a piccoli controller come AVR, MSP430 e ARM Cortex abbastanza bene, e intrinsecamente fornisce un modello migliore per la programmazione del microcontrollore rispetto a C. Ma SPARK aggiunge annotazioni al programma, sotto forma di commenti, che descrivono ciò che il programma sta facendo.

Ora gli strumenti SPARK analizzeranno il programma, comprese quelle annotazioni, e dimostreranno le proprietà al riguardo (o segnaleranno potenziali errori). Non è necessario perdere tempo o spazio di codice per gestire accessi di memoria errati o overflow di numeri interi perché è stato dimostrato che non si verificano mai.

Sebbene sia coinvolto un maggior lavoro iniziale con SPARK, l'esperienza dimostra che può arrivare a un prodotto in modo più rapido ed economico perché non passi il tempo a inseguire riavvii misteriosi e altri comportamenti strani.

Un confronto tra MISRA-C e SPARK


3
+1 questo. Il porting malloc()(ed è il compagno C ++ new) sull'AVR è una delle peggiori cose che la gente di Arduino avrebbe potuto fare, e ha portato a molti, molti programmatori molto confusi con codice rotto sia sul loro forum, sia allo scambio di stack di Arduino. Ci sono pochissime situazioni in cui avere mallocun ATmega è vantaggioso.
Connor Wolf,

3
+1 per la filosofia, -1 per il realismo. Se le cose fossero programmate correttamente, non ci sarebbe bisogno di questa domanda. La domanda era: cosa succede quando i microcontrollori esauriscono la memoria. Come impedire loro di rimanere senza memoria è un'altra domanda. In un'altra nota, la ricorsione è uno strumento potente, sia per risolvere i problemi che per esaurire lo stack.
PkP,

2
@Brian, dato che non sono un idiota, ovviamente sono d'accordo con te. Mi piace solo pensarci dal punto di vista inverso - mi piace sperare che quando ti rendi conto delle orribili conseguenze dell'esaurimento della memoria (stack), cercheresti modi per impedire che ciò accada. In questo modo hai un vero impulso verso la ricerca di buone pratiche di programmazione invece di seguire solo buoni consigli di programmazione ... e quando colpisci la barriera di memoria hai maggiori probabilità di applicare le buone pratiche anche a spese della convenienza. È solo un punto di vista ...
PkP

2
@PkP: sentiti forte e chiaro. L'ho etichettato come una visione alternativa, perché in realtà non risponde alla domanda!
Brian Drummond,

2
@ MisterMystère: i microcontrollori generalmente non esauriscono la memoria. Un microcontrollore che ha 4096 byte di RAM alla prima accensione avrà 4096 byte per sempre. È possibile che il codice possa tentare erroneamente di accedere ad indirizzi che non esistono o aspettarsi che due diversi metodi di calcolo degli indirizzi accedano a memoria diversa quando non lo fanno, ma il controller stesso eseguirà semplicemente le istruzioni fornite.
supercat

6

Mi piace molto la risposta di Majenko e l'ho fatta +1 da solo. Ma voglio chiarire questo punto in modo netto:

Tutto può succedere quando un microcontrollore esaurisce la memoria.

Non puoi davvero fare affidamento su nulla quando succede. Quando la macchina esaurisce la memoria dello stack, molto probabilmente lo stack viene danneggiato. E mentre ciò accade, tutto può succedere. Valori variabili, sversamenti, registri temporanei, tutti vengono danneggiati, interrompendo i flussi di programma. Se / then / elses può essere valutato in modo errato. Gli indirizzi di ritorno sono confusi, facendo saltare il programma a indirizzi casuali. Qualsiasi codice che hai scritto nel programma può essere eseguito. (Considera il codice come: "if [condition] then {fire_all_missiles ();}"). Anche un sacco di istruzioni che non hai scritto possono essere eseguite quando il core salta in una posizione di memoria non connessa. Tutte le scommesse sono chiuse.


2
Grazie per l'addendum, mi è piaciuta particolarmente la linea fire_all_missiles ().
Mister Mystère, il

1

AVR ha reimpostato il vettore all'indirizzo zero. Quando si sovrascrive lo stack con immondizia casuale, alla fine si gira e si sovrascrive un indirizzo di ritorno e punterà a "nulla"; quindi quando torni da una subroutine a quel nulla, l'esecuzione eseguirà un ciclo attorno all'indirizzo 0 dove di solito si trova un salto per reimpostare il gestore.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.