Actualizaciones A / B integradas en Android: cómo funcionan

imagen



Hola. En SberDevices, nuestro equipo se dedica al desarrollo de varios hardware y firmware para ellos basados ​​en AOSP.



A partir de Android 8 (algunos proveedores de 7.1), el sistema tiene un nuevo mecanismo de actualización de OTA, el llamado. Actualizaciones A / B OTA sin problemas: actualizaciones sin problemas. En esta publicación describiré los principios generales de su funcionamiento, consideraré el mecanismo desde el punto de vista del desarrollador y también lo compararé con el antiguo enfoque (lo llamaremos basado en recuperación) para aplicar actualizaciones. Todo lo siguiente será cierto solo para AOSP puro, ya que la implementación específica depende del proveedor.



OTA basada en recuperación



Las actualizaciones para Android se entregan en forma de archivo zip con actualizaciones basadas en bloques. En los días de KitKat, era solo un conjunto de archivos que se copiaban en el dispositivo mediante el script incluido. No me extenderé en este modo en detalle, describiré brevemente los principios básicos de su funcionamiento:



  • el archivo zip es descargado por el sistema al dispositivo;
  • el sistema se reinicia en modo de recuperación ;
  • verificar la compatibilidad de la actualización con el dispositivo, su firma;
  • si todo está bien, se ejecuta el script de actualización del archivo zip;
  • durante la actualización, el dispositivo puede reiniciarse varias veces (por ejemplo, para actualizar el árbol del dispositivo );
  • si todo salió bien, inicie el nuevo firmware.


¿Cuáles son las desventajas de este esquema?



  • La necesidad de reservar una cantidad suficiente de memoria interna para el archivo OTA. La sección / cache se usa para esto . Algunos proveedores usan / data , pero esto es poco común. Como resultado, el usuario se queda con menos espacio (sí, las aplicaciones aún pueden usar espacio en la partición / cache , pero con algunas restricciones).
  • Reiniciar y aplicar la actualización lleva tiempo, lo que puede ser crítico para algunos tipos de dispositivos, por ejemplo, para Smart TV.
  • La interrupción del proceso de actualización puede resultar en un ciclo de arranque .
  • No hay forma de volver a la versión anterior del firmware.


Este inconveniente le permite omitir el método de actualización sin interrupciones. Vamos a ver cómo funciona.



OTA A / B sin costuras



Componentes y mecanismos clave necesarios para implementar actualizaciones A / B sin interrupciones :



  • marcado de ranura de memoria flash; 
  • interacción con el cargador, gestión del estado de las ranuras ;
  • demonio del sistema update_engine ;
  • generando un archivo zip con una actualización. Este aspecto no se considerará en este artículo.


Ranuras



El principio básico de A / B OTA es el  posicionamiento . Todas las particiones que deben actualizarse (pueden ser cualquier partición, no solo las del sistema) deben estar en dos copias o, de lo contrario, en ranuras. La implementación de Android admite 2 ranuras, que se denominan A y B, respectivamente . El sistema arranca y funciona desde la ranura actual, la segunda se usa solo en el momento de la actualización. Se agrega un sufijo con el nombre de la ranura al nombre de la sección.



A continuación se muestra una tabla que compara las dos opciones para organizar particiones en un dispositivo. Todas las particiones ranurados están marcados con el slotselect opción de montaje  de manera que el sistema puede seleccionar la ranura correcta. Dependiendo de dónde se describan, esto podría ser fstab

imagen



o dts .



Cambios en la tabla de particiones



  • B / cache ya no es necesario. Ahora la actualización se puede guardar en / data , o flashear inmediatamente en una ranura inactiva (más sobre eso a continuación). 
  • La sección de recuperación tampoco se usa más. Sin embargo,  el modo de  recuperación  todavía existe, es necesario, por ejemplo, restablecer el dispositivo a la configuración de fábrica (esto puede llevar a un equipo de  rescate ). O para los llamados. actualización manual ( descarga lateral ) a través de adbEl disco RAM de recuperación  ahora está dentro de la  partición de arranque , el kernel está compartido.
  • (android/recovery) cmdline ‑ skip_initramfs.


A primera vista, parece que dicho esquema no es óptimo, ya que es necesario asignar el doble de espacio para el sistema. Pero nos deshicimos de  / cache , lo que significa que ya hemos guardado mucha memoria. Por lo tanto, el sistema tomará un poco más de la opción de recuperación .



La principal ventaja de las actualizaciones A / B es la capacidad de  transmitir el firmware. Es lo que garantiza actualizaciones fluidas y transparentes para el usuario: para actualizar el dispositivo, basta con reiniciar en una nueva ranura. En este modo, no es necesario descargar el archivo zip por adelantado, ocupando espacio en / data . En cambio, el sistema escribe inmediatamente bloques de datos de un archivo especialmente preparado ( carga útil, ver más abajo) en cada sección de una ranura inactiva. Desde el punto de vista de la implementación, no importa si descargamos la actualización por adelantado o la transmitimos inmediatamente a la ranura.



Las tragamonedas tienen los siguientes estados:



  • activo - ranura activa, el sistema se cargará desde el siguiente reinicio;
  • de arranque : la actualización se actualizó con éxito en la ranura, se validó, las sumas de hash coincidieron, etc.
  • exitoso : el sistema pudo iniciarse con éxito en la nueva ranura;
  • no arranca : la ranura está dañada. El sistema siempre marca la ranura como no iniciable antes de iniciar el proceso de actualización.


Ambas ranuras pueden  arrancarse  y funcionar  correctamente , pero solo una está activa .



El algoritmo del gestor de arranque al elegir una ranura:

imagen

  • El cargador de arranque detecta que hay una o más  ranuras de arranque .
  • La ranura activa (o la ranura con la prioridad más alta) se selecciona de ellos.
  • Si el sistema se inicia correctamente, la ranura se marca como  correcta  y  activa .
  • De lo contrario, la ranura se marca como no arrancable y el sistema se reinicia.




Cambiar los estados de las ranuras durante la actualización:

imagen



Requisitos previos para Seamless A / B.



boot_control



Para admitir las actualizaciones A / B, el proveedor debe implementar una interfaz HAL especial: boot_control . Te permite cambiar los estados de las tragamonedas y obtener información sobre ellas. Para trabajo externo (por ejemplo, a través de adb shell ), se utiliza la utilidad: bootctl . La interfaz se utiliza como medio de comunicación entre el sistema operativo y el cargador de arranque.



update_engine



El componente principal de todo el circuito A / B. Maneja la descarga, la transmisión de actualizaciones, la verificación de firmas y más. Cambia los estados de las ranuras mediante boot_control . Le permite controlar el proceso de actualización del dispositivo: pausar, reanudar, cancelar.

El componente llegó a Android desde ChromeOS, donde ha estado en uso por un tiempo. AOSP mantiene update_engine como un ensamblaje de carga lateral estática . Es ella quien se usa en la recuperación , porque este modo no admite enlaces dinámicos.



El proceso de este componente se puede dividir en los siguientes pasos:



  • cargando la actualización en la ranura. Puede descargar ambos desde un paquete descargado previamente con una actualización, o directamente a través de Internet a través de http / https. Durante la descarga, se verifica la firma, la clave pública ya está en el dispositivo (/system/etc/update_engine/update-payload-key.pub.pem);
  • verificación de la actualización descargada y comparación de sumas hash;
  • ejecución de scripts posteriores a la instalación


Estructura del paquete de servicios:



2009-01-01 00:00:00 .....          360          360  META-INF/com/android/metadata
2009-01-01 00:00:00 .....          107          107  care_map.txt
2009-01-01 00:00:00 .....    384690699    384690699  payload.bin
2009-01-01 00:00:00 .....          154          154  payload_properties.txt
2009-01-01 00:00:00 .....         1675          943  META-INF/com/android/otacert


  • care_map.txt: utilizado por update_verifier (ver más abajo);
  • payload_properties.txt: contiene hashes y tamaños de datos dentro de la carga útil ;
  • payload.bin - paquete de actualización, contiene bloques de todas las secciones, metadatos , firma.


update_engine_client



Cliente para administrar el demonio update_engine . Puede ser llamado directamente por el proveedor para aplicar la actualización.



update_verifier



Utilidad para verificar la integridad del sistema en el primer inicio (ranura con la bandera  activa , pero aún no  exitosa ). El control de integridad se implementa mediante el módulo del kernel dm-verity . Si la verificación es exitosa, la utilidad marca la ranura actual como  exitosa . De lo contrario, el sistema se reiniciará en la ranura anterior. Solo se verifican los bloques especificados en el archivo care_map.txt .



UpdateEngineApi



Existe una API de Java para implementar servicios de actualización de proveedores . También hay un ejemplo de implementación de un servicio de este tipo.



Veamos un ejemplo de compilación de actualización A / B en AOSP. Para hacer esto, edite el Makefile de la plataforma de destino:



#  A/B
AB_OTA_UPDATER := true
#    :
AB_OTA_PARTITIONS := boot system vendor
#  
PRODUCT_PACKAGES := update_engine update_engine_client update_verifier
#  recovery
TARGET_NO_RECOVERY := true
#,       cache:
#BOARD_CACHEIMAGE_PARTITION_SIZE := ...
#BOARD_CACHEIMAGE_FILE_SYSTEM_TYPE := ...


Después de llamar a make otapackage, obtenemos un archivo zip con la actualización. De esta forma, ya es adecuado para el modo de carga lateral . Podemos reiniciar en recuperación y llamar a adb sideload ota.zip . Este método es conveniente para depurar.



La aplicación de una actualización desde dentro de un sistema de producción suele ser específica del proveedor. La forma más sencilla es subir payload.bin a un servidor http y llamar a update_engine_client directamente .



Ejemplo de llamada:



update_engine_client \
--payload=http://path/to/payload.bin \
--update \
--headers=" \
FILE_HASH=ozGgyQEddkI5Zax+Wbjo6I/PCR8PEZka9gGd0nWa+oY= \
FILE_SIZE=282344983 \
METADATA_HASH=GLIKfE6KRwylWMHsNadG/Q8iy5f786WTatvMdBlpOPg= \
METADATA_SIZE=26723"


El contenido del archivo payload_properties.txt se pasa al parámetro de encabezados . En logcat, puede ver el progreso de la actualización. Si pasa el interruptor --follow , el progreso se duplicará en stdout .



Conclusión



Las ventajas del nuevo mecanismo de actualización son obvias:



  • la actualización del sistema se realiza en segundo plano sin interrumpir el trabajo del usuario. Sí, aún necesitará reiniciar (en una nueva ranura), pero será  mucho  más rápido que reiniciar en recuperación para aplicar la actualización;
  • la probabilidad de que se produzca un ciclo de arranque se minimiza (nadie es inmune a los errores en la implementación). El proceso de actualización puede interrumpirse, no afectará a la ranura activa de ninguna manera;
  • es posible volver a la versión de firmware anterior. Incluso si por alguna razón la actualización no tuvo éxito, el sistema simplemente volverá a la versión anterior;
  • gracias a la transmisión, el dispositivo se actualizará más rápido;
  • dependiendo de la implementación, puede excluir completamente al usuario del proceso de actualización.


De las desventajas, destacaría dos puntos:



  • A / B OTA se vuelve dependiente de la distribución actual del disco, porque la actualización se produce mientras el sistema está en ejecución. Es decir, resulta imposible ejecutar la actualización con las particiones cambiadas;
  • la relativa complejidad de la implementación.


Y, sin embargo, en mi opinión, los pros superan. Por cierto, en nuestro dispositivo recientemente anunciado estamos usando actualizaciones A / B OTA.



All Articles