Linux From Scratch XXXV: Creando nuestro propio initrd

En el artículo anterior, hemos dejado compilados los paquetes necesarios para poder crear un initrd que nos permita arrancar desde USB. En este artículo vamos a ver cómo hacer esto y podremos dar por finalizado el objetivo principal: Tener un Linux From Scratch básico capaz de arrancar por USB.

Comprobando el kernel

Lo primero que tenemos que hacer es asegurarnos de que tenemos el kernel compilado con todas las opciones que vamos a necesitar. Por defecto ya están activadas las opciones correctas, pero en caso de duda podemos mirar la configuración y recompilar el kernel.

[~/lfs]$ sudo lfs linux-3.5.2-2
[root@corellia:/]# cd sources/
[root@corellia:/sources]# tar xvf linux-3.5.2.tar.xz
[root@corellia:/sources]# cd linux-3.5.2
[root@corellia:/sources/linux-3.5.2]# make mrproper
[root@corellia:/sources/linux-3.5.2]# cp /boot/config-3.5.2 .config
[root@corellia:/sources/linux-3.5.2]# make menuconfig

Recordemos que una de las cosas que habíamos hecho cuando compilamos el kernel, era copiar el fichero de configuración a /boot/config-3.5.2. Gracias a esto, ahora podemos volver a ver la configuración que habíamos puesto en ese momento. Las opciones que tienen que estar marcadas para que todo funcione son:

  • En “General setup“, marcar “Initial RAM filesystem and RAM disk (initramfs/initrd) support“.
  • En “Device Drivers/Block devices“, marcar “Loopback device support” y “RAM block device support“.

Cuando os aseguréis de que tenéis marcadas las opciones correctas, podéis compilar e instalar el kernel si es necesario. Las instrucciones para ello ya las he dado anteriormente.

Creando el initrd

Un initrd es simplemente un fichero que contiene empaquetado un pequeño sistema de ficheros básico con las utilidades y scripts necesarios para continuar con el proceso de arranque hasta que el kernel sea capaz de lanzar el proceso init del disco duro.

El libro Beyond Linux From Scratch explica un método para crear un initrd. Lo que hace es copiar los binarios y las librerías necesarias de nuestro sistema LFS tal cual lo tenemos instalado. Si bien esta es una posible forma de hacerlo, no me ha gustado nada ya que repetir parte de los ficheros que ya tenemos en el disco duro. Me parece una solución muy poco elegante y más compleja de lo necesario. Requiere copiar todas las dependencias de los binarios que vamos a meter y, con seguridad, ocupará más espacio que otros métodos.

Por suerte, gentoo ofrece un método alternativo para crear un initrd que usa busybox para ello. Busybox ocupa bastante menos y se puede compilar con las características justas que necesitamos. Además, usar busybox no interfiere para nada con nuestro sistema, ya que el initrd se descarta en cuanto el kernel ha cargado la partición raíz.

Por esta razón he optado por usar el método de gentoo, que me ha gustado mucho más, así que vamos a crear nuestro initrd con busybox. Lo primero es descargar su código fuente. Por suerte, una de las cosas que hemos hecho en el anterior artículo es instalar wget, de manera que ahora podemos usarlo desde el chroot.

[~/lfs]$ sudo lfs initrd-3.5.2
[root@corellia:/]# cd sources/
[root@corellia:/sources]$ wget http://busybox.net/downloads/busybox-1.21.0.tar.bz2

Busybox es un programa que integra multitud de comandos de linux en un sólo binario. Es un ejecutable enorme que hace las funciones de otros comandos según el nombre que se use al lanzarlo. Suele ser útil para sistemas embebidos y para hacer cosas similares a lo que estamos haciendo nosotros. 🙂 La ventaja de usar busybox frente a los comandos habituales es que ocupa mucho menos espacio y no tiene dependencias (sobre todo si lo compilamos estáticamente). El inconveniente es que los comandos que trae tienen muchas menos opciones que los normales, aunque ya tienen todo lo que vamos a usar.

Como normalmente no necesitamos todos los comandos que vienen con busybox, el paquete nos permite configurar el código fuente con un menú similar al del kernel de linux, para que seleccionemos los comandos y las características que queremos tener. De esa manera podemos afinarlo y compilar sólo lo que necesitamos.

[root@corellia:/sources]# tar xvf busybox-1.21.0.tar.bz2
[root@corellia:/sources]# cd busybox-1.21.0
[root@corellia:/sources/busybox-1.21.0]# make menuconfig

Ahora una pequeña guía de las opciones que tenéis que seleccionar para que quede todo perfecto. Se parece a compilar el kernel, pero es bastante más fácil:

  • En “Busybox Settings“, desmarcar todo en “General Configuration“, “Debugging Options“, y “Busybox Library Tuning“. En “Build Options” dejar solamente “Build BusyBox as a static binary“.
  • Desmarcar todo en “Archival Utilities“, “Console Utilities“, “Debian Utilities“, “Editors“, “Finding Utilities“, “Init Utilities“, “Login/Password Management Utilities“, “Linux Ext2 FS Progs“, “Linux Module Utilities“, “Miscellaneous Utilities“, “Networking Utilities“, “Print Utilities“, “Mail Utilities“, “Process Utilities“, “Runit Utilities” y “System Logging Utilities“.
  • En “Coreutils” desmarcar todo excepto “cat“, “echo” y “sleep“. Las opciones de dentro no son necesarias.
  • En “Linux System Utilities” dejar solamente “mdev” sin las opciones de dentro, “mount” con la opción “Support specifying devices by label or UUID“, “switch_root“, “umount” y, dentro de “Filesystem/Volume identification“, dejar solamente “Ext filesystem“.
  • En “Shells” marcamos solamente “ash” con la opción “Optimize for size instead of speed“.

Mientras os paseáis por las opciones podéis echar un vistazo a todo lo que tiene. Es realmente impresionante. Es un linux entero en un sólo paquete. 🙂

Una vez seleccionadas las opciones, salimos del menuconfig y ya podemos compilar.

[root@corellia:/sources/busybox-1.21.0]# make

Ahora viene lo importante. Para crear el initrd tenemos que tener una estructura de directorios adecuada con un script llamado init en su raíz y las utilidades que necesitemos en el directorio bin. Estas utilidades serán en realidad enlaces a busybox, ya que ese es uno de los métodos que usa busybox para saber qué comando ejecutar.

[root@corellia:/sources/busybox-1.21.0]# mkdir -p ../initrd/{bin,mnt/root,proc,sys}
[root@corellia:/sources/busybox-1.21.0]# cp busybox ../initrd/bin
[root@corellia:/sources/busybox-1.21.0]# for c in mount umount cat sleep echo mdev switch_root; do ln -s busybox ../initrd/bin/$c; done
[root@corellia:/sources/busybox-1.21.0]# vim ../initrd/init

El fichero ../initrd/init será el que haga las veces de proceso init, o sea, el proceso que el kernel va a lanzar nada más cargarse. Este fichero va a ser un script que detecte los USB, monte el sistema de ficheros raíz y luego pase el control al verdadero init que estará en la partición montada.

#!/bin/busybox sh

mount -t proc none /proc
mount -t sysfs none /sys

echo /bin/mdev > /proc/sys/kernel/hotplug
mdev -s

init=/sbin/init
for cmd in $(cat /proc/cmdline); do
    case $cmd in
        init=*)
            init=${cmd#init=}
            ;;
        root=*)
            dev=${cmd#root=}
            while ! mount -o ro ${dev} /mnt/root; do sleep 1; done
            ;;
    esac
done

umount /proc
umount /sys

exec switch_root /mnt/root $init

Paso a explicar el script. Es tan sencillo como he podido hacerlo:

  • La linea 1 es el shebang para hacer que el kernel lance el script con busybox. El parámetro sh le dice a busybox que se comporte como un shell y ejecute el script. Recordemos que busybox puede simular cualquier otro comando para el que haya sido compilado.
  • Las lineas 3  y 4 montan los sistemas de ficheros /sys y /proc que el resto de los comandos del script necesitan para funcionar.
  • Las lineas 6 y 7 son las que detectan los dispositivos USB. La 6 configura el comando mdev de busybox para que el kernel lo use para detectar dispositivos. La 7 hace que mdev configure todos los dispositivos detectados.
  • De la linea 9 a la 20 hay un bucle que parsea los parámetros pasados al kernel. Estos parámetros se leen a través de /proc/cmdline en la linea 10 y se ignoran todos excepto init= y root=.
  • La linea 9 establece un valor por defecto para el nombre del proceso init que vamos a lanzar más adelante. Si el usuario no especifica lo contrario, el que lanzaremos será /sbin/init que es el estándar.
  • En la linea 13 se guarda el valor de init= para usarlo más tarde cuando lancemos el verdadero proceso init. Esto permite especificar el init que queramos en los parámetros del gestor de arranque.
  • La linea 16 lee el valor de root=, que es el que tiene el UUID de la partición raiz. A continuación, en la linea 17, se monta dicha partición. Como el kernel puede tardar un tiempo en detectar los USB, hacemos un bucle que reintente varias veces con una pausa de 1 segundo hasta que haya conseguido montar la partición. Esto es mejorable, ya que hacer un bucle como este puede hacer que el script se quede colgado si no hemos puesto bien el UUID, pero lo he dejado de la forma más simple posible.
  • Las lineas 22 y 23 desmontan los directorios /proc y /sys que habíamos montado anteriormente y que ya no vamos a necesitar.
  • Por último, la linea 25 cambia el sistema de ficheros del initrd por el de la partición raíz y lanza el verdadero init cuya ruta y nombre hemos dejado en la variable $init. Este init realiza el resto del proceso de arranque.

Ahora damos permisos de ejecución al script, lo empaquetamos con cpio dejándolo en /boot/initrd.cpio.gz y creamos e instalamos nuestro paquete. El nombre que pongamos al initrd no importa. Puede ser cualquiera. Usaremos luego el mismo nombre para referenciarlo desde el fichero de configuración del gestor de arranque.

[root@corellia:/sources/busybox-1.21.0]# chmod +x ../initrd/init
[root@corellia:/sources/busybox-1.21.0]# cd ../initrd
[root@corellia:/sources/initrd]# find . -print0 | cpio --null -ov --format=newc | gzip -9 > /boot/initrd.cpio.gz
[root@corellia:/sources/initrd]# exit
[~/lfs]$ sudo lfsinst initrd-3.5.2.txz

Configurando el gestor de arranque

Ya sólo queda entrar en el chroot y modificar la configuración del gestor de arranque para que le pase al kernel el initrd que hemos creado. pondré los ejemplos para syslinux y para grub.

Con syslinux

[~/lfs]$ sudo lfs
[root@corellia:/]# vim /boot/extlinux/extlinux.conf

No voy a poner el fichero completo. Simplemente hay que cambiar las lineas correspondientes a nuestro kernel por las que os indico a continuación. Ojo con poner bien el UUID correspondiente a la partición del USB en la que está instalado LFS. Probablemente ya lo tengáis puesto y sólo os falte añadir la linea que empieza por INITRD.

LABEL lfs
     LINUX ../vmlinuz-3.5.2-lfs-7.2
     APPEND root=UUID=dd02b529-840e-4ffa-9433-e2709dffd81c ro
     INITRD ../initrd.cpio.gz

Con grub

[~/lfs]$ sudo lfs
[root@corellia:/]# vim /boot/grub/grub.cfg

Al igual que en ejemplo de syslinux, pondré también sólo la sección que cambia. Probablemente con añadir la linea initrd sea suficiente. Y recordad verificar que ponéis bien el UUID, ya que de lo contrario no arrancará.

menuentry "GNU/Linux, Linux 3.5.2-lfs-7.2" {
        linux   /boot/vmlinuz-3.5.2-lfs-7.2 root=UUID=dd02b529-840e-4ffa-9433-e2709dffd81c ro
        initrd  /boot/initrd.cpio.gz
}

Probamos a arrancar

Una vez hecho esto sólo nos falta salir del chroot.

[root@corellia:/]# exit

Ahora ya podemos arrancar con ese pendrive y comprobar el resultado. En mi caso ha funcionado perfecto. ¡Por fin tengo mi LFS arrancando desde USB! 🙂 He probado a arrancar con él en varias máquinas y ha funcionado, aunque en algunas no me ha reconocido parte de los dispositivos, ya que no he compilado el kernel para todo ese hardware, pero por lo menos funciona.

EOF

Anuncios