¿Qué sucede cuando finaliza un programa incorporado?

28

¿Qué sucede en un procesador integrado cuando la ejecución llega a esa declaración final return ? ¿Se acaba de congelar todo como está? ¿Consumo de energía, etc., con un largo NOP eterno en el cielo? o ¿los NOP se ejecutan de forma continua, o se apagará un procesador por completo?

Parte de la razón por la que pregunto es que me pregunto si un procesador necesita apagarse antes de que finalice su ejecución y cómo lo hace. ¿Cómo alguna vez termina de ejecutarse si se ha apagado antes?

    
pregunta Toby

9 respuestas

39

Esta es una pregunta que mi papá siempre solía hacerme. " ¿Por qué no se ejecuta todas las instrucciones y se detiene al final? "

Echemos un vistazo a un ejemplo patológico. El siguiente código fue compilado en el compilador C18 de Microchip para el PIC18:

void main(void)
{

}

Produce la siguiente salida de ensamblador:

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

Como puede ver, main () se llama, y al final contiene una declaración de devolución, aunque nosotros no lo pusimos explícitamente allí. Cuando regresa main, la CPU ejecuta la siguiente instrucción que es simplemente un GOTO para volver al principio del código. main () simplemente se llama una y otra vez.

Ahora, habiendo dicho esto, esta no es la forma en que la gente haría las cosas normalmente. Nunca he escrito ningún código incrustado que permita que main () salga así. Sobre todo, mi código se vería así:

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

Por lo tanto, normalmente nunca permitiría que main () salga.

"OK ok", dices. Todo esto es muy interesante porque el compilador se asegura de que nunca haya una última declaración de retorno. ¿Pero qué pasa si forzamos el tema? ¿Qué pasa si codifiqué a mano mi ensamblador y no volví a saltar al principio?

Bueno, obviamente la CPU simplemente continuará ejecutando las siguientes instrucciones. Esos se verían así:

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

La siguiente dirección de memoria después de la última instrucción en main () está vacía. En un microcontrolador con memoria FLASH, una instrucción vacía contiene el valor 0xFFFF. Al menos en un PIC, ese código de operación se interpreta como un 'nop' o 'ninguna operación'. Simplemente no hace nada. La CPU continuaría ejecutando esos nops hasta el final de la memoria hasta el final.

¿Qué hay después de eso?

En la última instrucción, el puntero de instrucción de la CPU es 0x7FFe. Cuando la CPU agrega 2 a su puntero de instrucción, obtiene 0x8000, que se considera un desbordamiento en un PIC con solo 32k FLASH, por lo que vuelve a 0x0000, y la CPU felizmente continúa ejecutando las instrucciones al principio del código , como si se hubiera reiniciado.

También preguntaste sobre la necesidad de apagar. Básicamente, puedes hacer lo que quieras y depende de tu aplicación.

Si tuviera una aplicación que solo necesitaba hacer una cosa después del encendido, y luego no haga nada más, solo podría poner un poco de tiempo (1); al final de main () para que la CPU deje de hacer algo notable.

Si la aplicación requiere que la CPU se apague, entonces, dependiendo de la CPU, probablemente habrá varios modos de suspensión disponibles. Sin embargo, las CPU tienen el hábito de despertarse de nuevo, así que tendrías que asegurarte de que no haya límite de tiempo para el sueño, y que no esté activo el temporizador Watch Dog, etc.

Incluso podría organizar algunos circuitos externos que le permitirían a la CPU cortar completamente su propia energía cuando hubiera terminado. Vea esta pregunta: Utilizando un botón pulsador momentáneo como un interruptor de encendido y apagado .

    
respondido por el Rocketmagnet
20

Para el código compilado, depende del compilador. El compilador ARM Rowley CrossWorks gcc que utilizo salta para codificar en el archivo crt0.s que tiene un bucle infinito. El compilador Microchip C30 para los dispositivos dsPIC y PIC24 de 16 bits (también basado en gcc) restablece el procesador.

Por supuesto, la mayoría del software incorporado nunca termina así, y ejecuta el código continuamente en un bucle.

    
respondido por el Leon Heller
13

Hay dos puntos que deben hacerse aquí:

  • Un programa incorporado, estrictamente hablando, no puede "finalizar".
  • Rara vez es necesario ejecutar un programa incorporado durante algún tiempo y luego "finalizar".

El concepto de cierre de un programa normalmente no existe en un entorno integrado. En un nivel bajo, una CPU ejecutará instrucciones mientras pueda; no hay tal cosa como una "declaración de devolución final". Una CPU puede detener la ejecución si encuentra una falla irrecuperable o si se detiene explícitamente (se pone en modo de suspensión, modo de baja potencia, etc.), pero tenga en cuenta que incluso los modos de suspensión o fallas no recuperables generalmente no garantizan que no vaya a haber más código ser ejecutado. Puede activarse desde los modos de suspensión (así es como se usan normalmente), e incluso una CPU bloqueada puede ejecutar un controlador de NMI (este es el caso de Cortex-M). También se ejecutará un watchdog, y es posible que no pueda desactivarlo en algunos microcontroladores una vez que esté habilitado. Los detalles varían mucho entre arquitecturas. Necesitará leer los manuales relevantes con mucho cuidado si desea garantizar cierto comportamiento (vea a continuación por qué no debería intentar hacerlo de todos modos).

En el caso de firmware escrito en un lenguaje como C o C ++, lo que sucede si main () sale está determinado por el código de inicio. Por ejemplo, aquí está la parte relevante del código de inicio de la Biblioteca Periférica Estándar STM32 (para una cadena de herramientas GNU, los comentarios son míos):

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

Este código entrará en un bucle infinito cuando se devuelva main (), aunque de una manera no obvia ( bl main carga lr con la dirección de la siguiente instrucción que es en realidad un salto a sí mismo). No se hace ningún intento de detener la CPU o de ingresar en un modo de bajo consumo, etc. Si tiene una necesidad legítima de algo de eso en su aplicación, tendrá que hacerlo usted mismo.

Tenga en cuenta que, tal como se especifica en ARMv7-M ARM A2.3.1, el registro de enlace se establece en 0xFFFFFFFF al restablecerse, y una derivación a esa dirección provocará un error. Así que los diseñadores de Cortex-M decidieron tratar el retorno del controlador de reinicio como algo anormal, y es difícil discutir con ellos.

Hablando de una necesidad legítima de detener la CPU una vez finalizado el firmware, es difícil imaginar que no se pueda solucionar mejor con un apagado de su dispositivo. (Si desactiva la CPU "para siempre", lo único que puede hacer con su dispositivo es un ciclo de alimentación o reinicio de hardware externo). Puede deshabilitar una señal ENABLE para su convertidor DC / DC o apagar su fuente de alimentación. de alguna otra manera, como lo hace una PC ATX.

    
respondido por el Thorn
9

Cuando preguntas sobre return , estás pensando en un nivel demasiado alto. El código C se traduce en código máquina. Por lo tanto, si en cambio piensa que el procesador extrae las instrucciones de la memoria y las ejecuta ciegamente, no tiene idea de cuál es el "final" return . Por lo tanto, los procesadores no tienen un final inherente, sino que depende del programador manejar el caso final. Como señala Leon en su respuesta, los compiladores han programado un comportamiento predeterminado, pero muchas veces el programador puede querer su propia secuencia de apagado (he hecho varias cosas como ingresar un modo de bajo consumo y detener, o esperar a que se conecte un cable USB en y luego reiniciar).

Muchos microprocesadores tienen instrucciones de detención, lo que detiene el procesador sin afectar a los perhiperales. Otros procesadores pueden confiar en "detenerse" simplemente saltando a la misma dirección repetidamente. Hay muchas opciones, pero depende del programador porque el procesador simplemente seguirá leyendo las instrucciones de la memoria, incluso si esa memoria no tenía la intención de ser instrucciones.

    
respondido por el Klox
7

El problema no está integrado (un sistema integrado puede ejecutar Linux o incluso Windows), sino autónomo o sin restricciones: el programa de aplicación (compilado) es lo único que se ejecuta en la computadora (no importa si es un microcontrolador o microprocesador).

Para la mayoría de los idiomas, el idioma no define lo que sucede cuando termina el término 'principal' y no hay ningún sistema operativo al que volver. Para C depende de lo que está en el archivo de inicio (a menudo crt0.s). En la mayoría de los casos, el usuario puede (o incluso debe) proporcionar el código de inicio, por lo que la respuesta final es: lo que escriba es el código de inicio o lo que ocurra en el código de inicio que especifique.

En la práctica hay 3 enfoques:

  • no toma medidas especiales. lo que sucede cuando el rendimiento principal no está definido.

  • salta a 0, o usa cualquier otro medio para reiniciar la aplicación.

  • ingrese un ciclo cerrado (o deshabilite las interrupciones y ejecute una instrucción de detención), bloqueando el procesador para siempre.

Lo que es apropiado depende de la aplicación. Probablemente deberían reiniciarse una tarjeta de felicitación y un sistema de control de frenos (solo para mencionar dos sistemas integrados). La desventaja de reiniciar es que el problema puede pasar desapercibido.

    
respondido por el Wouter van Ooijen
5

Estuve viendo un código ATtiny45 desensamblado (C ++ compilado por avr-gcc) el otro día y lo que hace al final del código es saltar a 0x0000. Básicamente haciendo un reinicio / reinicio.

Si el compilador / ensamblador omite ese último salto a 0x0000, todos los bytes en la memoria del programa se interpretan como un código de máquina "válido" y se ejecutan hasta que el contador del programa se desplace a 0x0000.

En AVR, un byte 00 (el valor predeterminado cuando una celda está vacía) es un NOP = Sin operación. Por lo tanto, se ejecuta muy rápido, sin hacer nada, solo tomándose un tiempo.

    
respondido por el jippie
1

El código main generalmente compilado se vincula luego con el código de inicio (puede integrarse en la cadena de herramientas, proporcionado por el proveedor de chips, escrito por usted, etc.).

El enlazador luego coloca todas las aplicaciones y el código de inicio en los segmentos de memoria, por lo que las preguntas de respuesta a usted dependen de: 1. código desde el inicio, porque puede, por ejemplo:

  • termina con un bucle vacío ( bl lr o b . ), que será similar al "final del programa", pero las interrupciones y los periféricos habilitados previamente seguirán funcionando,
  • termine con el salto al principio del programa (ya sea vuelva a ejecutar completamente el inicio o jsut a main ).
  • simplemente ignora "lo que vendrá después" después de la llamada a main devuelve.

    1. En la tercera viñeta, cuando el contador del programa simplemente se incremente después de regresar de main , el comportamiento dependerá de su vinculador (y / o del script del vinculador utilizado durante la vinculación).
  • Si se coloca otra función / código después de su main , se ejecutará con valores de argumentos no válidos / indefinidos,

  • Si la siguiente memoria comienza con una mala instrucción, se puede generar una excepción y MCU finalmente se restablecerá (si la excepción genera un restablecimiento).

Si está habilitado el watchdog, finalmente se restablecerá MCU a pesar de todos los bucles infinitos en los que estás (por supuesto, si no se volverá a cargar).

    
respondido por el kwesolowski
-1

La mejor manera de detener un dispositivo integrado es esperar eternamente con las instrucciones de NOP.

La segunda forma es cerrar el dispositivo usando el dispositivo en sí. Si puede controlar un relé con sus instrucciones, simplemente puede abrir el interruptor que alimenta su dispositivo integrado y huh su dispositivo integrado desaparece sin consumo de energía.

    
respondido por el Enes Unal
-4

Se explicó claramente en el manual. Normalmente una excepción general será ser lanzado por la CPU porque accederá a una ubicación de memoria que está fuera de la Segmento de pila. [excepción de protección de memoria].

¿Qué quiso decir con el sistema integrado? ¿Microprocesador o microcontrolador? De cualquier manera, se define en el manual.

En la CPU x86 apagamos la computadora enviando el comando al controlador ACIP. Entrar en el modo de gestión del sistema. Así que ese controlador es un chip de E / S y no lo hace Necesito apagarlo manualmente.

Lea la especificación ACPI para obtener más información.

    
respondido por el Standard Sandun

Lea otras preguntas en las etiquetas