¿Qué sucede cuando los microcontroladores se quedan sin RAM?

12

Puede ser solo una coincidencia, pero he notado que los microcontroladores que utilicé se reiniciaron cuando se quedaron sin RAM (Atmega 328 si es específico del hardware). ¿Es eso lo que hacen los microcontroladores cuando se quedan sin memoria? Si no, ¿qué pasa entonces?

¿Por qué / cómo? El puntero de la pila ciertamente se aumenta ciegamente a un rango de memoria no asignado (o se transfiere), pero lo que sucede entonces: ¿hay algún tipo de protección que lo haga reiniciar o es (entre otros efectos) el resultado de la sobreescritura de un elemento crítico? datos (que supongo que son diferentes del código que creo que se ejecuta directamente desde flash)?

No estoy seguro de si esto debería estar aquí o en Stack Overflow, avíseme si se debe mover, aunque estoy bastante seguro de que el hardware tiene un papel en eso.

Actualizar

Debo señalar que estoy particularmente interesado en el mecanismo real detrás de la corrupción de la memoria (¿es el resultado del vuelco del SP -> depende de la asignación de memoria de la UC, etc.)?

Señor Mystère
fuente
8
Algunos micros se restablecerán si intenta acceder a direcciones no válidas. Es una característica valiosa implementada en hardware. Otras veces, puede terminar saltando a un lugar arbitrario (digamos que ha aporreado la dirección de retorno de un ISR), tal vez ejecutando datos en lugar de código si la arquitectura lo permite, y tal vez quedando atrapado en un bucle que el perro guardián saca. de.
Spehro Pefhany
2
Un procesador no puede quedarse sin RAM, no hay instrucciones que lo hagan quedarse sin RAM. Quedarse sin RAM es completamente un concepto de software.
user253751

Respuestas:

14

En general, la pila y el montón chocan entre sí. En ese punto, todo se vuelve desordenado.

Dependiendo de la MCU, una de varias cosas puede suceder (o sucederá).

  1. Las variables se corrompen
  2. La pila se corrompe
  3. El programa se corrompe.

Cuando sucede 1, comienzas a tener un comportamiento extraño: las cosas no hacen lo que deberían. Cuando sucede 2, se desata todo tipo de infierno. Si la dirección de retorno en la pila (si hay una) está dañada, entonces nadie sabe dónde volverá la llamada actual. En ese momento, básicamente, la MCU comenzará a hacer cosas al azar. Cuando 3 vuelve a suceder, quién sabe qué pasaría. Esto solo sucede cuando está ejecutando código fuera de RAM.

En general, cuando la pila se corrompe, todo termina. Lo que sucede depende de la MCU.

Puede ser que al intentar asignar la memoria en primer lugar falle, por lo que la corrupción no ocurre. En este caso, la MCU podría generar una excepción. Si no hay un controlador de excepción instalado, la mayoría de las veces la MCU simplemente se detendrá (un equivalente de while (1);. Si hay un controlador instalado, entonces podría reiniciarse limpiamente.

Si la asignación de memoria continúa, o si lo intenta, falla y simplemente continúa sin memoria asignada, entonces estás en los reinos de "¿quién sabe?". La MCU puede terminar reiniciando a través de la combinación correcta de eventos (las interrupciones causaron que terminen reiniciando el chip, etc.), pero no hay garantía de que eso suceda.

Sin embargo, lo que generalmente puede haber una alta probabilidad de que suceda, si está habilitado, es el temporizador interno de vigilancia (si existe) que excede el tiempo de espera y reinicia el chip. Cuando el programa deja de funcionar por completo durante este tipo de bloqueo, las instrucciones para restablecer el temporizador generalmente no se ejecutarán, por lo que se agotará el tiempo y se restablecerá.

Majenko
fuente
Gracias por su respuesta, es un excelente resumen de los efectos. Quizás debería haber especificado que me gustaría obtener más detalles sobre el mecanismo real de esas corrupciones: ¿se asigna toda la RAM a la pila y al montón, de modo que el puntero de la pila se desplace y sobrescriba las variables / direcciones anteriores? ¿O eso es menos dependiente de la asignación de memoria de cada micro? Opcionalmente (probablemente es un tema en sí mismo), me interesaría aprender cómo se implementan esos controladores de hardware.
Señor Mystère
1
Depende principalmente del compilador y de la biblioteca C estándar en uso. A veces también depende de cómo esté configurado el compilador (scripts de enlazador, etc.).
Majenko
¿Podría ampliar eso, tal vez con un par de ejemplos?
Señor Mystère
No, realmente no. Algunos sistemas asignan espacio finito para diferentes segmentos, otros no. Algunos usan scripts de enlazador para definir segmentos, otros no. Elija un microcontrolador que le interese e investigue un poco sobre cómo funcionan sus asignaciones de memoria.,
Majenko
12

Una vista alternativa: los microcontroladores no se quedan sin memoria.

Al menos, no cuando se programa correctamente. Programar un microcontrolador no es exactamente como la programación de propósito general, para hacerlo correctamente debe tener en cuenta sus limitaciones y programar en consecuencia. Hay herramientas para ayudar a garantizar esto. Búscalos y aprende - al menos cómo leer guiones y advertencias de enlazadores.

Sin embargo, como dicen Majenko y otros, un microcontrolador mal programado puede quedarse sin memoria, y luego hacer cualquier cosa, incluido el bucle infinito (que al menos le da al temporizador de vigilancia la oportunidad de restablecerlo. Habilitó el temporizador de vigilancia, ¿no? )

Las reglas de programación comunes para los microcontroladores evitan esto: por ejemplo, toda la memoria está asignada en la pila o está estática (globalmente) asignada; "nuevo" o "malloc" están prohibidos. También lo es la recursión, de modo que la profundidad máxima de anidamiento de subrutinas se pueda analizar y demostrar que se ajusta a la pila disponible.

Por lo tanto, el almacenamiento máximo requerido se puede calcular cuando el programa se compila o se vincula, y se compara con el tamaño de la memoria (a menudo codificada en el script del vinculador) para el procesador específico al que se dirige.

Entonces el microcontrolador puede no quedarse sin memoria, pero su programa sí. Y en ese caso, llegas a

  • reescribirlo, más pequeño o
  • elija un procesador más grande (a menudo están disponibles con diferentes tamaños de memoria).

Un conjunto común de reglas para la programación de microcontroladores es MISRA-C , adoptado por la industria del motor.

En mi opinión, la mejor práctica es utilizar el subconjunto SPARK-2014 de Ada. Ada en realidad se dirige a pequeños controladores como AVR, MSP430 y ARM Cortex razonablemente bien, e inherentemente proporciona un mejor modelo para la programación de microcontroladores que C. Pero SPARK agrega anotaciones al programa, en forma de comentarios, que describen lo que el programa está haciendo.

Ahora las herramientas SPARK analizarán el programa, incluidas esas anotaciones, y probarán propiedades al respecto (o informarán posibles errores). No tiene que perder tiempo ni espacio de código lidiando con accesos de memoria erróneos o desbordamientos de enteros porque se ha demostrado que nunca suceden.

Aunque hay más trabajo inicial relacionado con SPARK, la experiencia demuestra que puede llegar a un producto más rápido y más barato porque no pasa tiempo persiguiendo reinicios misteriosos y otros comportamientos extraños.

Una comparación de MISRA-C y SPARK

Brian Drummond
fuente
3
+1 esto. La transferencia malloc()(y su compañero de C ++ new) al AVR es una de las peores cosas que la gente arduino podría haber hecho, y ha llevado a muchos, muchos programadores muy confundidos con código roto tanto en su foro como en el intercambio de pila arduino. Hay muy, muy pocas situaciones en las que tener mallocun ATmega es beneficioso.
Connor Wolf
3
+1 para filosofía, -1 para realismo. Si las cosas se programaran correctamente, no habría necesidad de esta pregunta. La pregunta era qué sucede cuando los microcontroladores se quedan sin memoria. Cómo evitar que se queden sin memoria es otra cuestión. En otra nota, la recursividad es una herramienta poderosa, tanto para resolver problemas como para quedarse sin pila.
PkP
2
@ Brian, ya que soy no un idiota, que, obviamente, de acuerdo con usted. Simplemente me gusta pensarlo en el punto de vista inverso: me gusta esperar que cuando te des cuenta de las horribles consecuencias de quedarse sin memoria (stack), busques formas de evitar que suceda. De esa manera, tiene un ímpetu real para encontrar buenas prácticas de programación en lugar de simplemente seguir buenos consejos de programación ... y cuando llegue a la barrera de la memoria, es más probable que aplique las buenas prácticas incluso a expensas de la conveniencia. Es solo un punto de vista ...
PkP
2
@PkP: escucharte fuerte y claro. Lo etiqueté como una vista alternativa, ¡porque en realidad no responde la pregunta!
Brian Drummond el
2
@ MisterMystère: los microcontroladores generalmente no se quedan sin memoria. Un microcontrolador que tiene 4096 bytes de RAM cuando se enciende por primera vez tendrá 4096 bytes para siempre. Es posible que el código intente acceder erróneamente a direcciones que no existen o espere que dos métodos diferentes de cálculo de direcciones accedan a memoria diferente cuando no lo hacen, pero el controlador simplemente ejecutará las instrucciones que se le dan.
supercat
6

Realmente me gusta la respuesta de Majenko y la hice +1. Pero quiero aclarar esto con precisión:

Cualquier cosa puede suceder cuando un microcontrolador se queda sin memoria.

Realmente no puedes confiar en nada cuando sucede. Cuando la máquina se queda sin memoria de pila, la pila probablemente se corrompe. Y mientras eso sucede, cualquier cosa puede suceder. Los valores variables, los derrames, los registros temporales, todos se corrompen, interrumpiendo los flujos del programa. Si / then / elses puede evaluar incorrectamente. Las direcciones de retorno son confusas, lo que hace que el programa salte a direcciones aleatorias. Cualquier código que haya escrito en el programa puede ejecutarse. (Considere código como: "if [condición] entonces {fire_all_missiles ();}"). También se pueden ejecutar un montón de instrucciones que no ha escrito cuando el núcleo salta a una ubicación de memoria no conectada. Todas las apuestas están cerradas.

PkP
fuente
2
Gracias por el apéndice, me gustó especialmente la línea fire_all_missiles ().
Señor Mystère
1

AVR ha restablecido el vector en la dirección cero. Cuando sobrescribe la pila con basura aleatoria, eventualmente recorrerá y sobrescribirá alguna dirección de retorno y apuntará a "en ninguna parte"; luego, cuando regrese de una subrutina a ese lugar, la ejecución se repetirá en la dirección 0, donde generalmente se encuentra un salto para restablecer el controlador.

Ilia
fuente