Cosa succede quando termina un programma incorporato?


29

Cosa succede in un processore integrato quando l'esecuzione raggiunge quell'istruzione finale returnTutto si blocca così com'è; consumo di energia ecc., con un lungo eterno NOP in cielo? o i NOP vengono continuamente eseguiti o un processore si spegne del tutto?

Parte del motivo che chiedo è che mi chiedo se un processore deve spegnersi prima che finisca l'esecuzione e se fa come finisce mai l'esecuzione se si è spento prima?


22
Dipende dalle tue convinzioni. Alcuni dicono che si reincarnerà.
Telaclavo,

9
È per un missile?
Lee Kowalkowski,

6
alcuni sistemi supportano l' istruzione HCF (Halt and Catch Fire) . :)
Stefan Paul Noack,

1
si

Risposte:


41

Questa è una domanda che mi faceva sempre mio padre. " Perché non scorre tutte le istruzioni e si ferma alla fine? "

Diamo un'occhiata a un esempio patologico. Il seguente codice è stato compilato nel compilatore C18 di Microchip per PIC18:

void main(void)
{

}

Produce il seguente output dell'assemblatore:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

Come puoi vedere, viene chiamato main () e alla fine contiene un'istruzione return, anche se non l'abbiamo esplicitamente inserita lì. Quando ritorna principale, la CPU esegue l'istruzione successiva che è semplicemente un GOTO per tornare all'inizio del codice. main () viene semplicemente chiamato più volte.

Ora, detto questo, questo non è il modo in cui le persone farebbero le cose di solito. Non ho mai scritto alcun codice incorporato che consentirebbe a main () di uscire così. Principalmente, il mio codice sarebbe simile a questo:

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

Quindi normalmente non lascerei mai uscire main ().

"OK ok" stai dicendo. Tutto ciò è molto interessante che il compilatore si assicura che non ci sia mai un'ultima dichiarazione di ritorno. Ma cosa succede se forziamo il problema? Cosa succede se codifico a mano il mio assemblatore e non faccio un salto all'inizio?

Bene, ovviamente la CPU continuerebbe a eseguire le istruzioni successive. Sembrerebbero qualcosa del genere:

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

L'indirizzo di memoria successivo dopo l'ultima istruzione in main () è vuoto. Su un microcontrollore con memoria FLASH, un'istruzione vuota contiene il valore 0xFFFF. Almeno su un PIC, quel codice operativo viene interpretato come 'nop' o 'nessuna operazione'. Semplicemente non fa nulla. La CPU avrebbe continuato a eseguire quei nops fino in fondo alla memoria fino alla fine.

Cosa succede dopo?

All'ultima istruzione, il puntatore alle istruzioni della CPU è 0x7FFe. Quando la CPU aggiunge 2 al suo puntatore di istruzioni, ottiene 0x8000, che è considerato un overflow su un PIC con solo 32k FLASH, quindi torna indietro a 0x0000 e la CPU continua felicemente a eseguire le istruzioni all'inizio del codice , proprio come se fosse stato ripristinato.


Hai anche chiesto la necessità di spegnere. Fondamentalmente puoi fare quello che vuoi, e dipende dalla tua applicazione.

Se avessi un'applicazione che doveva solo fare una cosa dopo l'accensione, e quindi non fare nient'altro, potresti semplicemente dedicare un po '(1); alla fine di main () in modo che la CPU smetta di fare qualcosa di evidente.

Se l'applicazione richiede lo spegnimento della CPU, quindi, a seconda della CPU, saranno probabilmente disponibili varie modalità di sospensione. Tuttavia, le CPU hanno l'abitudine di svegliarsi di nuovo, quindi dovresti assicurarti che non ci siano limiti di tempo per il sonno e nessun timer Watch Dog attivo, ecc.

Potresti persino organizzare alcuni circuiti esterni che consentirebbero alla CPU di interrompere completamente la propria potenza al termine. Vedi questa domanda: Utilizzo di un pulsante momentaneo come interruttore a levetta on-off a scatto .


20

Per il codice compilato, dipende dal compilatore. Il compilatore ARM di Rowley CrossWorks gcc che uso i salti per codificare il file crt0.s che ha un ciclo infinito. Il compilatore Microchip C30 per i dispositivi dsPIC e PIC24 a 16 bit (anch'esso basato su gcc) ripristina il processore.

Naturalmente, la maggior parte dei software integrati non termina mai in questo modo ed esegue il codice continuamente in un ciclo.


13

Ci sono due punti da fare qui:

  • Un programma incorporato, a rigor di termini, non può "finire".
  • Molto raramente è necessario eseguire un programma incorporato per qualche tempo e poi "finire".

Il concetto di arresto del programma normalmente non esiste in un ambiente incorporato. A un livello basso una CPU eseguirà le istruzioni mentre può; non esiste una "dichiarazione di reso finale". Una CPU può interrompere l'esecuzione se riscontra un errore irrecuperabile o se viene esplicitamente arrestata (messa in modalità di sospensione, modalità di risparmio energia, ecc.), Ma si noti che anche le modalità di sospensione o guasti irreversibili non garantiscono in genere che non verrà più assegnato alcun codice essere eseguito. Puoi svegliarti dalle modalità di sospensione (è così che vengono normalmente utilizzate) e persino una CPU bloccata può comunque eseguire un gestore NMI (questo è il caso di Cortex-M). Anche un watchdog funzionerà ancora e potresti non essere in grado di disabilitarlo su alcuni microcontrollori una volta abilitato. I dettagli variano notevolmente tra le architetture.

Nel caso di firmware scritto in un linguaggio come C o C ++, cosa succede se esce main () è determinato dal codice di avvio. Ad esempio, ecco la parte rilevante del codice di avvio dalla libreria periferica standard STM32 (per una toolchain GNU, i commenti sono miei):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

Questo codice entrerà in un ciclo infinito quando main () ritorna, sebbene in modo non ovvio (si bl maincarica lrcon l'indirizzo dell'istruzione successiva che è effettivamente un salto a se stesso). Non viene effettuato alcun tentativo di arrestare la CPU o di farla entrare in una modalità a basso consumo, ecc. Se si dispone di una necessità legittima di qualsiasi cosa presente nell'applicazione, è necessario farlo da soli.

Si noti che, come specificato in ARMv7-M ARM A2.3.1, il registro dei collegamenti è impostato su 0xFFFFFFFF al ripristino e una diramazione a quell'indirizzo attiverà un errore. Quindi i progettisti di Cortex-M hanno deciso di considerare anormale un ritorno dal gestore di reset ed è difficile discutere con loro.

Parlando di un legittimo bisogno di fermare la CPU dopo che il firmware è finito, è difficile immaginare che non sarebbe meglio servire da un spegnimento del dispositivo. (Se si disabilita la CPU "per sempre", l'unica cosa che può essere fatta sul dispositivo è un ciclo di alimentazione o un ripristino hardware esterno.) È possibile disinserire un segnale ENABLE per il convertitore DC / DC o disattivare l'alimentazione in in qualche altro modo, come fa un PC ATX.


1
"Puoi svegliarti dalle modalità di sospensione (è così che vengono normalmente utilizzate) e persino una CPU bloccata può comunque eseguire un gestore NMI (questo è il caso di Cortex-M)." <- suona come la parte fantastica di una trama di un libro o di un film. :)
Mark Allen

"Bl main" caricherà "lr" con l'indirizzo delle seguenti istruzioni ("bx lr"), no? C'è qualche motivo per aspettarsi che "lr" contenga qualcos'altro quando viene eseguito "bx lr"?
supercat,

@supercat: hai ragione ovviamente. Ho modificato la mia risposta per rimuovere l'errore ed espanderlo un po '. Pensando a questo, il modo in cui implementano questo ciclo è piuttosto strano; avrebbero potuto facilmente farlo loop: b loop. Mi chiedo se in realtà intendessero fare un ritorno ma si sono dimenticati di salvare lr.
Thorn,

È curioso. Mi aspetterei che un sacco di codice ARM uscisse con LR che conteneva lo stesso valore che conteneva all'ingresso, ma non so che è garantito. Una tale garanzia non sarebbe spesso utile, ma sostenendola sarebbe necessario aggiungere un'istruzione alle routine che copiano r14 in qualche altro registro e quindi chiamano qualche altra routine. Se lr è considerato "sconosciuto" al ritorno, si potrebbe "bx" il registro che contiene la copia salvata. Ciò causerebbe comunque un comportamento molto strano con il codice indicato.
supercat

In realtà sono abbastanza sicuro che le funzioni non foglia dovrebbero salvare lr. Questi di solito spingono lr sullo stack nel prologo e ritornano facendo scattare il valore salvato nel pc. Questo è ciò che farebbe ad esempio un main () in C o C ++, ma ovviamente gli sviluppatori della libreria in questione non hanno fatto nulla del genere in Reset_Handler.
Spina

9

Quando chiedi return, stai pensando a un livello troppo alto. Il codice C viene tradotto in codice macchina. Quindi, se invece pensi al processore che estrae alla cieca le istruzioni dalla memoria e le esegue, non ha idea di quale sia il "finale" return. Quindi, i processori non hanno un fine intrinseco, ma invece spetta al programmatore gestire il caso finale. Come sottolinea Leon nella sua risposta, i compilatori hanno programmato un comportamento predefinito, ma spesso il programmatore può desiderare la propria sequenza di spegnimento (ho fatto varie cose come entrare in una modalità a basso consumo e arrestare, o aspettare che un cavo USB venga collegato e quindi riavviare).

Molti microprocessori hanno istruzioni di arresto, che arrestano il processore senza influire sui perhiperali. Altri processori possono fare affidamento sul "blocco" semplicemente saltando ripetutamente allo stesso indirizzo. Ci sono opzioni possibili, ma dipende dal programmatore perché il processore continuerà semplicemente a leggere le istruzioni da meory, anche se quella memoria non era intesa come istruzioni.


7

Il problema non è incorporato (un sistema incorporato può eseguire Linux o anche Windows) ma autonomo o bare-metal: il programma applicativo (compilato) è l'unica cosa che è in esecuzione sul computer (non importa se si tratta di un microcontrollore o microprocessore).

Per la maggior parte delle lingue, la lingua non definisce cosa succede quando termina "principale" e non è necessario tornare al sistema operativo. Per C dipende da cosa c'è nel file di avvio (spesso crt0.s). Nella maggior parte dei casi l'utente può (o addirittura deve) fornire il codice di avvio, quindi la risposta finale è: qualunque cosa tu scriva è il codice di avvio o ciò che accade nel codice di avvio specificato.

In pratica ci sono 3 approcci:

  • non prendere misure speciali. cosa succede quando i rendimenti principali non sono definiti.

  • passare a 0 o utilizzare qualsiasi altro mezzo per riavviare l'applicazione.

  • inserire un ciclo stretto (o disabilitare gli interrupt ed eseguire un'istruzione di arresto), bloccando il processore per sempre.

Ciò che è appropriato dipende dall'applicazione. Una cartolina d'auguri fur-elise e un sistema di controllo del freno (solo per citare due sistemi integrati) dovrebbero probabilmente riavviarsi. L'aspetto negativo del riavvio è che il problema potrebbe passare inosservato.


5

Stavo guardando qualche codice smontato ATtiny45 (C ++ compilato da avr-gcc) l'altro giorno e quello che fa alla fine del codice è saltare a 0x0000. Fondamentalmente facendo un reset / riavvio.

Se l'ultimo passaggio a 0x0000 viene escluso dal compilatore / assemblatore, tutti i byte nella memoria del programma vengono interpretati come codice macchina "valido" e viene eseguito fino a quando il contatore del programma non passa a 0x0000.

Su AVR un 00 byte (valore predefinito quando una cella è vuota) è un NOP = Nessuna operazione. Quindi funziona molto velocemente, non facendo altro che prendersi del tempo.


1

Il maincodice generalmente compilato viene successivamente collegato al codice di avvio (potrebbe essere integrato nella toolchain, fornita dal fornitore del chip, scritto dall'utente ecc.).

Linker posiziona quindi tutto il codice dell'applicazione e di avvio nei segmenti di memoria, quindi la risposta alle domande dipende da: 1. codice dall'avvio, perché può ad esempio:

  • termina con anello vuoto ( bl lrob . ), che sarà simile a "fine programma", ma gli interrupt e le periferiche abilitati in precedenza continueranno a funzionare,
  • termina con il salto all'inizio del programma (riesegui completamente l'avvio o jsut a main ).
  • semplicemente ignora "quello che sarà il prossimo" dopo la chiamata ai mainritorni.

    1. Nel terzo punto, quando il contatore del programma semplicemente gli incrementi dopo il ritorno dal maincomportamento dipenderanno dal tuo linker (e / o dallo script del linker utilizzato durante il collegamento).
  • Se viene inserita un'altra funzione / codice dopo il main che verrà eseguito con valori di argomento non validi / non definiti,

  • Se la memoria successiva inizia con un'istruzione errata, potrebbe essere generata un'eccezione e la MCU verrà ripristinata (se l'eccezione genera un ripristino).

Se il watchdog è abilitato, alla fine ripristinerà l'MCU nonostante tutti i loop infiniti in cui ci si trova (ovviamente se non verrà ricaricato).


-1

Il modo migliore per arrestare un dispositivo incorporato è attendere per sempre con le istruzioni NOP.

Il secondo modo è quello di chiudere il dispositivo utilizzando il dispositivo stesso. Se si riesce a controllare un relè con le istruzioni, si può solo aprire l'interruttore che alimenta il dispositivo embedded e Huh tuo dispositivo embedded è andato senza alcun consumo di energia.


Questo non risponde davvero alla domanda.
Matt Young,

-4

È stato chiaramente spiegato nel manuale. In genere, la CPU genererà un'eccezione generale perché accederà a una posizione di memoria esterna al segmento dello stack. [eccezione protezione memoria].

Cosa intendevi con sistema incorporato? Microprocessore o microcontrollore? In entrambi i casi, è definito nel manuale.

Nella CPU x86 spegniamo il computer inviando il comando al controller ACIP. Accesso alla modalità di gestione del sistema. Quindi quel controller è un chip I / O e non è necessario spegnerlo manualmente.

Leggi le specifiche ACPI per ulteriori informazioni.


3
-1: il TS non ha menzionato alcuna CPU specifica, quindi non assumere troppo. Diversi sistemi gestiscono questo caso in modi molto diversi.
Wouter van Ooijen,
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.