Traducido del original de M.Tim Jones (mtj@mtjones.com) y que se pueden encontrar en el servicio de documentación técnica de IBM.

Una visita guiada desde el Registro Maestro de Arranque (Master Boot Record) hasta la primera aplicación en el espacio de usuario

31 May 2006

El proceso de arranque de un sistema Linux® consiste de un número de fases. Pero tanto si estás arrancando un escritorio estándar x86 como si tu objetivo es el altamente empotrado PowerPC®, mucho del flujo es sorprendentemente similar. Este artículo explora el proceso de arranque de Linux desde su fase de lanzamiento hasta el inicio del primer espacio de usuario de una aplicación. Por el camino, veremos otros diversos temas relacionados con el arranque tales como cargadores de arranque, descompresión del núcleo, el disco RAM inicial, y otros elementos del arranque Linux.

En los viejos tiempos, el proceso de arranque de una computadora significaba alimentar una cinta de papel que contenía el programa de arranque o manualmente cargar un programa de arranque usando el panel frontal de interruptores de direcciones/datos/control. Los equipos actuales vienen equipados con utilidades que simplifican el proceso de arranque, pero que no lo hacen necesariamente simple.

Empecemos con una vista de alto nivel de un arranque Linux de manera que tengamos una vista amplia. Despues revisaremos que sucede en cada uno de los pasos individuales. Las referencias a las fuentes durante el proceso ayudarán a navegar el árbol del núcleo e investigar en profundidad.

Visión general

La figura 1 nos da una vista de pájaro:

Figura 1. El proceso de arranque Linux a vista de pájaro

Figura 1. El proceso de arranque Linux a vista de pájaro

Cuando un sistema es arrancado por primera vez, o se reinicia, el procesador ejecuta código en una ubicación bien conocida. En un ordenador personal (PC), esta ubicación está en el sistema básico de entrada/salida (BIOS), que se almacena en una memoria flash o en la placa base. La unidad central de procesamiento (CPU) en un sistema empotrado, invoca el vector de reinicio para iniciar un programa en una dirección conocida de la flash o ROM. En ambos casos, el resultado es el mismo. Debido a que los PCs ofrecen mucha flexibilidad, la BIOS debe determinar que dispositivos son candidatos para el arranque. Entraremos en más detalle en esto posteriormente.

Cuando un dispositivo de arranque es encontrado, la primera fase del cargador de arranque se carga en la RAM y se ejecuta. Este cargador es menor de 512bytes de longitud (un sólo sector), y su trabajo es cargar la segunda fase del cargador de arranque.

Cuando la segunda fase del cargador está en la RAM y ejecutándose, comúnmente se muestra una pantalla de presentación, y Linux y un disco inicial RAM opcional (para un sistema de ficheros temporal) son cargados en memoria. Cuando las imágenes están cargadas, la segunda fase del cargador de arranque pasa el control a la imagen del núcleo, que es descomprimido e inicializado. En este punto, la segunda fase del cargador chequea el hardware del sistema, enumera los dispositivos hardware conectados, monta el dispositivo raíz, y después carga los módulos necesarios del núcleo. Al completarse, el primer programa en el espacio de usuario (init) se inicia, entonces se realizan inicializaciones del sistema a alto nivel.

Esto resume el proceso de arranque. Ahora entremos en profundidad explorando algunos de  los detalles de este proceso.

Inicio del sistema

La fase de inicio del sistema depende del hardware desde el que Linux está siendo arrancado. En una plataforma empotrada, se usa un entorno de secuencia de arranque cuando el sistema se enciende, o se reinicia. Algunos ejemplos serían U-Boot, RedBoot y MicroMonitor (de Lucent). Las plataformas empotradas comúnmente se distribuyen con un monitor de arranque. Estos programas residen en una región especial de la memoria flash en el hardware objetivo y proveen los medios para descargar una imágen de núcleo de Linux en la memoria flash para a continuación ejecutarla. Además de tener la habilidad de almacenar y arrancar una imagen Linux, estos monitores de arranque realizan algunos test a nivel de sistema e inicialización de hardware. En un sistema empotrado, estos monitores de arranque comúnmente cubren tanto la primera como la segunda fase del proceso de arranque.

En un PC, el arranque de Linux empieza en la BIOS en la dirección 0xFFFF0 . El primer paso de la BIOS es el auto text de encendido (POST, por sus siglas en inglés). El trabajo del POST es ejecutar un chequeo del hardware. El segundo paso de la BIOS es la enumeración de los dispositivos locales y su inicialización.

Dados los diferentes usos de las funciones de la BIOS, ésta se encuentra formada de dos partes: el código de POST y los servicios en tiempo de ejecución. Tras el completado del POST, es sacado de la memoria, pero los servicios de tiempo de ejecución permanecen y están disponibles para el sistema operativo objetivo.

Para el arranque de un sistema operativo, el proceso de ejecución de la BIOS busca dispositivos que sean tanto activos como arrancables en el orden de preferencia definido por la configuración en el semiconductor complementario de óxido metálico (CMOS, por sus siglas en inglés). Un dispositivo de arranque puede ser un disquete, un CDROM, una partición en un disco duro, un dispositivo de red o una memoria USB.

Comúnmente, Linux es arrancado desde un disco duro, donde el registro maestro de arranque (MBR, por sus siglas en inglés) contiene el cargador primario de arranque. El MBR es un sector de 512-byts, ubicado en el primer sector del disco (sector 1 del cilindro 0, cabezal 0). Tras la carga en la RAM del MBR, la BIOS le cede el control.

Fase 1 del cargador de arranque

El cargador primario de arranque que reside en el MBR es una imagen de 512 byts que contiene tanto el código de programa como una pequeña tabla de particiones (ver Figura 2). Los primeros 446 bytes son el cargador primario de arranque, que contienen tanto el código ejecutable como los mensajes de texto de error. Los siguientes 64 bytes son la tabla de particiones, que contienes un registro por cada una de las cuatro particiones (16 bytes cada una). El MBR termina con 2 bytes definidos como el número mágico (0xAA55), que sirve para el chequeo de validación del MBR.

Figura 2. Anatomía del MBR

La función del cargador primario de arranque es encontrar y cargar el cargador secundario de arranque (fase 2). Lo hace buscando en la tabla de particiones una partición activa. Cuando encuentra una, escanea el resto de particiones en la tabla para asegurarse que están inactivas. Cuando lo ha verificado, el registro de arranque de la partición activa es leído del dispositivo en la RAM y ejecutado.

Fase 2 del cargador de arranque

El cargador de arranque secundario, o segunda fase, podría ser llamado más adecuadamente cargador del núcleo. La tarea en esta fase es cargar el núcleo de Linux y un disco RAM de inicio opcional.

La primera y segunda fases del cargador de arranque combinadas son el llamado Cargador de Linux (LILO, por sus siglas en inglés) o Grande y Unificado Cargador de arranque ( GRand Unified Bootloader (GRUB) ) en los entornos de PCx86. Ya que LILO tiene algunas desventajas que fueron corregidas en GRUB, tomaremos este segundo como referencia.

Lo mejor respecto a GRUB es que incluye conocimiento de los sistemas de ficheros de Linux. En lugar de usar sectores en crudo del disco, como hace LILO, GRUB puede cargar el núcleo de Linux de un sistema de ficheros ext2/3/4. Consigue esto convirtiendo el proceso de dos fases del cargador de arranque en un proceso de tres fases. La fase 1 (MBR) arranca una fase 1.5 que entiende las particularidades del sistema de ficheros que contienen la imagen del núcleo de Linux. Dos ejemplos son reiserfs_stage1_5 (para cargar desde un sistema de ficheros Reiser con registro) o e2fs_stage1_5 (para cargar de un sistema de ficheros ext2/3). Una vez que la fase 1.5 del cargador está cargada y ejecutándose, la fase 2 puede ser lanzada.

Con la fase 2 cargada, GRUB puede, a petición, mostrar un listado de núcleos disponibles (definidos en /etc/grub.conf, con enlaces sencillos desde /etc/grub/menu.lst). Se puede seleccionar un núcleo e incluso particularizarlo con parámetros de núcleo. Opcionalmente, se puede usar una linea de comandos para un mayor control manual sobre el proceso de arranque.

Una vez la segunda fase está en memoria, el sistema de ficheros es consultado, y la imagen de núcleo por defecto junto con la imagen de initrd son cargados en memoria. Con las imágenes en memoria, la fase 2 invoca la imagen del núcleo.

Núcleo (Kernel)

Con la imagen del núcleo en memoria y el control otorgado por la fase 2 del cargador de arranque, empieza la fase del núcleo. La imagen del núcleo no es un ejecutable, sino una imagen comprimida del ejecutable real. Típicamente, es una zImage (imagen comprimida para ocupar menos de 512KB) o una bzImage (gran imagen comprimida, mayor que 512KB), y que previamente se han comprimido con zlib. Al inicio de esta imagen del núcleo hay una rutina que hace una serie mínima de configuraciones de hardware antes de descomprimir el núcleo contenido en la imagen y cargarlo en la memoria. Si hubiera una imagen de disco RAM inicial, esta rutina la mueve a la memoria y se lo apunta para un uso posterior. Finalmente, se invoca el núcleo y empieza el arranque del mismo.

Figura 3. Flujo de funciones del proceso de carga del núcleo

Cuando la bzImage (para el ejemplo, una imagen i386) es invocada, se empieza en ./arch/i386/boot/head.S en la rutina de inicio de ensamblado (en la figura 3 se puede visualizar mejor el flujo). Esta rutina realiza algo de configuración básica de hardware e invoca a la rutina startup_32 de  ./arch/i386/boot/compressed/head.S. Esta rutina configura un entorno básico (pila, etc.) y limpia el BSS (Block Started by Symbol). El núcleo se descomprime mediante una llamada a una función C de nombre decompress_kernel (ubicada en ./arch/i386/boot/compressed/misc.c). Cuando se ha descomprimido en memoria, es ejecutado. Esto vuelve a ser mediante una función startup_32, pero esta vez de ./arch/i386/kernel/head.S.

 En la nueva función startup_32 (también llamada el intercambiador, o swapper, o proceso 0), la tabla de páginas se inicializa y el paginado de memoria se activa. El tipo de CPU es detectado junto con cualquier unidad de punto flotante (FPU) opcional y se almacena esta información para un posterior uso. La función start_kernel se invoca en este punto (init/main.c), que te redirige al núcleo de Linux no-específico de arquitectura. En esencia, esta es la función “main” del núcleo de Linux.

Con esta llamada, una larga lista de funciones de inicialización son llamadas para configurar interrupciones, ejecutar configuraciones de memoria más avanzadas, y cargar el disco RAM inicial. Al final, se hace una llamada a kernel_thread (en arch/i386/kernel/process.c) para iniciar la función “init“, que es el primer proceso del espacio de usuario. Finalmente, se inicia la tarea “idle” y el planificador puede tomar el control (tras la llamada a cpu_idle). Con las interrupciones activadas, el planificador pre-emptivo periódicamente toma el control para proveer de multitarea.

Durante el arranque del núcleo, el disco RAM inicial (initrd) que fue cargado en memoria por el cargador de arranque de la fase 2 es copiado en la memoria y montado. Este initrd sirve como sistema de ficheros raíz temporal en memoria permitiendo al núcleo arrancar completamente sin tener que montar aún ningún disco físico. Puesto que los módulos necesarios para interactuar con los periféricos pueden ser parte de initrd, el núcleo puede ser pequeño, pero aún así soportar un gran número de posibles configuraciones de hardware. Tras el arranque del núcleo, el sistema de ficheros raíz se traslada (mediante pivot_root) a donde el sistema de ficheros raíz de initrd se ha montado y el sistema de ficheros raíz real se monta.

La función initrd permite crear un núcleo pequeño de Linux con controladores compilados como módulos cargables. Estos módulos cargables dotan al núcleo de los medios para acceder discos y sistemas de ficheros en esos discos, así como de controladores para otros componentes hardware. Debido a que el sistema de ficheros raíz reside en un disco, la función initrd provee los medios para que la secuencia de arranque pueda tener acceso al disco y montar el sistema de ficheros raíz real. En un sistema empotrado sin disco duro, initrd puede ser el sistema de ficheros raíz final, o el sistema de ficheros raíz final puede ser montado por red (vía NFS).

Init

Tras el arranque del núcleo y su inicialización, se inicia la primera aplicación dentro del espacio de usuario. Este es el primer programa invocado que se compila con la librería estándard de C. Antes de este punto en el proceso, no se había ejecutado aún ninguna aplicación en C estándard.

En un sistema Linux de escritorio, la primera aplicación iniciada es típicamente /sbin/init . Pero no tiene por qué serlo. Rara vez los sistemas empotrados requieren de la vasta inicialización que provee init (tal como se configura a través de /etc/inittab). En muchos casos, se puede invocar simplemente a un comando de consola que inicie las aplicaciones empotradas que se necesiten.

Resumen

Tal como el propio Linux, su proceso de arranque es altamente configurable, y soporta un gran número de procesadores y plataformas hardware. Al inicio, el cargador de arranque loadlin proveía una manera simple de arrancar Linux sin muchos adornos. El cargador LILO aumentó las capacidades del arranque, pero carecía de consciencia sobre los sistemas de ficheros. La última generación de cargadores de arranque, tales como GRUB, permiten a Linux arrancar desde un amplio rango de sistemas de ficheros (desde Minix a Reiser).