Es importante comprender adecuadamente el arranque del Linux, a continuación se describe el proceso que brindará un entendimiento de los diferentes elementos que lo conforman lo que servirá a la hora de compilar un sistema de Linux embebido. En el arranque del sistema intervienen principalmente tres componentes Software: el gestor de arranque (Bootloader), el Kernel y el iniciador de procesos (Init Process).
1. Fase del gestor de arranque.
El Bootloader es el primer Software que se ejecuta una vez se arranca el sistema, desempeña una inicialización de bajo nivel del Hardware y de algunos puntos de prueba para luego cargar la imagen del Kernel seguido del código de inicialización del Kernel delegandole el control desde este punto, el gestor de arranque depende altamente de la plataforma Hardware empleada. A continuación se presenta la secuencia de pasos ejecutada por el gestor de arranque:
1.1. Inicialización de Hardware. Normalmente incluye:
1. Configurar la velocidad de la CPU.
2. Inicialización de la memoria (Determinación del tamaño de memoria disponible, borrado de la misma y configuración de registros).
3. Encendido del caché disponibles.
4. Configuración del puerto serial para el arranque de la consola.
5. Realización de diagnósticos de Hardware, esto se conoce como POST: Power On Self-Test diagnostics.
Una vez estos pasos se han completado satisfactoriamente, el paso a seguir es el de cargar el Kernel de Linux.
1.2. Descarga de la imagen del Kernel y el Disco RAM inicial (Initrd).
El gestor de arranque requiere localizar la imagen del Kernel que dependiendo de la aplicación se puede encontrar en un Disco Duro, una memoria Flash o alguna ubicación en la red. Cualquiera que sea el caso se requiere cargar la imagen en la memoria, si se encuentra comprimida (en la mayoría de los casos) debe ser descomprimida.
Si también se encuentra presente un Disco RAM Inicial (Initrd) el gestor de arranque también debe cargarlo en la memoria, el Initrd se trata de un sistema de archivos temporal que emplea el Kernel durante el inicio del sistema típicamente para hacer los arreglos necesarios para que el sistema de archivos raíz pueda ser cargado.
Se resalta que la dirección de memoria donde el Kernel es cargado es designada por el gestor de arranque dependiendo de lo que lee del encabezado de la imagen del kernel, normalmente para las imágenes de Kernel se emplean archivos .ELF (Executable and Linkable Format) que contienen un encabezado con información referente al archivo.
1.3. Configurando Argumentos.
La transferencia de argumentos es una opción poderosa soportada por el Kernel de Linux. Linux genera medios genéricos para la transferencia de argumentos al Kernel a través de cualquier plataforma. Normalmente el gestor de arranque configura un área de la memoria inicializándola con las estructuras de datos requeridas (que pueden ser identificadas por el Kernel) para que la transferencia de argumentos se presente solo es necesario cargar los valores deseados en estas estructuras.
El punto de entrada del Kernel es asignado por el enlazador cuando se compila el Kernel una ves el gestor de arranque salta hacia el punto de entrada del Kernel se culmina su trabajo en la mayoría de los casos entonces el Kernel entra a disponer del espacio en memoria utilizado por este, se debe tener en cuenta este aspecto a la hora de el diseño del mapa de memoria del sistema.
1.5. Arranque del Kernel.
El arranque del Kernel se puede dividir en dos etapas:
Inicialización específica de la plataforma/CPU.
Si se está portando el Linux a una plataforma específica, esta sección es muy importante porque presenta una serie de pasos necesarios en el portado o migrado del BSP (Board Suport Package). En sistemas embebidos BSP se trata del código de soporte de una board especifica y normalmente hace parte del gestor de arranque de un sistema. La inicialización de la plataforma consiste en los siguientes pasos:
1. Configurando el ambiente para la ejecución de la primera rutina en C: el punto de entrada del Kernel se trata de una rutina en assembler que dependerá específicamente de la plataforma. El nombre de esta función varía pero normalmente se encuentra en un archivo llamado head.s y realiza lo siguiente:
a. Activar el MMU de la plataforma: Muchos de los gestores de arranque no trabajan con MMU por lo tanto la dirección virtual concuerda con la física; por otro lado el Kernel es compilado con la dirección virtual. por tanto se requiere que en máquinas que no tienen el MMU activado, se active, así el Kernel puede empezar a utilizar la dirección virtual normalmente.
b. Realizar la inicialización del Caché, esta labor también depende de la plataforma.
c. Configurar BSS (block started by symbol) colocándola en cero. d. Configurar la pila para que la primera rutina en C pueda ser
invocada. La primera rutina en C es la función start_kernel()que se encuentra en init/main.c Esta es una función mas grande que realiza una serie de procesos hasta que termina en una tarea de marcha lenta (la primera tarea en el sistema que tiene una identidad de proceso igual a 0). Esta función invoca el resto de las inicializaciones de la plataforma que se describen a continuación
2. La función setup_arch(): realiza la inicialización de la plataforma/CPU especifica para que las demás inicializaciones puedan ser invocadas de
forma segura esta función es altamente dependiente de la plataforma, se pueden describir algunas funcionalidades en común:
a. Reconocimiento del Procesador. Se realizan los arreglos requeridos por este procesador.
b. Reconocimiento de la Placa, igualmente se realizan los arreglos requeridos por la placa.
c. Análisis de los parámetros de la línea de comandos transferidos al Kernel
d. Identificación si el Disco Ram ha sido configurado por el gestor de arranque para que el Kernel después pueda cargarlo como el Sistema de Archivos Raíz.
e. Llamada de funciones Bootmem, estas se refieren a la memoria inicial que el Kernel puede reservar para diferentes propósitos antes de que el código de paginado disponga de toda la memoria. Por ejemplo se puede utilizar un espacio reservado previamente por el Bootmem para manejar el DMA.
f. Se Llama la función de inicialización de paginado que toma el resto de la memoria para el ajuste de las páginas de memoria del sistema. 3. Inicialización de las Excepciones – la función trap_init(): esta función ajusta
los manejadores de excepciones referentes al el Kernel específico. Previo a esto si una excepción ocurre la respuesta depende de la configuración de la plataforma.
4. Inicialización del proceso del manejo de interrupciones – la función init_IRQ(): esta función inicializa el controlador y los descriptores de interrupciones (estos son estructuras de datos empleadas por el BSP para asignar interrupciones). Nótese que las interrupciones no se encuentran habilitadas en este punto esto es responsabilidad del individuo; los drivers contienen las líneas que habilitan las interrupciones durante su inicialización pero son cargados luego. Por ejemplo la inicialización del timer se asegurará de que su interrupción es habilitada.
5. Inicialización de Timers – La función time_init(): Esta inicializa la señal del temporizador Hardware para se comience a generar la señal de reloj con la cual funciona el sistema.
6. Inicialización de la consola – La función console_init(): Esta inicializa el dispositivo serial como una consola. Una vez esta está activada, todos los mensajes de arranque son presentados en la pantalla del sistema si la posee, de lo contrario será enviada por el puerto serial. Para imprimir un mensaje desde el Kernel se emplea la función printk().
7. Calculando ciclos de espera para la plataforma – la función calibrate_delay(): Esta función es utilizada para implementar esperas de
microsegundos dentro del Kernel utilizando la función udelay(), ésta oscila por algunos ciclos hasta obtener los microsegundos especificados por el argumento para esto el número del ciclos de reloj por microsegundo deben ser conocidos por el Kernel y la función calibra el número de ciclos de espera, se basa en las interrupciones del timer asegurando que los ciclos de espera trabajan de forma uniforme para cualquier plataformas.
Inicialización del Sub-sistema.
La mayoría de las inicializaciones del subsistema son realizadas durante la ejecución de la función start_kernel(), al final de esta el Kernel crea otro proceso llamado el proceso de inicio que realiza el resto de la inicialización (drivers, llamadas de inicio, carga del sistema de archivos raíz y salto al espacio de usuario) este proceso es el que se convierte en el proceso 0.
Incluye:
Inicialización del Scheduler.
Inicialización del administrador de memoria. Inicialización del VSF.
1.6. Inicialización de Drivers.
La inicialización de drivers es realizada después de que la administración de memoria y de procesos se encuentran activas, esto es realizado en el contexto del proceso de inicio.
1.7. Carga del Sistema de Archivos Raíz.
Teniendo en mente que el Sistema de Archivos Raíz es el principal, es decir, se trata del sistema de archivos en donde otros archivos del sistema pueden ser cargados. El registro de carga es un proceso importante durante la etapa de arranque en la medida en que el Kernel puede comenzar la transición de este a espacio de usuario. El bloque que contiene el sistema de archivos raíz puede ser codificado en el Kernel cuando éste es compilado o puede ser transferido como un argumento en una línea de comando del gestor de arranque utilizando la etiqueta “root=”. Los siguientes son tres tipos de sistemas de archivo raíz que pueden encontrarse en sistemas embebidos:
El Disco RAM Inicial.
Sistemas de archivos basados en Redes que emplean NFS (Network File System).
Los sistemas de archivos raíz normalmente son utilizados para compilaciones de depuración los otros dos para compilaciones de producción. El disco RAM simula un dispositivo de bloque utilizando la memoria del sistema; también puede ser utilizado para cargar sistemas de archivos generando una imagen de éstos, el disco RAM puede ser utilizado como Raíz (Initrd). Initrd es un concepto poderoso y tiene una amplia gama de usos especialmente en las primeras etapas de diseño de Linux Embebido cuando no se cuenta con un driver para Flash pero las aplicaciones se encuentran listas para hacer pruebas. Si se quiere que el Kernel cargue un Initrd, se debe configurar el Kernel durante el proceso de compilación con la opción CONFIG_BVLK_DEV_INITRD. Como se sabe la imagen del Initrd es cargada solamente con la imagen del Kernel; éste debe transferir la dirección de inicio y final del Initrd empleando argumentos en líneas de comandos. Una vez realizado esto el Kernel cargará un sistema de archivos raíz cargado en el Initrd. A pesar de la utilidad el Initrd una vez se ha utilizado es desechado, la necesidad de utilizar Initrd radica en el hecho de que se requiere para cargar otro sistema de archivos, a simple vista parece innecesario pero teniendo en cuenta que para cargar un sistema de archivos desde un dispositivo de almacenamiento se requiere el driver del éste, pero este driver se encuentra en el sistema de archivos raíz del Kernel que no ha sido montado, este inconveniente se convierte en la paradoja de la gallina y el huevo, la solución es incluir el driver como un módulo en el Initrd, una vez que éste es cargado entonces el módulo del driver puede ser accedido entonces si se puede cargar la raíz desde el dispositivo y se desecha el Initrd.
1.8. Realizando la llamada inicial (Initcall) y liberando la memoria inicial.
Si se observa el script del enlazador de cualquier arquitectura, este tendrá una sección de arranque, el inicio y fin de esta sección se marca con: __init_begin e __init_end. La idea de este espacio es el de contener texto y datos que puedan ser desechados una vez el sistema ha arrancado y ya no sean necesarios. Un ejemplo de esto puede ser las funciones de inicialización de drivers. La idea de colocar todas estas funciones juntas es la de que el bloque de memoria ocupada por estas sea significativo como para que quede disponible en forma de páginas libres.
Linux también proporciona una forma de agrupar funciones que deben ser llamadas durante el arranque del sistema, esto se logra declarando las funciones con la directiva __initcall, Estas funciones automáticamente serán llamadas durante el arranque por lo tanto no requieren ser insertadas en el código de arranque.
1.9. Moviéndose hacia el espacio de Usuario.
El Kernel que se ejecuta en el contexto del proceso de arranque salta al espacio de usuario superponiéndose (empleando la función execve) con el ejecutable de la
imagen de un programa especial conocido como Init. Este ejecutable normalmente se encuentra en la Raíz en el archivo /sbin.
1.10. Inicialización del Espacio de Usuario.
El espacio de usuario depende de la distribución que se utilice, la responsabilidad del Kernel se limita a la transición del proceso de arranque; lo que hace el proceso de arranque y como inicia los servicios depende de la distribución. A continuación se presenta el modelo genérico de Linux que asume que el proceso de inicio es /sbin/init, y es muy similar a la secuencia de inicialización de la variante de UNIX conocida como V UNIX.
El proceso /sbin/init y el /etc/inittab.
El proceso Init es muy importante para el Kernel, cumple con las siguientes características:
Nunca puede ser eliminado, Linux provee una señal llamada SIGKILL que puede terminal la ejecución de cualquier proceso pero no puede hacerlo con el proceso Init.
Cuando un proceso inicia otro proceso éste se convierte en el hijo del proceso inicial, esta relación es importante puesto que si el padre es eliminado antes que el hijo, el Init adopta al proceso huérfano.
El Kernel informa el comienzo de eventos especiales por medio de señales, por ejemplo si se presiona Ctrl-Alt-Del en el teclado del sistema, esto hace que el Kernel envíe una señal al proceso Init que por lo general provoca un apagado general.
El proceso Init puede ser configurado en cualquier sistema utilizando el archivo inittab que normalmente se encuentra en la siguiente dirección: /etc. El Init lee éste archivo y realiza las acciones descritas en éste de forma secuencial. Init también decide el estado del sistema, que se conoce como nivel de ejecución (Run Level) y es mandado al inittab como un argumento, si no se envía ninguno se emplea el nivel de ejecución designado por defecto, entre los niveles de ejecución disponibles están:
0. Sistema en alto.
1. Modo de Mono-Usuario (para propósitos administrativos). 2. Modo Multi-Usuario con capacidades de red restringidas. 3. Modo Multi-Usuario completo.
4. Sin Uso.
5. Modo Gráfico (X11™). 6. Estado de Reinicio.
El archivo Inittab tiene un formato especial compuesto generalmente por los siguientes detalles:
El nivel de ejecución por defecto.
Las acciones a realizar cuando Init se mueve a nivel de ejecución. Normalmente se invoca el script /etc/rc.d/rc con el nivel de ejecución como argumento.
El proceso que necesita ser ejecutado durante el arranque, normalmente se trata del archivo /etc/rc.d/rc.sysinit.
Init puede volver a la “vida” un proceso si esto se encuentra configurado en el archivo Inittab. Esta característica se emplea por ejemplo en el proceso de autenticación una vez que el usuario ha cerrado la sesión y se quiere volver a ingresar.
Acciones para manejar eventos especiales como Ctrl-Alt_Del o una falla de energía.
El archivo rc.sysinit.
Este realiza la inicialización del sistema antes de que los servicios sean iniciados, en un sistema embbebido este archivo realiza lo siguiente:
Carga sistemas de archivos especiales como proc, ramf, entre otros.
Crea directorios y links si es necesario.
Designa el nombre del Host para el sistema.
Ajusta la configuración de red del sistema. 1.11. Iniciando los Servicios.
Como se ha mencionado previamente el script /etc/rc.d/rc se responsabiliza de iniciar los servicios. Estos se definen como una función que controla un proceso del sistema. Al utilizar los servicios, un proceso puede ser detenido, reiniciado o en funcionamiento, entonces su estado puede ser requerido. Los servicios normalmente se encuentran en directorios basados en niveles de ejecución, y se comportan dependiendo del nivel de ejecución seleccionado. Después de desempeñar los pasos anteriores, en Init inicia un programa de autenticación, puede ser a través de un administrador de ventas en el display gráfico
El código inicial de arranque del Kernel también presenta diferencias que dependen de la plataforma donde se ejecute, éste gestiona su propia inicialización antes de generar el ambiente apropiado para la ejecución de código en C. Una vez se han realizado estas labores, el Kernel salta a la parte independiente de la arquitectura star_Kernel() que a su ves inicia la funcionalidad de alto nivel del Kernel, carga el Sistema de Archivos Raíz y el proceso de inicialización general.
CAPITULO 9: IMPLEMENTACIÓN DE EMBEDDED LINUX