¿Cómo compilar un kernel nuevo en Ubuntu 20.04?

El kernel actual de Ubuntu 20.04 tiene un problema catastrófico si utilizas ZRAM, como describo en zram: Decompression failed! Lo fácil sería esperar a que Ubuntu publique una actualización, pero eso puede llevar semanas. Con eventos catastróficos diarios, no me puedo permitir esperar.

Necesito compilar mi propio kernel Linux. Lo triste del asunto es que la cosa no es ni sencilla ni está bien documentada. En la web de Ubuntu y por todo internet hay infinidad de tutoriales describiendo el proceso, pero son para versiones antiguas del sistema operativo y ya no funcionan. La verdad es que he perdido bastante tiempo leyendo y experimentando, con poco éxito y bastante frustración. Mis necesidades son bastante razonables:

  • Idealmente, compilar el mismo kernel oficial de Ubuntu, pero con la opción CONFIG_PGTABLE_MAPPING=y desactivada. Esto me permitiría reemplazar el kernel sin tener que preocuparme de paquetes y otras historias, y podría reutilizar también el initrd normal del sistema operativo. Eso me daría compatibilidad con todo lo que necesito, incluyendo ZFS y VirtualBox. Además, Ubuntu tiene parches en su kernel que no están disponibles en las versiones de kernel.org.

    Tendría que tener cuidado en recompilar un kernel nuevo cada vez que Ubuntu publicase una actualización que no incluyese la corrección, pero me parecía asumible.

    Ubuntu publica detalles sobre cómo descargar y compilar sus kernels, pero es un proceso complejo y que no he conseguido hacer funcionar satisfactoriamente.

  • Mis necesidades son algo delicadas si en vez de intentar reutilizar componentes Ubuntu, compilo un kernel genérico por mi cuenta:

    • Debe tener compatibilidad con ZFS.
    • Debe tener compatibilidad con VirtualBox. Esto supone que los módulos VirtualBox se recompilen automáticamente cuando se publica una actualización del software, lo que no es trivial.

    El plan es utilizar este kernel personal hasta que Ubuntu publique una actualización solucionando el problema.

Como he dicho antes, estudié docenas de páginas web con poco éxito. Casi toda la información está anticuada, es incompleta o no me ayuda en lo referente a integrar ZFS. Al final elaboré el siguiente procedimiento. No es elegante ni trivial de seguir, pero mi tiempo es limitado y cubrió mis necesidades:

  • Instalo las herramientas de compilacion de kernel disponibles en GitHub: mtompkins / linux-kernel-utilities.

  • En un terminal, lanzo el script compile_linux_kernel.sh. El programa nos mostrará una lista de kernels disponibles en kernel.org. Ubuntu 20.04 utiliza un kernel de la rama 5.4, así que me bajo la última versión de esa rama. En concreto la 5.4.80.

  • El script descarga el kernel indicado.

  • Una vez que el script ha descargado el kernel, abrirá un entorno gráfico para configurar las opciones de compilación.

    El script utilizará la configuración del kernel actualmente en funcionamiento, que es precisamente lo que nos interesa (casi).

    Queremos desactivar la opción CONFIG_PGTABLE_MAPPING. Buscamos PGTABLE_MAPPING con la opción de "buscar" y la desactivamos:

    kernel-20201126.png
  • Tras desactivar PGTABLE_MAPPING, hacemos "Save" y "Quit".

  • En el terminal nos pondrá lo siguiente:

    [+] Cleaning the workspace after configuration . . .
     \_ Cleaned
    
    [?] Would you like to build the kernel now? This will take a while (y/N):
    

    Esperamos. Antes de compilar el kernel tenemos que añadirle el soporte ZFS.

  • Vamos a la web OpenZFS on Linux y descargamos la versión 0.8.3 de ZFS, que es la versión que usa Ubuntu 20.04.

  • Abrimos un terminal nuevo y vamos al directorio ZFS que hemos bajado. Ahí dentro hacemos:

    jcea@jcea:/tmp/ram/zfs$ ./autogen.sh
    jcea@jcea:/tmp/ram/zfs$ ./configure --enable-linux-builtin \
                                        --with-linux=/tmp/ram/linux-kernel-utilities/Build_dic14_17-07-08$ cd Build_nov26_11-07-08/linux-5.4.80/ \
                                        --with-linux-obj=/tmp/ram/linux-kernel-utilities/Build_dic14_17-07-08$ cd Build_nov26_11-07-08/linux-5.4.80/
    jcea@jcea:/tmp/ram/zfs$ ./copy-builtin /tmp/ram/linux-kernel-utilities/Build_dic14_17-07-08$ cd Build_nov26_11-07-08/linux-5.4.80/
    
  • En ese terminal vamos a /tmp/ram/linux-kernel-utilities/Build_dic14_17-07-08/linux-5.4.80/ y escribimos make oldconfig. Se irán mostrando las opciones ya configuradas y se parará en las nuevas que, en este caso, se refieren al soporte ZFS que acabamos de integrar. Lo activamos y lo configuramos para que se compile dentro del kernel. No lo configuro como módulo para evitar líos con el initrd. Dado que este kernel será de uso temporal hasta que Ubuntu se ponga las pilas, esto no es problema.

  • Cerramos ese terminal y volvemos al anterior, el que nos preguntaba:

    [+] Cleaning the workspace after configuration . . .
     \_ Cleaned
    
    [?] Would you like to build the kernel now? This will take a while (y/N):
    

    Le decimos y.

  • El script empezará a compilar el kernel. El proceso puede ser largo, en función de la potencia de nuestra CPU. Es mejor ponerse a hacer cualquier otra cosa, ya que el script mostrará una notificación cuando el proceso termine; no hace falta que estemos pendientes.

    Un buen detalle es que el script paralelizará la compilación en función de cuántas CPUs tengamos.

    Para evitar que una compilación tan larga y exigente penalice el rendimiento interactivo de la máquina, lo más fácil es haber lanzado el script con nice, aunque el proceso se alargará un poco.

    En mi portátil la compilación tarda 77 minutos, aunque tengo bastante actividad adicional en el ordenador.

  • Para instalar el kernel nuevo, hacemos:

    jcea@jcea:~$ cd /tmp/ram/linux-kernel-utilities/Build_nov26_11-07-08/
    jcea@jcea:/tmp/ram/linux-kernel-utilities/Build_nov26_11-07-08/$ sudo dpkg -i *.deb
    jcea@jcea:/tmp/ram/linux-kernel-utilities/Build_nov26_11-07-08/$ cd linux-5.4.80
    jcea@jcea:/tmp/ram/linux-kernel-utilities/Build_nov26_11-07-08/linux-5.4.80$ sudo make modules_install
    

    Nota

    El script compile_linux_kernel.sh tiene la opción de instalar el kernel recién compilado. Yo no lo he hecho directamente porque necesitaba hacer unos ajustes previos.

  • Ahora hacemos los ajustes necesarios en /boot/grub/grub.cfg y reiniciamos el ordenador.

Este procedimiento genera una configuración que altera poco el sistema operativo. En particular, siguen entrando actualizaciones de kernel oficiales de Ubuntu y puedo desinstalar mi kernel personalizado con total facilidad.

Actualización 20201214: Llevo usando este kernel casi un mes y funciona correctamente. Ya no tengo problemas con ZRAM. Mientras tanto, Ubuntu ha publicado varias actualizaciones del kernel, pero aún no ha desactivado CONFIG_PGTABLE_MAPPING=y.

Para saber si una actualización de kernel desactiva CONFIG_PGTABLE_MAPPING=y, podemos buscar PGTABLE_MAPPING en /boot/config-5.4.0-58-generic (obviamente debemos comprobar el fichero del último kernel instalado).

Si tienes curiosidad por conocer la equivalencia (aproximada, porque Ubuntu tiene parches propios) entre los kernel Ubuntu y los kernels de kernel.org, podemos verla en Ubuntu to Mainline kernel version mapping.