¿Por qué necesitamos un cargador de arranque separado de nuestro programa de aplicación en microcontroladores?

¿Por qué necesitamos un programa separado en la misma memoria de programa flash de un microcontrolador, específicamente STM32F103, que se llama gestor de arranque?

¿Qué tiene de especial para mantenerlo separado del programa de aplicación principal?

En términos generales, ¿un cargador de arranque de un sistema basado en microprocesador (por ejemplo, PowerPC MPC8270) hace el mismo trabajo que el de un microcontrolador (por ejemplo, ARM STM32F103) o realizan trabajos fundamentalmente diferentes entre sí y, sin embargo, ambos se denominan 'cargador de arranque'? ?

la misma razón por la que tiene chips y piezas individuales y no una estructura monolítica gigante
tu no Simplemente ingrese su programa con los interruptores y las luces en la consola de la computadora.
Estrictamente hablando, no necesita un programa de cargador de arranque separado en un microcontrolador. Pero la mayoría de las veces elegimos tener uno por las funciones de utilidad adicionales que ofrece. Si estas funciones no son necesarias, no se desean, puede eliminar el gestor de arranque. El cargador de arranque del microcontrolador se usa normalmente para grabar un nuevo programa en flash. A veces se puede usar para funciones de depuración, algunos puntos de interrupción de soporte y otras funciones agradables. En una microcomputadora, normalmente el cargador de arranque carga programas desde la memoria masiva y será necesario allí.

Respuestas (8)

Un cargador de arranque en un microcontrolador es responsable de actualizar el firmware principal a través de un canal de comunicación que no sea el encabezado de programación. Esto es útil para actualizar el firmware en el campo a través de BLE, UART, I2C, tarjetas SD, USB, etc. Sería extremadamente inconveniente pedir a los clientes que compren programadores solo para actualizar el firmware en sus dispositivos.

La razón por la que el cargador de arranque se mantiene separado es por confiabilidad. El cargador de arranque y el código de la aplicación se colocan en secciones separadas de la memoria flash, de modo que el cargador de arranque pueda borrar y volver a escribir el código de la aplicación sin cambiar nada relacionado con el código del cargador de arranque.

Si el cargador de arranque y la aplicación se mantuvieran juntos, el código del cargador de arranque tendría que copiarse en la RAM antes de que pudiera ejecutarse, ya que cualquier actualización de firmware borraría el código del cargador de arranque en la memoria flash. Si se cortara la energía con el código del cargador de arranque en la RAM y se borrara el flash, el dispositivo se bloquearía.

La nuestra es la misma razón. Están en el mismo flash, pero el cargador de arranque está alineado con el límite de borrado de flash y es lo suficientemente inteligente como para borrar solo el flash más alto que sus propias direcciones.
Entonces, para el caso de MCU STM32F103 y compiladores Keil o GCC, primero se ejecutará el archivo startup.s y luego el control se transferirá al cargador de arranque y una vez que el cargador de arranque devuelva los controles, irá al programa de aplicación main(). corrígeme si me equivoco en este entendimiento.
En algunos casos, es posible que no se pueda acceder al encabezado de programación del microprocesador sin tener que desarmar el chasis del producto, por lo que poder reprogramarlo a través del bus de comunicaciones sin hardware adicional es un factor clave para la confiabilidad.
@alt-rose El cargador de arranque y el programa de aplicación son programas compilados por separado, cada uno con su propio código de inicio y main()función. Al encender, el código de inicio del cargador de arranque se ejecuta y llama al archivo main(). El programa del cargador de arranque busca un programa de aplicación válido y luego salta al código de inicio del programa de aplicación que llama al archivo main(). El código de inicio de cada programa inicializa el entorno de tiempo de ejecución de C para el programa respectivo (es decir, inicializa variables, pila, etc.) y, por lo general, ninguno de los programas main()vuelve nunca al código de inicio.
@kkrambo Si tanto el cargador de arranque como el programa de aplicación se compilan por separado, ¿cómo llega el programa del cargador de arranque a conocer la dirección de inicio del código de inicio del programa de aplicación?
@alt-rose: de la misma manera que la CPU obtiene la dirección de inicio del cargador de arranque, no lo hace. En su lugar, la CPU especifica qué utilizará como dirección de inicio del cargador de arranque, y el cargador de arranque especifica qué utilizará como dirección de inicio del programa de aplicación.
@kkrambo Aunque comúnmente es cierto, no existe ningún requisito (ni un hecho universalmente cierto) de que un cargador de arranque esté escrito en C o en un lenguaje derivado de C con a mainen absoluto.
@alt-rose La dirección inicial del programa de aplicación la determina el desarrollador en el momento del diseño. Luego, se puede codificar en el cargador de arranque para que el cargador de arranque sepa dónde comienza la aplicación. El gestor de arranque también necesita saber dónde se encuentra el programa de la aplicación para que pueda verificar o actualizar el programa de la aplicación.
  1. Para que el proceso de carga pueda recuperarse de errores. Supongamos que hay un error de comunicación o se desconecta la alimentación durante una actualización. Si el cargador de arranque fuera parte de la aplicación que estaba actualizando, el usuario no podría volver a intentarlo sin usar un hardware especial para actualizar el cargador de arranque.

  2. Algunos microcontroladores no pueden ejecutar código desde la RAM. Si el cargador de arranque se mezcló con el resto del software, entonces no podría actualizar su software porque no puede borrar las páginas de flash que está ejecutando actualmente. La solución consiste en grabar primero el código nuevo en la segunda mitad de la memoria flash y luego saltar a ella. Luego, el nuevo código se copia a sí mismo en la primera mitad de la memoria flash. Por supuesto, la desventaja es que la grabación del flash suele ser lenta y ahora que tiene que hacerlo dos veces, el proceso de carga puede demorar hasta el doble. Además, esta solución limita el tamaño de su aplicación para que no supere la mitad de su flash total.

  3. Los cargadores de arranque bien escritos intentan verificar que existe un código válido en el dispositivo antes de intentar ejecutarlo. Si el cargador de arranque y otro código se mezclaron, ¿cómo podría estar seguro de que su rutina de validación funcionaría si todo el código no se cargara?

  4. Autenticación. Los cargadores de arranque seguros intentan verificar que la aplicación cargada coincida con una firma digital antes de ejecutarse. Pero si el cargador de arranque y otro código se mezclaron, entonces no puede controlar lo que se ejecuta en el dispositivo porque una vez que el usuario carga el código nuevo, no puede controlar lo que sucede al inicio.

Como ejemplo del punto 2, es posible que algunos microcontroladores ni siquiera tengan RAM accesible al inicio: por ejemplo, Raspberry Pi usa su GPU para cargar el cargador de arranque desde una tarjeta SD, que luego habilita el procesador ARM y la memoria.

Por lo general, están allí para permitirle actualizar su programa de aplicación principal.

Necesita algún código que sepa cómo borrar y reprogramar parte del flash interno, ese no puede ser el programa principal, ya que cuando se borra no podría reprogramarse.

El cargador de arranque permite que la MCU se comunique con otra cosa para aceptar un nuevo programa, almacenarlo y ejecutarlo después de un reinicio. Si no tenía un gestor de arranque, se necesita un programador para acceder a la memoria y poner el programa en su lugar.

Eso es practicamente todo. La MCU solo puede obtener código a través de un subsistema de programación especial (como AVRICE o JTAG) o si ya tiene un gestor de arranque en flash. Es una decisión de la aplicación en cuanto a qué tan complejo es el gestor de arranque, por ejemplo, algunos sistemas pueden cargar código desde WiFi. En MCU de gama muy baja como ATTiny, un cargador de arranque (y pines seriales) son una gran sobrecarga, por lo que siempre usa un programador.

Además de las otras respuestas correctas sobre permitir la reprogramación del firmware principal desde el cargador de arranque, otro beneficio de tener el cargador de arranque separado es que puede separar lógicamente las tareas "hacer una vez en el arranque" del código que necesita durante el tiempo de ejecución. Luego, después de que el cargador de arranque termine sus tareas de configuración inicial, el firmware principal puede desalojar el cargador de arranque con todo su código que ya no se necesita de la memoria, lo que ahorra un espacio de memoria RAM significativo. Es posible lograr esto de otras maneras, pero la división del cargador de arranque/firmware lo hace mucho más fácil en muchas arquitecturas.

En un microcontrolador, lo más probable es que el código nunca esté en la RAM, por lo que no se puede desalojar. Por supuesto, puede descartar los datos del cargador de arranque de la RAM.
@BenVoigt, depende del microcontrolador. Algunos (principalmente aquellos con flash NOR) le permitirán ejecutar directamente desde flash, pero otros (generalmente con flash NAND, que se está volviendo más común) requieren que ejecute desde RAM. A veces, ni siquiera hay un flash incorporado, y debe copiar el código de un chip flash externo a la SRAM local antes de poder ejecutar algo.

La respuesta corta es porque el software es asombroso.

Podría tener todo lo que hace el cargador de arranque como "hardware puro". Pero es mucho, mucho, mucho más fácil hacer que las tareas del cargador de arranque se escriban como software y luego se interpreten por hardware.

Estas tareas pueden implicar configurar el hardware para que se ejecute el software "real" (por ejemplo, en una Raspberry Pi (a través de @ErikF)), tener un protocolo para reemplazar el programa "real" antes de que se ejecute (verifique un pin, si ese pin se configura y luego vuelve a actualizar el programa real), o incluso configurar el entorno de software para el programa "real".

En software de menor escala, cuando ejecuta un ejecutable, el cargador de aplicaciones se mueve, hace cosas como cargar partes de sus datos en la memoria, a veces corrige direcciones, configura argumentos para principal u otras cosas globales, activa las bibliotecas proporcionadas por su sistema operativo y luego salta al inicio del _maincódigo. Algunas de estas cosas se pueden hacer con un gestor de arranque.

En un microcontrolador, algunas de las tareas que realiza un cargador de arranque podrían dividirse en el programa. El compilador de su plataforma podría inyectar automáticamente el código de "configuración" en cada ejecutable.

Pero tenerlo en el gestor de arranque significa que el mismo compilador podría funcionar en hardware diferente, ya que el gestor de arranque puede "ocultar" la diferencia entre las plataformas.

Para colmo, el hecho de que un flash del programa principal no pone en riesgo el cargador de arranque (y la capacidad de volver a flashear el programa principal), y tener un cargador de arranque no trivial es una gran cosa.

Una parte de la pregunta que no ha sido respondida hasta ahora es la diferencia entre cargadores de arranque en microcontroladores y sistemas de microprocesadores.

microcontrolador

La mayoría de los microcontroladores tienen una memoria ROM incorporada que contiene su código de programa. Cambiar este código generalmente requiere un dispositivo programador que se conecta a la interfaz de programación del microcontrolador (por ejemplo, ISP en ATMega). Pero estas interfaces de programación generalmente no son muy convenientes de usar, en comparación con otras interfaces, ya que es posible que no estén disponibles en el contexto dado. Entonces, por ejemplo, aunque casi todas las computadoras cuentan con puertos USB, la interfaz SPI necesaria para ISP es mucho más rara, y otras interfaces como la interfaz PID utilizada en ATXMega solo son compatibles con hardware de programación dedicado.

Entonces, por ejemplo, si desea actualizar el software desde una computadora normal sin ningún hardware externo, puede usar un gestor de arranque que lea desde un tipo diferente de interfaz (por ejemplo, RS232, USB o RS232 a través de USB como en Arduino) para programar el dispositivo sobre interfaces comunes.

Dicho esto, si no necesita esta funcionalidad, el cargador de arranque es completamente opcional. El microcontrolador aún puede ejecutar su código completamente sin el gestor de arranque.

Microprocesador

En un microprocesador las cosas son un poco diferentes. Si bien la mayoría de los microprocesadores cuentan con una ROM que es lo suficientemente grande para un cargador de arranque, esas ROM no son lo suficientemente grandes para contener un sistema operativo completo. Entonces, el propósito del cargador de arranque es inicializar el hardware, buscar un sistema operativo de arranque, cargarlo y ejecutarlo. Por lo tanto, el cargador de arranque es fundamental para cada arranque.

En los sistemas x86/x64, este cargador de arranque es el BIOS o el UEFI (básicamente, una versión más nueva de un BIOS).

A veces, incluso puede tener varios cargadores de arranque ejecutándose en una cadena. Por ejemplo, si tiene un sistema de arranque dual con Windows y Linux, podría terminar con lo siguiente:

  • BIOS/UEFI se inicia y encuentra GRUB instalado. Luego carga GRUB (=Gran cargador de arranque unificado)
  • GRUB encuentra algún tipo de Linux y el gestor de arranque de Windows. El usuario selecciona el cargador de arranque de Windows.
  • El gestor de arranque de Windows se inicia y encuentra Windows 7 y Windows 10 instalados. El usuario selecciona Windows 10.
  • Windows 10 finalmente arranca.

Entonces, en este caso, había tres piezas de software que pueden considerarse un gestor de arranque. Tanto GRUB como el cargador de arranque de Windows están principalmente ahí para brindarle al usuario una opción de selección de arranque más conveniente que la que les daría el BIOS/UEFI. También permite que se inicien varios sistemas operativos desde el mismo disco duro o incluso desde la misma partición.

TLDR

Entonces, mientras que en ambos sistemas el cargador de arranque hace cosas similares (ayuda al usuario a elegir qué código arrancar), ambos difieren mucho en cómo logran eso y qué hacen exactamente.

Si bien es útil distinguir los sistemas con suficiente almacenamiento no volátil de acceso aleatorio (ROM o flash) para contener todo el programa de aquellos que necesitan ejecutar código desde la RAM, existen microcontroladores de ambos tipos y microprocesadores de ambos tipos.
Por supuesto, la diferencia entre un microcontrolador y un microprocesador no es un borde rígido y algunos microcontroladores se comportan más como un microprocesador y viceversa. Por eso tomé como ejemplo el AtMega/Arduino y el x86/x64, porque se comportan de esa manera.
"Los microprocesadores cuentan con una ROM que es lo suficientemente grande para un cargador de arranque... En los sistemas x86/x64, este cargador de arranque es el BIOS o el UEFI" No. BIOS o UEFI se almacenan en la memoria flash fuera del chip. La ROM en el chip es para funciones de nivel aún más bajo, como la inicialización del microcódigo.
@Dakkaron: dibujaría la línea entre un microprocesador y un microcontrolador en función de si el chip está diseñado para usarse con fines no triviales sin nada más en el bus de direcciones. El 8031 ​​no calificaría, excepto que es funcionalmente 8051 (que definitivamente es un microcontrolador) que no se especifica que tenga algo útil en la ROM interna, pero de lo contrario estaría diseñado para usarse completamente desde el almacenamiento interno). Algo así como un RCA/CDP 1802 no calificaría a pesar de que se puede usar para manejar una placa de identificación LED...
...sin RAM ni ROM externos, porque los diseños sin RAM/sin ROM se limitan a tareas triviales. Sin embargo, algo así como un TMS 32050 que, si mal no recuerdo, tiene un cargador de arranque y unos pocos miles de palabras de RAM de 16 bits internamente calificaría como un microcontrolador; aunque muchas aplicaciones requerirían agregar más RAM, si se conecta a través de UART a otro sistema, podría servir para muchos propósitos sin nada en su bus de memoria.

Una respuesta que no se ha cubierto es la necesidad de separar las preocupaciones con respecto al código de inicio.

Esto se hace para configurar ciertas cosas como:

  • asignación de la pila C
  • leer el puntero de la pila en el registro
  • leer el contador del programa en el registro
  • Vectores de reset reset
  • cargando la segunda etapa (initramfs) en la RAM.

Esta es una aproximación muy aproximada de los pasos tomados y estoy describiendo el proceso de arranque ARM, es diferente nuevamente para x86 y otras arquitecturas.

Sin embargo, la razón principal sigue siendo la misma: la asignación de la pila C debe realizarse desde el ensamblaje.

¿Por qué el voto negativo? Esto es relevante y preciso.
yo no lo hice Pero supongo que es porque lo que describe aquí es el trabajo del código de inicio y no del gestor de arranque. (De hecho, lo más probable es que un cargador de arranque en el dispositivo mencionado tenga su propio código de inicio y luego la aplicación vuelva a hacer el mismo trabajo...) (y... en un stm32f103 no tendría un initramfs...)
Justo. He especificado que estoy hablando de código de inicio.