Experimentos-con-systemd.jpg

Como bien sabéis, de distrohopper no tengo un pelo, pero sí de experimentador para intentar solucionar problemas que me incomoden. Desde hace mucho tiempo intento resolver un fallo que afecta la hibernación (“suspensión a disco”) en mi sistema de forma bastante aleatoria y extraña: en algunas ocasiones –si fuera siempre, sería más fácil– la recuperación del sistema a partir de la imagen hibernada falla y fuerza al sistema operativo a hacer un reinicio que, obviamente, acaba en un fsck y en pérdida de datos. En el fondo es un fallo menor fácilmente evitable no usando la hibernación, pero en un portátil prescindir de esa función puede ser un poco incómodo, por lo que preferiría poder contar con ella de forma segura y consistente. Así pues, a día de hoy no puedo decir que haya podido resolverlo, aunque he hecho avances en encontrar la causa y al menos tengo un remedio que parece funcionar, pero que me deja con mal sabor de boca. Sin embargo, estos experimentos que estoy llevando a cabo me han llevado a una exploración a fondo de las herramientas de systemd que siempre criticamos todos como un caso agudo de “expansionismo”: systemd-boot, por ejemplo, entre otras.

1. De por qué un crítico con systemd se pasa a systemd

Antes de nada quiero explicar el razonamiento de por qué decidí darle una oportunidad a systemd. Resulta que en mis investigaciones sobre la hibernación me di cuenta de que la configuración predeterminada que trae Arch Linux para la suspensión del sistema a través de systemd es, técnicamente, incorrecta. Seré lo más técnico y didáctico a la vez que me sea posible.

En Linux la hibernación y la suspensión están bajo el control último del núcleo, como es fácil de suponer. Realmente, en Linux hibernar un sistema es o se supone que es algo tan sencillo como escribir dos cadenas de caracteres en dos archivos especiales que actúan como interfaz de administración de energía del núcleo:

# echo platform > /sys/power/disk
# echo disk > /sys/power/state

El hecho de que Linux –no, GNU aquí no tiene nada que ver– emule las formas de un núcleo UNIX es una maravilla: la hibernación es posible de esta manera porque en UNIX todo es un archivo. Ahora bien, esos dos archivos sólo pueden ser escritos por root, por lo que, como gestión cotidiana de los estados de suspensión, este método es francamente impráctico.

Por eso, desde siempre, lo normal en una distribución Linux es que haya interfaces que permitan que un usuario regular active la hibernación o la suspensión. El procedimiento es muy sencillo de entender: el usuario regular tiene acceso a utilizar un programa que es parte de un servicio que se ejecuta como root. Antiguamente estas interfaces eran diferentes, aunque pm-utils intentara unificarlas, pero systemd, como ya os podéis imaginar, ha tomado el relevo y es quien, a través de systemctl, se ocupa de darle la orden al núcleo. Los entornos de escritorio que utilicemos, simplemente, hacen la petición a systemd.

Así pues, en una configuración “normal” de una distribución Linux “systemd”, el procedimiento de hibernación es iniciado por systemd, quien delega al núcleo la función de congelar los procesos y dispositivos, crear la imagen del sistema, almacenarla en la partición swap y acabar dándole la orden a la BIOS a través de ACPI de que el sistema entre en lo que se conoce como Estado S4. Hasta aquí, todo parece en orden, pero el problema está que, en la configuración “normal” de Arch Linux y en otras distribuciones que he podido comprobar, en la recuperación del sistema hibernado systemd brilla por su ausencia: BIOS inicia el sistema asumiendo S4 (cosa que acelera un poco las cosas comparado a un inicio desde S0, el apagado total), UEFI busca un bootloader, éste carga el initramfs que pidamos, el cual, a su vez, debería detectar la presencia de una imagen en swap y continuar la ejecución a partir de la imagen de hibernación recuperada. Esto implica que systemd no aparece en escena hasta que se restituye el sistema anterior como otro proceso más que se “descongela”.

Mi intención era conseguir un entorno de arranque y de hibernación consistente. La razón: quizás hubiera una incompatibilidad introducida por la asimetría de que systemd controlara o al menos iniciara lo último y, en cambio, no hiciera nada en la recuperación. Para ello, tapándome la nariz, decidí que systemd tomara el control de ambos procesos, pero paso a paso. Lo primero fue cambiar el cargador de arranque o bootloader.

2. Una sorpresa interesante: systemd-boot

El sistema que había configurado utilizaba como cargador de arranque GRUB 2. GRUB en Arch Linux, por alguna razón, sin hacer con él ninguna cosa extraña, acaba produciendo unos archivos de configuración un tanto estrambóticos que, por ejemplo, dan dos entradas para el mismo sistema Arch dependiendo de si se entra presionando Enter en el menú o si se deja transcurrir la cuenta regresiva para que inicie el sistema predeterminado (y a eso sumad la entrada para el initramfs de emergencia). A mí GRUB siempre se me ha antojado innecesariamente complejo para el arranque UEFI: que la configuración se haga mediante una plantilla de configuración (/etc/default/grub) de la cual se ha de generar /boot/grub/grub.cfg mediante grub-mkconfig, además del paso de copiar el binario EFI usando grub-install, son fósiles del antiguo GRUB para BIOS/MBR.

Instalar systemd-boot –que es un fork del antiguo Gummiboot– en el sistema es tan sencillo como saber que por el simple hecho de usar systemd ya tenemos systemd-boot instalado y que sólo debemos transformarlo en el cargador de arranque. Para evitar problemas eliminé todo rastro de GRUB en /boot antes de la operación, cosa que se puede hacer sin riesgos porque el cargador de arranque deja de funcionar en cuanto ha dado paso al sistema operativo –es decir, no se mantiene ejecutado en segundo plano durante la operación del sistema–. Para instalarlo, basta con ejecutar:

# bootctl install

Pues bien, una vez hecho eso systemd-boot ya está en la partición /boot y sólo hay que configurarlo… cosa que se hace de forma trivial y, afortunadamente, de forma íntegra en la misma partición /boot. Se acabaron las plantillas de configuración y olvidarse de hacer grub-mkconfig: basta con editar dos archivos de texto triviales, usando una sintaxis sencillísima, y systemd-boot ya está funcionando.

Systemd-boot es más ligero que GRUB, pero ya sabemos que su precio es depender de systemd por el simple hecho de que la única forma (soportada) de instalar systemd-boot es usando un binario bootctl que proviene de systemd. Sin eso, systemd-boot podría ser utilizado sin tener systemd instalado en el sistema, como antiguamente no lo requería Gummiboot, ya que, como he dicho, el cargador de arranque no tiene absolutamente nada que ver con el sistema que se ejecute después, aunque se arguyera en algún momento que la integración era buena para “mantener la cadena de confianza en Secure Boot” (artículo en alemán). Dado que es un absurdo en el contexto en el que muchos tienen más de una distribución en un sistema, ¿por qué no proveerlo de forma separada, entonces? Podrían incluso mantenerle el nombre si con ello quisieran hacer un poco de “marca” en favor de systemd proporcionando un buen bootloader ligero y sencillo de configurar mantenido por un equipo de personas numeroso y estable como el que mantiene systemd.

Obviamente, después de este cambio probé en repetidas ocasiones la hibernación. Extrañamente, la hibernación funcionó el primer día sin quejas y por esto dije en el podcast que veía efectos positivos en la maniobra, pero al segundo volvió a fallar. Después de unos días de trabajo bastante intenso en la universidad, pude ponerme manos a la obra tan sólo unos días después.

3. Sueño perdido

En cuanto pude, hice una lectura concienzuda de los registros. Hay que decir que journald simplifica un poco estas cosas con las opciones para seleccionar trozos de los registros según arranques específicos en vez de fechas (con la opción -b). Fue así cómo me di cuenta de que cuando la recuperación de la imagen de hibernación falla, curiosamente no hay rastros de que se genere una imagen de hibernación. Observad los resultados de los registros:

mar 30 00:44:00 UGI systemd[1]: Reached target Sleep.
mar 30 00:44:00 UGI systemd[1]: Starting Hibernate...
mar 30 00:44:00 UGI kernel: PM: Hibernation mode set to 'platform'
mar 30 00:44:00 UGI systemd-sleep[1760]: Suspending system...
-- Reboot --
mar 30 00:44:40 UGI systemd-journald[171]: Runtime journal (/run/log/journal/) is 8.0M, max 192.0M, 184.0M free.
mar 30 00:44:41 UGI systemd-journald[171]: System journal (/var/log/journal/) is 1.7G, max 4.0G, 2.2G free.
mar 30 00:44:41 UGI systemd-journald[171]: Time spent on flushing to /var is 2.395ms for 2 entries.
[...]
mar 30 00:44:41 UGI kernel: PM: Starting manual resume from disk
mar 30 00:44:41 UGI kernel: PM: Hibernation image partition 8:3 present
mar 30 00:44:41 UGI kernel: PM: Looking for hibernation image.
mar 30 00:44:41 UGI kernel: PM: Image not found (code -22)
mar 30 00:44:41 UGI kernel: PM: Hibernation image not present or could not be loaded.
[Y sigue con un fsck por desmontado incorrecto...]

Comparad esos registros con los siguientes, de una hibernación recuperada correctamente. Para empezar, no hay un “reboot”, sino que la recuperación se considera, como debe ser, como una continuación del arranque original:

abr 03 14:16:32 UGI systemd[1]: Reached target Sleep.
abr 03 14:16:32 UGI systemd[1]: Starting Hibernate...
abr 03 14:16:32 UGI kernel: PM: Hibernation mode set to 'platform'
abr 03 14:16:32 UGI systemd-sleep[5419]: Suspending system...
abr 03 16:01:46 UGI kernel: PM: Syncing filesystems ... done.
abr 03 16:01:46 UGI kernel: Freezing user space processes ... (elapsed 0.015 seconds) done.
[...]
abr 03 16:01:46 UGI kernel: PM: Basic memory bitmaps created
abr 03 16:01:46 UGI kernel: PM: Preallocating image memory... done (allocated 363029 pages)
abr 03 16:01:46 UGI kernel: PM: Allocated 1452116 kbytes in 1.81 seconds (802.27 MB/s)
abr 03 16:01:46 UGI kernel: Freezing remaining freezable tasks ... (elapsed 0.001 seconds) done.
abr 03 16:01:46 UGI kernel: Suspending console(s) (use no_console_suspend to debug)
abr 03 16:01:46 UGI kernel: PM: freeze of devices complete after 204.458 msecs
abr 03 16:01:46 UGI kernel: PM: late freeze of devices complete after 12.534 msecs
abr 03 16:01:46 UGI kernel: PM: noirq freeze of devices complete after 1.181 msecs
abr 03 16:01:46 UGI kernel: ACPI: Preparing to enter system sleep state S4
abr 03 16:01:46 UGI kernel: ACPI : EC: EC stopped
[...]
abr 03 16:01:46 UGI kernel: PM: Creating hibernation image:
abr 03 16:01:46 UGI kernel: PM: Need to copy 361013 pages
abr 03 16:01:46 UGI kernel: PM: Normal pages needed: 361013 + 1024, available pages: 657692
[...]
abr 03 16:01:47 UGI kernel: ACPI: Waking up from system sleep state S4
[...]
abr 03 16:01:47 UGI kernel: PM: restore of devices complete after 885.632 msecs
abr 03 16:01:47 UGI kernel: PM: Image restored successfully.
abr 03 16:01:47 UGI kernel: PM: Basic memory bitmaps freed
abr 03 16:01:47 UGI kernel: Restarting tasks ... done.

Evidentemente, la primera intuición es que el fallo en la hibernación tenía una causa en la forma en que se estaba hibernando y para ello necesitaba conseguir saber cómo asegurarme que systemd-sleep hiciera lo correcto. Así pues, consulté el manual de la unidad systemd-sleep –hay que decirlo, la documentación de systemd es buena–, en el cual se hace referencia a un archivo /etc/systemd/sleep.conf en el que se pueden especificar los parámetros que se escriben en /sys/power/disk y /sys/power/state mediante unas variables.

La sorpresa fue ver que /etc/systemd/sleep.conf no existía en mi sistema… Lo creé inmediatamente para pedirle a systemd que utilizara “platform” como modo de hibernación y que pasara el parámetro “disk” al estado de energía de hibernación, pero aún así fallaba a veces. Cambié la configuración para forzar el “otro” modo de hibernación que existe en Linux, el llamado modo “shutdown”, el cual básicamente consiste en saltarse ACPI S4: se guarda la imagen de hibernación en swap y se fuerza un ACPI S0 tan bruto como apagar el sistema manualmente usando el botón de encendido (no puede hacerlo “bien” porque entonces dejaría de ser hibernación). El modo “shutdown” funciona siempre, pero la detención abrupta del disco duro no me hace ninguna gracia, razón por la cual me parecía una solución un tanto peligrosa que sólo podía aceptar si nada más funcionaba. Lo positivo de probar con el modo “shutdown” era saber que systemd-sleep estaba, efectivamente, respetando los parámetros en sleep.conf, por lo que, al menos, podía estar seguro de que el modo de hibernación no estaba sujeto a “comportamiento indefinido” (undefined behavior), una situación que todo programador sabrá que debe ser evitada a toda costa: es preferible siempre tener un comportamiento incorrecto pero definido antes que uno indefinido, porque es impredecible. Así pues, con la creación del dichoso sleep.conf perdido al menos ya había adquirido el control explícito de una fase del procedimiento.

4. Bienvenido, Sr. Poettering, a mi intramfs

Si la fase de hibernación ya estaba controlada, si el cargador de arranque también había sido cambiado, entonces ¿qué podía estar pasando? Entonces me di cuenta de que estaba interpretando incorrectamente los registros. Observad en los registros que he copiado que la hora de los eventos que marcan el inicio de la hibernación coincide con la de la recuperación por una razón obvia: esos eventos se guardan en memoria durante la hibernación porque los discos ya se han detenido y sólo se pueden escribir en los archivos de registro, en disco, una vez que el sistema se recupera. En los registros de la hibernación errónea esos eventos no se llegan ni a escribir porque no hay recuperación del sistema. Es decir, ¡el fallo está en la recuperación! 

Rebuscando información, me encontré con algo curioso: en Arch Linux existe un hook para introducir una versión reducida de systemd en el initramfs –initramfs es una imagen mínima de Linux que se carga en el sistema antes de montar la partición /–. Dicha versión reducida de systemd incluye un sistema de recuperación de imágenes de hibernación propio y diferente del “regular” proporcionado por el hook llamado resume (“continuar”), basado en pm-utils. De hecho, la inclusión de systemd en el initramfs está pensada para sustituir el paquete de utilidades GNU que se suele incluir en el initramfs para proporcionar una shell de emergencia y sustituir también udev en el initramfs, además de otras cosas.

Pues bien, para el objetivo de crear ese entorno “homogéneo” basado en systemd, el paso de dar entrada a systemd en el initramfs era evidente: si systemd-sleep se ha encargado de hibernar el sistema, systemd-boot de iniciarlo en un primer momento, tiene sentido que systemd opere desde el momento que se carga el sistema operativo. Además, me llamaban la atención ciertas advertencias de que los componentes “típicos” en un initramfs no suelen llevarse bien con systemd, por lo que parecía una buena solución para el problema concreto que estaba intentando solucionar. Hacerlo es tan sencillo como cambiar /etc/mkinitcpio.conf y generar una nueva imagen initramfs con mkinitcpio.

Por ahora la hibernación del sistema parece funcionar, además de que la velocidad de arranque ha mejorado sustancialmente; al fin y al cabo, una de las motivaciones detrás de systemd, se supone, es acelerar el arranque. Sin embargo, aún es pronto para cantar victoria. El comportamiento anterior era errático y, al igual que con una enfermedad en un organismo, se necesitan unos cuantos días para poder declarar que un fallo así se ha eliminado del sistema.

5. Unas reflexiones

Sé que ésta es posiblemente una de las entradas más largas que he escrito en el blog y que ha sido densa, por lo que no voy a entretenerme con una retahíla larga de reflexiones. Sin embargo, hay una serie de puntos que me gustaría, al menos, enunciar para evaluar qué es lo que han significado estos procedimientos.

Si systemd invade tantos elementos y tan diversos y, además, parece forzar a una homogeneidad una vez que se introduce, es absolutamente válido pensar que la naturaleza y arquitectura de un sistema Linux con systemd es bien diferente a la de un sistema pre-systemd. ¿Deberíamos hablar de un “Arch systemd/Linux” en el caso de mi portátil? Esto en principio no es malo: todos los proyectos, de una u otra manera, “guían” u “obligan” a cumplir con una serie de prerrequisitos para que funcionen, pero en el caso de systemd estos prerrequisitos consisten en la modificación sustancial de la arquitectura del sistema, a diferencia de, por ejemplo, el hecho de que KDE Plasma necesite Akonadi.

Lo que ofrece systemd no es malo. Creo que journald ha sido positivo, que como cargador de arranque systemd-boot está muy bien, que el sistema de gestión de servicios es superior a la situación que teníamos antes –pero no puedo comparar con OpenRC, por ejemplo– y otras cosas como que systemd-ntp es mejor que NTPd porque ajusta la hora mediante NTP lo antes posible en el sistema y avisa del cambio. Sin embargo, al igual que cuando hablamos de GNOME, las virtudes de systemd son siempre virtudes particulares, pero el gran vicio de systemd es cómo esa suma de virtudes particulares lo son de un monstruo incontrolable e incontrolado que mezcla en un único proyecto todo tipo de componentes cuya único denominador común es que funcionan en segundo plano.

El cambio no es malo y systemd hace unas cuantas cosas bien, pero ¿hace falta poner de patas para arriba una arquitectura probada sólo para hacer funcionar la hibernación? Y los que no usáis Arch no penséis que estéis a salvo: Arch es la vanguardia de las cosas. Lo que nos llega a nosotros primero ya le llegará al resto tarde o temprano…

 

Imagen: Chemistry, de Faris Algosaibi, publicada bajo CC-BY 2.0

Fuente: etccrond

¿Quién está en línea?

Hay 41110 invitados y ningún miembro en línea