Yet Another From Boot to Linux Desktop Article

Por Arrecio


Cuando hablamos de Linux normalmente hablamos de sistemas operativos GNU/Linux.

Linux en si es el núcleo del sistema operativo el cual es rodeado de todo tipo de utilizadades para darle una funcionalidad completa de sistema operativo ya que el núcleo básicamente funciona como soporte de todas ellas. El núcleo prové métodos para abstraer el hardware y de gestión de procesos pero para que el sistema operativo realmente resulte útil para el usuario necesita de software o programas que aporten capacidades propias de los sistemas operativos. Algunos de estos programas podrían ser calificados como super-software como por ejemplo los entornos de escritorio o las utilidades GNU. Los superconjuntos formados por el núcleo de Linux y el conjunto de herramientas que lo dotan como sistema operativo completo se denominan distribuciones Linux.

Arch Linux es una de estas distribuciones y es en la que está corriendo en mi ordenador mientras escribo esta línea, así mismo el resto del texto de esta entrada del blog tendrá en cuenta que lo que voy a arrancar es un sistema Arch Linux.

Por otro lado es algo poco discutido por toda la comunidad de usuarios de Linux que la wiki de Arch es una de las principales fuentes de conocimiento de este sistema operativo libre. Aunque algunas entradas tienen aspecto de desordenadas e incluso incompletas siguen siendo una la mejor referencia en términos globales.

En esta wiki tenemos una entrada llamada Arch boot process que busca introducir precisamente este proceso. El enlace lleva a la versión en español por lo que hay que tener en cuenta que las versiones traducidas algunas veces se encuentran algo actualizadas. He utilizado esa página de la wiki como punto de partida de todo este texto, especialmente para estructurarlo por lo que lo primero que trataré son las diferencias entre UEFI y BIOS.

En el momento de empezar a escribir esta entrada del blog todo lo que sé del proceso de arranque Linux es visualizado por mi como un batiburrillo de conceptos que pretendo ordenar a medida que escribo. A pesar de que lo he titulado como aspectos básicos soy consciente de que probablemente termine siendo el texto más largo de todos los escritos hasta ahora en el blog aunque me voy a esforzar de que no lo sea por mucho. Resumir no es mi fuerte pero el objetivo es esbozar mínima pero suficientemente el procedimiento para que cualquiera sepa por donde seguir si quiere ampliar sus conocimientos. Como siempre se incurstarán enlaces a webs externas que explican mejor ciertos conceptos, lo cual trato de hacer cada vez que se nombra una primera vez en el blog o considero que debo volver a hacerlo.

El objetivo con todo ello es que cualquiera que sienta algo de interés sobre como transcurre este proceso en su propio ordenador personal pueda tener una idea algo más claro de lo que hay detrás de todo ello. Creo la entrada sin grandes pretensiones, a sabiendas de que se trata de un complejísimo proceso a pesar de que en los tiempos que corren pueda suceder en apenas unos segundos. La parte más compleja empieza sin duda con la cargar del kernel de Linux cuyo código fuente va por más de 30 años de desarrollo. Cualquier entusiasta está invitado a navegar por él y por supuesto a perderse entre sus millones de líneas.

Para personas que ambicionan conocer el proceso en profundidad existen por supuesto otras publicaciones mucho más completas, tratándose además de un asunto que quedaría fuera del alcance de lo que se considera un blog cuando quizás este propio artículo ya podría considerarse que quedar fuera de ello. Cuando ya llevaba escritos distintos apartados se me ocurrió dividirlo en varias entradas pero finalmente lo he terminado descartando. He editado este párrafo para incluir esta situación.

Una de esas publicaciones es linux-inside, siendo contenidos imprescindibles para iniciarte en el conocimiento profundo de este proceso Kernel boot process y Kernel initilization process, en este orden. Aún no las he leido, algo que me dejo pendiente, y quien sabe, igual da para escribir algo más en el blog.

UEFI vs BIOS

Considero que lo primero que debe quedar claro en alguien que quiera entender el proceso de arranque del sistema operativo es conocer las diferencias generales entre UEFI y BIOS. Ambos son un tipo de firmware de los ordenadores personales, esto viene a ser como lo primero que se ejecuta al arrancar. UEFI es el estandar actual mientras que BIOS es el que tradicionalmente fue usado hasta la aparición del anterior. Básicamente los dos simbolizan el mismo elemento del sistema tan sólo que UEFI es una versión más moderna del concepto.

Como estándares, sea quien sea el fábricante de este firmware debe seguir una serie de especificaciones. Para conocer más sobre la última especificación de BIOS puedes consultar el paper BIOS Boot Specification v1.01 y para UEFI puedes consultar la web oficial de UEFI desde donde se accede al superset de especificaciones UEFI, tanto a la última versión como a versiones anteriores.

La última especificación de BIOS es producto del trabajo de Compaq, Phoenix Tecnologies e Intel Corporation aunque existen muchos fabricantes muy conocidos como AMI. UEFI fue introducida inicialmente por Intel pero actualmente sus especificaciones son producidas por el UEFI Forum.

BIOS es un acrónimo de Sistema Básico de Entrada y Salida (obviamente en su traducción inglesa) y aunque aquí solo interesan los aspectos relacionados con el arranque resulta que en los primeros tiempos se utilizaba también para acceder a todos los periféricos del equipo como teclado, ratón o incluso la pantalla, como una especie de concentrador y mediante un sistema conocido como interrupciones BIOS, lo que fue antes de la aparición de otras soluciones como como Super I/O, aunque esta entrada del blog no va sobre eso.

Aunque siempre suelo utilizar los enlaces de Wikipedia en este caso recomiendo visitar los de OSDev y en concreto los de UEFI y BIOS. En general este wiki es un muy buen lugar que explorar para quienes quieran introducirse en el mundo del desarrollo de sistemas operativos.

Para cualquiera que haya usado tanto UEFI como BIOS encontrará que la principal diferencia a nivel de usuario es que UEFI se siente mucho más moderno, con un interfaz gráfico que permite el uso del ratón contra el interfaz de texto de BIOS que únicamente permitía el teclado.

Pero en lo que respecta a esta publicación la principal diferencia radica en que mientras BIOS prácticamente sólo conoce el sector 0 de cada disco duro, la visión de UEFI va un poco más allá podiendo leer en ciertos lugares del disco llamados ESP que reciben ese nombre no por cuasualidad. Introduzcamos algunos conceptos antes de seguir.

Antes de seguir hablando mejor ver desde ya un diagrama con todo el procedimiento y que usaré como referencia. Un primer diagrama usando BIOS:

Proceso de arranque linux con BIOS+GRUB+systemd

Y un segundo usando UEFI:

Proceso de arranque linux con UEFI+GRUB+systemd

Tanto BIOS como UEFI tienen como primer paso la realización del POST que es un proceso en el que básicamente se detecta el hardware del equipo y entre tales componentes se encuentran los dispositivos de almacenamiento entre los que se pueden encontrar los llamados discos duros o HDD.

Hablando en primer lugar de BIOS, cuando existen varios discos instalados se establece el llamado orden de arranque. El dispositivo en primer lugar será aquel por el que transcurra el hilo de ejecución del equipo una vez finalizado el POST. UEFI se comporta de manera similar, especialmente cuando funciona en modo de arranque tipo BIOS. Pronto se expondrán más detalles.

Antes de seguir veamos un sencillo comando con el que podemos, desde Linux, conocer que tipo de firmware corre nuestro equipo:

[ -d /sys/firmware/efi ] && echo UEFI || echo BIOS

De su forma podemos inferior que cuando el equipo corre en un equipo con UEFI existirá un punto de montaje /sys/firmware/efi (puede variar según distribuciones, supongo).

Accediendo a los datos almacenados en disco duro

Antes de nada debemos tener claro como se accede a los datos de un disco duro. En el párrafo anterior se ha nombrado el sector 0 del disco duro lo cual es una expresión que aún siguiéndose utilizado se refiere realmente a la primera únidad lógica del dispositivo de almacenamiento tipo disco duro que no únicamente HDD. La verdad no sabría definir exáctamente que es una unidad de almacenamiento disco duro pero algunos todavía usamos estas palabras para referirnos a los discos SSD y M.2 o cualquier otro que use el interfaz NvME.

Los discos duros iniciales, o también llamados discos rígidos, consisten en un sistema de almacenamiento mecánico no volatil formado por discos realizados con material ferromagnético donde se almacena la información induciendo un campo magnético al material que luego puede ser leido.

Lo normal es que no exista un único disco sino varios superpuestos llamados platos en los que se almacena información en la parte superior e inferior. Sobre cada cara descansa una cabeza lectora. Todos los platos giran al unísono a una velocidad constante y la cabeza puede por el mismo mediante accionamiento mecánico.

Los discos se dividen en pistas concéntricas de cierta anchura las cuales obviamente tenían una longitud mayor a medida que se acercaban al borde de los discos. La anchura y espaciado de las mismas depende de las capacidades de las cabezas lectoras la cuales siempre estarán descansando sobre una u otra pista cuando se realicen las funciones de lectura o escritora.

Existiendo varios platos todas las pistas de cada uno de ellos situada a la misma distancia del centro forma lo que se conoce un cilindro.

Las platos igualmente se dividen en sectores que dividen igualmente a las pistas. Lógicamente todas las pistas tendrán el mismo número de sectores por lo que su longitud física sera mayor en las pistas más alejadas del centro y por ende la información estará más concentrada en las pistas más cercanas al centro del plato.

La siguiente imagen describe gráficamente todo esto:

Estructura de un disco duro

Los discos duros, dependiendo de su tecnología y capacidad, pueden estar formados por más o menos platos y estos estar divididos en más o menos pistas y y más o menos sectores. En cualquier caso el tamaño del sector, nombrado no por su longitud física sino por la información que puede contener, fue tradicionalmente de 512 bytes algo que se mantuvo durante años hasta la aparición de Advanced Format cuando se comercializaron discos rigidos con tamaños de sectores físicos mayores, de hasta 4096 bytes. No obstante para mantener la obligada compatibilidad tenían un modo de funcionamiento llamado 512e con el que técnicamente los sectores tenían ese tamaño de 512 bytes.

Los sistemas operativos proveen sistemas de abstracción para leer los sectores del disco. A groso modo las operaciones de lectura consistían en programar en el firmware del disco la operación indicando la cabeza a utilizar (asociada a una superficie de un plato), la pista y el sector en el que comenzar la lectura, así como el número de sectores a leer (dentro de un límite) los cuales terminaban en algún lugar de la memoria del equipo. Toda la comunicación se realiza a través de un interfaz siendo los más conocidos ATA o IDE, SCSI, SATA o SAS. Las operaciones de escritura eran similares, cambiando únicamente el como fluye la información por la interfaz.

La forma para direccionar estos datos al inicio era conocida como Cillinder-Header-Sector o CHS usando 1 byte para cada uno de los parámetros. Con este sistema se podían direccionar 256x256x256 sectores de 512 bytes, es decir un máximo de 8.59 GB por lo que terminó en desuso.

La forma en la que se direccionan los sectores de un disco rígido pasó a ser el sistema Logic Block Address o LBA el cual es realmente simple ya que se enumeran los sectores del disco desde el primero considerado el sector 0 hasta el úlitmo y se usa una dirección LBA para referenciarlos. Será el hardware del disco duro el que traduzca esa dirección hacía un lugar físico del disco (cilindro, cabeza y sector) en el que realizar las operaciones.

El tamaño del bloque lógico puede coincidir o no con el del sector físico. Aquí vamos a considerar bloques de 512 bytes por lo que si usamos un disco duro con sectores físicos de mayor tamaño entenderemos que se está trabajando con el modo 512e.

El sistema de direccionamiento LBA es usado igualmente en otros sistemas de almacenamiento como SSD o M.2 por lo que las operaciones de lectura y escritura sobre estos dispositivos pueden realizarse mediante la misma abstracción.

Los sectores forman el bloque de datos que se considera como unidad mínima de lectura. Es decir el hardware del disco no permite acceder a bits concretos dentro de un sector sino que se leen completamente.

Desde ahora me referiré a disco indistintamente a un HDD, SSD, M.2, etc.

Particionamiento del disco: MBR vs GPT

Empecemos por lo viejo. Cuando un sistema operativo es instalado en un disco de un equipo con BIOS una de las tareas de la operación consiste en preparar el llamado MBR, acrónimo que viene de Master Boot Record o registro de arranque principal en español. Este se encuentra alojado precisamente en el sector 0 de un disco o lo que viene a ser su primera unidad lógica es decir en el LBA 0.

La ejecución realmente no puede correr directamente en el disco. Lo que realmente ocurre es que se carga el código en la memoria (usualmente RAM) y es de ahí donde corre. Describiremos algo más el procedimiento cuando proceda.

Hablando siempre de BIOS, cuando finaliza el POST lo que ocurre es que el LBA 0 del disco seleccionado como arranque se copia a la dirección de memoria 0000:7C00 y establece el registro PC de la CPU precisamente en ese valor. Así la CPU continúa ejecutando el código máquina que ha sido volcado a ese lugar de la memoria. Realizado esto la BIOS pierde el protagonismo en favor de ese programa.

La versión del sistema operativo IBM PC DOS 2.0 introdujo un sistema para particionar los discos. Particionar un disco consiste en dividir todo su soporte físico en varios espacios lógicos llamados volúmenes. Este sistema imponía una descripción específica para el MBR que podemos examinar en este enlace en OSDev: MBR(x86).

Según esta especificación se destinaban los primeros 446 bytes para ese código máquina al que pasar la ejecución desde el POST y que se denomina espacio para el bootstrap. A continuación, los siguientes 64 bytes son reservados para la llamada tabla de particiones MBR. Los dos últimos bytes quedan reservados necesariamente para la firma del MBR la cual es 0x55 0xAA. La BIOS utiliza esos dos últimos bytes como única manera de distinguir si ese LBA 0 contiene un MBR o no, rechazando su carga si contiene valores distintos.

Gráficamente este sería la estructura de este MBR:

Estructura de un MBR

En realidad este dibujo no representa la introducida por IBM PC DOS 2.0 ya que la tabla de particiones contiene campos para LBA la cual es algo bastante más moderno. El estándar de este dibujo apareció con MS-DOS 3.30 (Windows 95).

Recomiendo visitar esta web: All the Details of many versions of both MBR (Master Boot Records) and OS Boot Sectors (also called: Volume Boot Records).

La especificación dada por IBM PC DOS 2.0 incluía un programa bootstrap extremadamente sencillo y que ocupaba bastante menos de los 446 bytes. La idea era que tras recibir el control de la ejecución desde el POST el programa buscara entre las particiones del disco cual estaba marcada como de arranque para, en su caso, continuar la ejecución a partir del primer sector de la misma al que se denomina VBR de Volume Boot Record. Los más curiosos pueden leer este análisis del código fuente del MBR de IBM PC DOS 2.0 y ya de paso del considerado standar MBR que fue el introducido como MS-DOS 3.30 (el del diagrama).

Pero lo que en realidad se tiene ahora mismo como un estándar es la parte que afecta a la tabla de particiones del MBR ya que el código almacenado en el bootstrap es algo que queda al libre albedrío de los sistemas de carga de sistemas operativos o bootloader. Seguiremos por ahí más adelante, ahora mismo estamos con las particiones.

Los descriptores de cada partición incluyen campos para indicar si la partición está activa (seleccionada para el arranque) y también dos bytes para almacenar el tipo lo cual será útil para el sistema operativo existiendo cierta convención para sus valores. Aquí una lista de tipos de partición.

Este sistema de particiones MBR permite un máximo de 4 particiones que son el número de entradas que contiene su tabla. Sin embargo como podía preverse una vez nacida la primera especificación del MBR tal y como lo conocemos el número de 4 terminó por quedarse corto. Como solución se propuso que una (y sólo una) de esas 4 particiones podría establecerse como partición extendida. El establecimiento de una partición como extendida se hace estableciendo en el byte destinado al tipo los valores 0x05 o 0x0F siendo la diferencia entre ambos el que el primer indica que la partición extendida comienza dentro de los primeros 1024 cilindros del disco y en el segundo caso está más allá.

El primer sector de una partición extendida contendría el EBR o Extended Boot Record que se estructura de manera similar al MBR con varias cosas a tener en cuenta. En primer lugar el espacio reservado para el bootstrap puede tener finalidades adicionales dependiendo de la implementación. De las entradas en la tabla de particiones sólo tienen utilidad la primera y la segunda quedando las otras dos normalmente rellenadas con 0. La firma es la misma.

La primera entrada de la tabla de particiones extendida contiene información propiamente dichos respecto a la partición. La segunda entrada contiene información mediante la que se puede localizar el siguiente EBR para la siguiente partición extendida. Todo ello está diseñado como una lista enlazada.

Siguiendo esta estructura no existe límite teórico para el número de particiones extendidas más allá del espacio físico disponible, no obstante hay que tener en cuenta que el número de volúmenes soportados es siempre dependiente del sistema operativo.

Hasta aquí la introducción a MBR, continuamos con GPT

En el diagrama de arranque mediante UEFI desaparece el término MBR y aparece ESP sin embargo no debemos pensar de que ESP y MBR son términos desconectados. ESP es un tipo de partición que puede ser también manejada por un sistema MBR. A una partición tipo EFI o ESP se le asigna el código 0xEF en una sistema de particionado MBR.

Las particiones tipo EFI son estructuralmente particiones tipo FAT.

ESP se asocia a UEFI y UEFI es algo moderno, algo que vino tras BIOS para cubrir sus necesidades. Pues bien existe un paso similar en lo que respecta al sistema de particionamiento ya que MBR ha sido superado por GPT y es por ello que el uso de MBR se considera legacy, es decir un sistema que todavía puede usarse y los equipos vienen preparados para ello pero que no se considera el estándar actual el cual sería GPT. GPT y UEFI comparten que fueron iniciativa de Intel y ahora ambas especificaciones son mantenidas por el UEFI Forum, aquí la documentación sobre GPT.

Para mantener la compatibilidad, un disco particionado con GPT seguirá considerando el LBA 0 como reservado para el MBR ya que un disco particionado con GPT puede seguir iniciando desde BIOS en cuyo caso ese LBA 0 contiene un Legacy MBR. Cuando se pretende que eso no sea posible lo que contendrá es un Protective MBR o PMBR.

A partir del LBA 1 se establece la tabla de particiones GPT. Ese mismo LBA contendrá la cabecera o GPT Header y desde él la tabla de particiones en sí y cuya estructura se define en la cabecera. Esa tabla de particiones tiene una copia de respaldo en la parte final del disco. Un diagrama de lo dicho es el siguiente:

Estructura de disco particionado con GPT

En la cabecera se establece la métrica de cada entrada en la tabla de particiones así como el lugar que ocupa mediante el señalamiento del primer y último LBA, valores que variarán dependiendo de si estamos en la GPT principal o de respaldo, y mediante los cuales se calcula el tamaño de la tabla. Así mismo se indica el número de entradas de la tabla y el tamaño de cada una cuya multiplicación debe coincidir con el tamaño de la tabla el cual será como mínimo de 16384 bytes o lo que es lo mismo 32 bloques lógicos de 512 bytes.

En la última especificación de GPT se exige que el tamaño en bytes del descriptor sea un valor 2 elevado a n >= 128, es decir puede ser de 128, 256, 512, 1024, etc, bytes.

El estándar de tamaño para el descriptor es 128 bytes por lo que en una tabla de 16384 bytes habría espacio para establecer 128 particiones usando ese tamaño para cada descriptor. Precisamente 128 es el máximo de particiones admitidas por las últimas versiones de Windows.

Aunque técnicamente es posible tener un mayor número de entradas estableciendo ese número en el campo NumberOfPartitionEntries la realidad es UEFI admite también un máximo de 128.

Hablando de cada entrada de la tabla. esta contendrá al menos campos para tipo de partición llamado PartitionTypeGUID así como un indicador único para la misma que se conoce como UniquePartitionGUID, ambos codificados en GUID (16 bytes para cada uno). En el caso de las particiones tipo EFI este PartitionTypeGUID es C11A7328-F81F-11D2-BA4B-00A0C93EC93B. Igualmente inluirá un campo con el nombre dado a la partición en texto plano y además los LBA de inicio y fin de la misma junto con un campo en el codificar los atributos de la partición.

Y con esto ya tenemos suficiente información para pasar al siguiente apartado.

Momento para los bootloader

Hasta el momento sabemos distinguir este BIOS y UEFI. También sabemos lo que es un sistema particionado mediante MBR y mediante GPT. Sabemos igualmente que en modo BIOS tras el POST el código se pasa al LBA 0 del disco de arranque y en modo UEFI a la partición de tipo ESP que haya sido seleccionada como de arranque. Efectivamente en modo BIOS se seleccionan unidades mientras que en modo UEFI particiones con independencia de que el boostrap utilizado termine llevando la ejecución median redirección a un Volume Boot Recod alojado en alguna partición.

Los bootloader son un tipo de software que se aloja en tales lugares con el objetivo de encaucar la ejecución del sistema hasta la carga de un sistema operativo completo. Dependiendo de nuestro firmware y sistema de particiones el bootloader estará instalado en el equipo de una manera o de otra.

La instalación del bootloader es una fase importante de la instación de cualquier sistema operativo. Aunque no se vea, la instalación de Windows termina modificando el bootloader y adaptándolo al tipo de particionamiento del disco, el cual puede venir establecido previo a la instalación del sistema operativo. Si el disco es nuevo Windows necesitará particionarlo y las últimas versiones lo harán mediante GPT ya que desde Windows 11 no soporta (oficialmente) MBR, aunque algunos usuarios han encontrado métodos para hacerlo.

Si quieres ver en detalle las últimas versiones del MBR soportadas oficialmente por windows tienes el enlace An Examination of the Windows™ 7, 8/8.1 or 10 MBR ( Master Boot Record ) y también A Comparison of the Windows™ Vista, 7 and 8 VBR (or Boot Sector) Code

GRUB2

Durante la instalación de Arch Linux siguiendo el orden indicado en su entrada de la wiki el pariticionamiento se realiza en fase temprana y para ello insta el uso de la utilidad fdisk mientras que para el tema del bootloader nos deriva al final del manual hacia la entrada wiki correspodiente a la instalación de un bootloader. Eligiremos GRUB.

GRUB refiere a GRand Unified Bootloader y el 2 significa que es su segunda versión la cual fue reescrita desde 0. Cuando actualmente hablamos de GRUB lo hacemos de GRUB2. Para referirnos al original puedos utilizar GRUB Legacy.

GRUB es obviamente un bootloader instalable tanto en BIOS como en UEFI mientras que su antecesor no soporta UEFI al igual que su competidor en tiempos, LILO (de LInux LOader).

El comando instalador de GRUB es grub-install quien recibirá parámetros con el que debemos darle a conocer el sistema en el que se va a instalar, tanto la arquitectura del equipo como el firmware objetivo. Cuando hablamos de arquitectura hablamos por ejemplo de x86-64 que es la más común en estos tiempos cuando hablamos de ordenadores personales. Podemos hacernos una idea de las arquitecturas en las que puede instalarse linux examinando el contenido de la carpeta arch de su código fuente. Respecto al firmware se refiere obviamente a BIOS o UEFI.

GRUB identifica los objetivos de instalación mediante identificadores únicos que aunan referencias tanto a la arquitectura como al firmware. Por ejemplo el objetivo de instalación x86_64-efi se refiere a un sistema con arquitectura x86-64 y firmware UEFI. A la suma de ambos elementos se le conoce como plataforma.

El identificador para un con BIOS tradicional, es decir el que tiene como objetivo un MBR de un ordenador personal es i386-pc con independencia de que la arquitectura sea de 32 o 64 bytes.

Como no, GRUB tiene su propio repositorio en GitHub donde poder trastear. Si examinamos el contenido del directorio grub de fuentes varios de los directorios nos resultarán familiares, coincidiendo en nombre que los vistos anteriormente en el directorio arch de las fuentes del núcleo.

Cuando GRUB se instala en un equipo con UEFI, además de especificar la plataforma objeto, necesitará conocer el directorio donde se encuentra montada la partición tipo EFI donde se alojará y un identificador para el bootloader que será el nombre con el que se de a conocer en UEFI y el que aparecerá como opción a elegir para el arranque. Un comando de instalación típico es:

grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=GRUB

Para el arranque en modo BIOS, es decir para instalarlo en un MBR, es necesario indicar la referencia al disco destino o install device mediante su denominación o referencia en el directorio /dev donde linux monta un sistema de archivos virtual para los dispositivos hardware detectados. La página del manual de grub-install ofrece información de todas las opciones. Si se especifica un objetivo tipo EFI el install device es ignorado.

Las ventajas de usar instalaciones en sistemas UEFI es que las particiones tipo EFI están bastante menos limitadas que el MBR haciendo que del primer caso una operación casi trivial. La instalación en un MBR es algo más compleja. El siguiente diagrama ilustra como se alojan los componentes de GRUB para ser utilizados mediante arranque con MBR. Contiene dos ejemplos, uno con un sistema particionado también mediante el MBR y otro particionado con GPT:

Ejemplos de arranque mediante MBR (también con disco particionado con GPT)

En ambos casos la ejecución viene directamente del POST de BIOS. Allí ya se ha comprobado que la firma del MBR es válida, se ha copiado el contenido a la dirección de memoria 0000:7c00 y se han actualizado los registro de la CPU para que la siguiente instruccion a ejecutar se precisamente la alojada en esa dirección.

Cuando GRUB se instala en modo BIOS su funcionalidad se divide en dos archivos llamados boot.img y core.img que contienen código máquina de la arquitectura en la que se instala. boot.img es muy pequeño y se instala en el espacio para el bootstrap del MBR, de ahí el nombre de boot.img. El código es suficiente para cargar la segunda parte contenida en core.img. En ambos ejemplos se observa que las diferencias radican en el lugar en el que se aloja ese segundo archivo.

Cuando trabajamos con un archivo particionado con MBR se utiliza el llamado MBR gap.

Para conocer que es el MBR gap primero debemos saber que incluso en un disco particionado con MBR las particiones no nacen en el LBA 1, es decir no se sitúan inmediatamente a continuación del MBR, sino que el primer LBA que puede ocupar una partición estará bastante más allá.

El motivo de esto es que el sistema de particionado buscará, inteligentemente, alinear las particiones con cierto valor que optimiza las operaciones de acceso. Este valor será seguramente 1MB. El espacio entre el LBA 1 (inclusive) y el LBA donde comienza la primera partición definida es lo que se denomina MBR gap y es el lugar aprovechado por GRUB para instalar core.img cuyo tamaño está obviamente limitado por ese espacio. No lo tengo muy claro pero creo que si el disco, por el motivo que sea., no tiene suficiente espacio en ese lugar como para alojar core.img la instalación de GRUB fracasará.

Para más información sobre el alineado de las particiones puedes consultar el artículo Partition Alignment detailed explanation.

Este problema desaparece con particionado GPT y aunque puede existir también un espacio aprovechable entre la tabla de particiones y la primera partición del disco lo que se hace es crear una partición específica para alojar core.img. Supongo igualmente que si la tabla de particiones de GPT está completa la instalación de GRUB fracasará, algo en todo caso bastante improbable. Como curiosidad y ya que las particiones GPT pueden tener un nombre escrito en la tabla de particiones, GRUB nombra estas particiones como BIOS_grub y su tamaño será también ese 1MB que se espera que esté disponible en el MBR gap.

Si la función de boot.img es cargar core.img (fase que se denomina fase o stage 1), la función de core.img es montar la partición donde se ha instalado linux y buscar en el directorio concreto donde se encuentran todos los archivos relativos al arranque para configurar y continuar con la ejecución de GRUB (fase que se denomina fase o stage 1.5). Finalmente se mostrará el menú lo que ya se considera el fase o stage 2 de GRUB.

GRUB en modo UEFI

La instalación de GRUB en modo UEFI es bastante más trivial ya que las fases 1 y 1.5 se ejecutan a la vez mediante un ejecutable instalado en una partición tipo EFI. Vamos a dar por hecho de que el particionado es mediante GPT y no vamos a realizar ningún tipo de experimento.

Las particiones EFI contienen un sistema de archivos tipo FAT con una estructura bastante sencilla. Se puede utilizar una única partición EFI a pesar de que pudieramos tener multiples particiones EFI con diferentes bootloaders, uno por cada sistemas operativos instalados.

La partición EFI contendrá obligatoriamente un directorio llamado EFI en la raíz de la misma que contendrá a su vez otro directorio para cada uno de los sistemas operativos que se pueden arrancar desde allí. Cada directorio contendrá uno o varios ejecutables del bootloader así como cualquier otro archivo que necesite, no hay restricciones. Así por ejemplo puede contenderse también en la partición EFI el archivo de configuración de GRUB. Incluso diría que pueden incluirse las imágenes del núcleo dentro de la partición EFI.

Ejemplo de arranque mediante ESP

En la figura anterior se alojan dos bootloader en la partición tipo EFI. Uno de ellos es EFI/boot/bootx64.efi que es el llamado Fallback bootloader.

Para que una bootloader este disponible en la tabla de opciones de UEFI deben realizarse diversaso operaciones de escritura en su NVRAM para darse a conocer. En caso de que no exista ninguna entrada configurada, para evitar el cuelge del sistema UEFI siempre tendrá en cuenta esta dirección para buscar en todas las particiones tipo EFI que encuentre. En nuestro ejemplo existe si bien se desconoce como procede.

El otro bootloader instalado lo está en EFI/grub/grubx64.efi. Este es el bootloader de GRUB en sistemas x86-64. El nombre del directorio puede cambiar ya que depende de la identificación dada durante la instalación. Además viene acompañado del archivo de configuración y el directorio de los módulos aunque lo usual es alojar todo esto en /boot/grub junto con el resto de archivos del sistema GRUB. Esta posibilidad viene documentada en el Arch Linux Wiki para GRUB dentro de la sección método alternativo de instalación y así de paso hablamos de ella. Podemos decir que este método auna en un mismo lugar todo lo necesario para la ejecución de las fases 1 y 2 de GRUB.

Si montamos la partición EFI de Windows 11 encontramos que en el directorio EFI existe un directorio llamado Boot y otro llamado Windows. En este segundo se alojan multitud de ficheros, incluidos algunos .dll utilizados por el bootloader de Windows que recibe el nombre de Windows Boot Manager y su archivo ejecutable principal es nombrado bootmgfw.efi. En total son más de 30 MB lo que ocupa el bootloader de Windows mientras que en una partición usada por GRUB2 para un único sistema Linux el bootloader puede llegar a ocupar únicamente unos 300 kb, lo que ocupa el único archivo ejecutable que se aloja en la partición EFI. En el caso de la versión alternativa nombrada en el párrafo anterior se necesita algo más de espacio.

Como nota curiosa, siguiendo este tutorial de arranque dual Arch Linux se indica que si instalas antes un Linux con su partición EFI entonces Windows termina utilizándola evitando la creación de una nueva partición dedicada.

Esta publicación contiene una descripción bastante detallada y a la vez resumida del procedimiento de arranque del Windows Boot Manager.

Tras el POST de un firmware UEFI la ejecución se transfiere a uno de los bootloader disponibles para arranque y alojados en una o varias particiones tipo EFI. Todos ellos deben estar visibles para poder ser elegidos como sistema de arranque.

La instalación de GRUB en este caso realiza varias operaciones. La primera es copiar el correspondiente ejecutable en la partición EFI seleccionada, pero nada de esto sirve si no se indica a UEFI que esa partición forma parte de los ESP de arranque por lo que realiza operaciones de escritura en la NVRAM para que UEFI sepa de su existencia. Las operaciones son realizadas mediante llamadas a la API de UEFI a través de las cuales se crean las conocidas como boot entry.

La NVRAM de UEFI esta ocupada por diversas entradas que tienen la forma de variables. Arch Linux las monta como archivos dentro del sistema de archivos y están disponibles usualmente en /sys/firmware/efi/efivars. Las entradas que comienzan por Boot son las utilizadas por el sistema de arranque. El usario puede leerlas e incluso manipularlas usando la utilidad efivar que al menos Arch Linux instala con el sistema base con UEFI disponible. La wiki de Arch Linux dedica un apartado para estas variables dentro de la entrada dedicada a UEFI.

De no existir entradas Boot UEFI intentará buscar ese Fallback Bootloader del que ya hemos hablado, pero imaginemos que todo ha ido correctamente y hemos alcanzado el menú de GRUB.

El menú de GRUB

Tras la primera fase GRUB se muestra su menú en el que se disponen todas las opciones de arranque establecidas en grub.cfg. La muestra del menú es el punto 4 de nuestros diagramas iniciales.

GRUB es un bootloader muy potente que ofrece gran cantidad de opciones. Es un sistema modular y muchas de estas opciones están disponibles únicamente si el módulo que la implementa está disponible. No todos los módulos están disponibles para todas las plataformas y si lo están lo estarán en /boot/grub/ agrupados en un directorio con el nombre de la plataforma, por ejemplo en /boot/grub/x86_64-efi o pendiendo del lugar elegido durante el método alternativo. Además ofrece un command shell disponible para expertos. También es el lugar en el que acabará GRUB si la opción elegida del menú no puede llevarse a cabo.

Tales opciones no tienen porque circunscribirse a arranques del sistema pudiendo contener opciones apara apagar o reiniciar el equipo, mostrar el UEFI Shell o ejecutar otras utilidades como memtest86 por poner algunos ejemplos. Aquí algunas opciones de configuración y gran parte de las posibilidades sólo estan disponibles en sistemas UEFI.

Mostrado el menu de GRUB y seleccionada la carga de un sistema operativo Linux provocará siempre cargar el kernel en memoria, y opcionalmente montará un sistema de archivos de sólo lectura en la RAM que habremos creado con initramfs (o antiguamente con initrd, puedes leer sobre las diferencias entre ambos aquí). Un ejemplo de opción para la carga de un sistema operativo Linux es el siguiente:

menuentry 'Arch Linux' --class arch --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-8525ac2d-870a-465f-ad9c-d77cc6adf882' {
        load_video
        set gfxpayload=keep
        insmod gzio
        insmod part_gpt
        insmod ext2
        search --no-floppy --fs-uuid --set=root 8525ac2d-870a-465f-ad9c-d77cc6adf882
        echo    'Loading Linux linux ...'
        linux   /boot/vmlinuz-linux root=UUID=8525ac2d-870a-465f-ad9c-d77cc6adf882 rw  loglevel=3 quiet nvidia-drm.modeset=1
        echo    'Loading initial ramdisk ...'
        initrd  /boot/initramfs-linux.img
}

Esta es la entrada que selecciono para cargar mi Arch Linux. El espació entre llaves representa la lista comandos GRUB que se ejecutarán secuencialmente al seleccionar la opción. En este caso se ejecuta load_video que carga los módulos de video, se trata de una macro definida en el propio fichero de configuración. set establece variables de entorno que pueden ser consultadas por el núcleo, en este caso set gfxpayload=keep se utiliza para mantener el mismo modo gráfico para el proceso de carga del sistema operativo que el utilizado para mostrar el menú de GRUB. insmod se ocupa de cargar módulos. search se utiliza para buscar vólumenes y en este caso al establecer --set=root monta el sistema de archivos como raíz, siempre que el sistema de archivos esté soportado. echo muestra un mensaje por pantalla, linux carga en memoria la imagen del núcleo de linux indicada y establece su linea de comandos, initrd hace lo propio pero para la imagen del initial ramdisk (no necesariamente generado con la utilidad del mismo nombre).

Si entramos en el GRUB Shell y ejecutamos la lista arriba indicada obtendremos el mismo resultado que al seleccionar si bien tendremos que ejecutar manualmente el último paso mediante el comando boot que se ejecuta implícitamente al final de una entrada menuentry que en nuestro caso configura la CPU para continuar la ejecución a través del núcleo cargado.

Me resulta increible la poca documentación que hay de GRUB2. El manual de GRUB Legacy sí que está fácilmente disponible.

Carga del sistema operativo desde GRUB

Acabamos de ver que tanto el archivo del núcleo como una versión temprana del sistema de archivos raíz son lanzados desde GRUB. La ubicación de ambos archivos es dada a GRUB a través del archivo de configuración o mediante GRUB Shell.

Cuando hablamos del núcleo hablamos de una imagen binaria del mismo, en mi caso ya he mostrado que se encuentra en el archivo /boot/vmlinuz-linux (la z indica que es una imagen comprimida mediante el algoritmo gzip, su descompresión es trabajo de GRUB). Cuando coexisten varias imágenes del kernel lo normal es nombrar tales imágenes de alguna manera que se distingan. Respecto a esa versión temprana del sistema de archivos en mi caso se llama /boot/initramfs-linux.img y para sorpresa de nadie es del tipo initramfs (las versiones tipo initrd suelen contener este vocablo en el nombre del archivo).

La imagen del kernel es un archivo binario que se ejecuta de manera nativa y su código fuente es mantenido por Linus Torvals constando su repositorio en GitHub. Una vez cargado (previa su descompresión si es necesaria) se lanza y es el kernel quien toma el mando de la máquina. Este ejecutable admite incluso argumentos desde la "línea de comandos" y que son establecidos en grub.cfg o mediante GRUB Shell. Para los más curiosos todo el proceso de inicio es llevado mayormente por la función start_kernel que puede visualizarse aquí

start_kernel es la primera función escrita en puro C ya que previamente gran parte del código es dependiente de la plataforma por lo que se hace uso de mucho lenguaje ensamblador que probablemente estará alojado por aquí: https://github.com/torvalds/linux/tree/master/arch.

Para una vista rápida de lo que se hace desde la función start_kernel se puede ver este artículo: Early Boot and start_kernel().

Respecto al init ramdisk esta conforma una solución al problema del huevo y la gallina que surge ante la carga del sistema operativo. El núcleo de linux es monolítico, consiste como hemos dicho en un único binario ejecutable, lo cual no implica que pueda dotarse de funcionalidades mediante módulos. Estos módulos incluyen los controladores para manejar sistemas de archivos pero si alojas estos módulos en un sistema de archivos resulta imposible accederlos. Inicialmente se decidió que incrustar estos controladores en el módulo no era una opción por lo que la solución pasó por incluir en el núcleo un método para construir un sistema de archivos raíz temporal llamado early user space a partir de un archivo tipo cpio que es el formato del init ramdisk que es donde se encontran otros módulos que resulte necesario cargar y gracias a los cuales se montará finalmente el el sistema de archivos raíz definito.

Llegados a este punto uno termina preguntándose como es posible que GRUB pueda montar tan fácilmente el sistema de archivos donde se aloja el sistema operativo para poder acceder al kernel y al propio kernel le cueste tanto. Realmente esta pregunta ya no puede hacerse por lo que indico en el párrafo siguiente.

A partir de linux 2.6.13 (lanzado en 2003) se tomó la decisión de permitir incorporar en el núcleo gran parte de controladores de los sistemas de archivos más utilizados lo que permite omitir el initrd sin embargo sigue formando parte de las buenas prácticas incluir un init ramfs por cuestiones de velocidad y otros motivos como por ejemplo que la compilación del kernel no incluya los drivers necesarios para montar directamente el definito porque así se ha decidido o porque simplemente no existe tal posibilidad.

Para finalizar esta sección indicar que es fácil caer en el error de asociar rootfs con el sistema raíz definitivo cuando técnicamente se refiere al sistema raíz en el que opera el núcleo durante su carga por lo que podemos decir que es sinómino de ese early user space al que hicimos mención.

Un último enlace antes de seguir: ramfs, rootfs and initramfs.

Montaje del sistema raiz definitivo

Linux es el núcleo de un sistema operativo y de nada sirve sin un sistema de archivos. Este sistema de archivos crece desde el llamado directorio raíz / desde el cual crece un árbol de directorios más o menos estándarizado que sustituye al rootfs. No todos los ficheros de este sistema de archivos se corresponden a archivos almacenados en los discos, en memoria o compartidos por red, algunos otros pueden ser incluso sistemas virtuales y son montados durante el proceso de inicio init.

Algunos de los directorios montados en ese sistema de archivos raíz lo son por acción directa del kernel (como por ejemplo otros son montados automáticamente durante el arranque como parte del llamado proceso init y otros pueden ser montados manualmente por acción del usuario, en ambos casos mediante el uso de mount.

Lanzando /sbin/init

Como parte final del arranque del núcleo se produce el lanzamiento de init mediante la ejecución de /sbit/init quien se encarga de iniciar el espacio de usuario. Un sistema de inicio tan querido como odiado es systemd (aquí su web oficial) que es el que utiliza por defecto la distribución Arch Linux, al menos de momento. Si hacemos un ls -l /sbin/init veremos que se trata de un enlace simbólico a /sbin/lib/systemd/systemd. Si el núcleo se considera el primer proceso del sistema operativo (PID 0) el PID 1 es /sbin/init.

Mucha gente considera que este sistema se ha vuelto monstruoso y que poco a poco está fagocitando a GNU/Linux con intención de que se conozca como Systemd/Linux. Considero que la gente es un poco exagerada en este aspecto.

Son funciones de /sbin/init, o en este caso systemd, levantar servicios que son programas corriendo en segundo plano ofreciendo cierta utilidad, configurar hardware, montar sistemas de archivos, así como cualquier otra operación que pretenda preparar el sistema opeartivo para la utilización final por parte del usuario.

Tambíen se encarga de montar los directorios virtuales /dev, /proc o /sys).

Todas estas funcionalidades son proporcionadas por systemd mediante las denominadas systemd units. Las unidades de systemd son las cosas que hace systemd. La filosofía a seguir es una unidad por cada operación a realizar durante el arranque. Si vamos a montar sistemas de archivos configuraremos una unidad de tipo mount por cada systema de archivos.

Las unidades se mantienen con la utilidad systemctl. La configuración de las unidades se realiza mediante un sistema declarativo en contraposición de un diseño de sistema init mediante la ejecución directa de scripts como es por ejemplo s6 o runit. Cada tipo de unidad de systemd necesitará de un archivo de configuración. Por ejemplo para una unidad tipo mount sería algo así:

[Unit]
Description=Additional drive

[Mount]
What=/dev/disk/by-uuid/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
Where=/mnt/driveone
Type=ext4
Options=defaults

[Install]
WantedBy=multi-user.target

Cada fichero de unidad tiene 3 secciones. Unit, Install y otra dependiente del tipo de unidad y que en este caso es Mount y en el défindos Qué queremos montar, Donde queremos hacerlo, el Tipo de lo que queremos montar y las Opciones de montaje. Systemd está diseñado para ser simple y funcional. Además systemd concentra todo el log de las unidades en un único lugar llamado journal, como sustituto de la solución syslog. Los archivos de journal no son de texto plano y requieren de journalctl para consultarlos siendo esta otra de las cosas que no gusta a sus detractores.

Este enlace a Working with systemd unit files de la documentación de RedHat es una buena fuente para seguir.

Para conocer más de systemd de manos de su creador este está realizando una serie de artículos publicados en la red social Mastodon y también en este blog Pid Eins (para sorpresa de pocos eins es 1 en alemán).

Finalmente el prompt

Concluido el sistema de iniciado /sbin/init el núcleo configura las consolas virtuales. En los inicios de los sistemas informáticos modernos se utilizó la palabra consola para definir el conjunto de elementos con los que el usuario se comunica con la máquina e inicialmente se componía básicamente de un teclado como dispositivo de entrada y una pantalla como de salida. Los sistemas Linux utilizan una abstracción del concepto de consola mediante estas consolas virtuales que pueden ser varias y a las que se denomina ttyN donde N es el número que la identifica. Se puede cambiar de consola con la combinación de teclas CONTROL+ALT+F1..FN.

Tradicionalmente (ojo, tradicionalmente) en Linux se reservan las 6 primeras consolas virtuales para el modo texto y la 7 para el modo gráfico, donde se conecta algún display manager. A las consolas de texto se conecta un gestor de consola de texto que recibe el nombre genérico de getty. Uno es un punto de entrada gráfico y el otro texto en el que el usuario ingresa sus credenciales para poder hacer uso del equipo. El display manager sirve además para seleccionar el tipo de sessión, es decir el window manager con el que interactuaremos. Los window manager o gestores de ventanas son los que se encargan dibujar las decoraciones de las ventanas, su posicionamiento y redimensión. Esto corren sobre el sistema gráfico elegido existiendo ahora mismo dos opciones que copan el mercado, estas son Xorg y Wayland.

Xorg es un sistema que funciona mediante modelo cliente/servidor del X Window System lo que permite también escritorios remotos ya que prácticamente recibe entradas y emite salidas al más puro estilo de terminal serie. Los gestores de ventanas se conectan al servidor y sirven de puente para las aplicaciones de la misma manera que el servidor X sirve de puente para los gestores de ventanas para acceder directamente al ratón, teclado o la pantalla. Wayland sin embargo sigue otro modelo y a sus gestores de ventanas se les conoce realmente como compositores y a Wayland no se le tiene como un programa independiente sino como una parte del gestor, la que le da la funcionalidad de comunicarse con ese teclado, ratón y pantalla. Un resumen de las diferencias entre los dos métodos de renderizado lo tenemos en la siguiente imagen:

Diferencias entre Xorg y Wayland

Por encima de todo esto están las que consideramos super sesiones mediante los llamados Desktop Enviroment o entornos de escritorio.

En el contexto de Arch Linux y systemd, el servicio que se encarga de lanzar el gestor de consola, que en este caso es agetty, viene configurado por defecto en /etc/systemd/system/getty.target.wants/getty@tty1.service y en cuanto al display manager este no se instala en la instalación base pero cuando instalamos algún paquete que hace tal función usualmente quedará configurado en /etc/systemd/system/display-manager.service.

Trabajar con systemd implica también salir algo de esa configuración histórica de tty ya que los display manager por defecto escuchan en el tty1 y entran en conflicto con getty.

Sea como fuere, mostrada la solicitud de credenciales el sistema linux se da por iniciado.