Descarga la aplicación para disfrutar aún más
Vista previa del material en texto
Proyecto Fin de Carrera Ingeniería de Telecomunicación Formato de Publicación de la Escuela Técnica Superior de Ingeniería Autor: F. Javier Payán Somet Tutor: Juan José Murillo Fuentes Dep. Teoría de la Señal y Comunicaciones Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2013 Trabajo Fin de Master Master Universitario en Ingeniería Aeronáutica Creación Sistemas Operativos embebidos personalizados basados en Linux para SoC con FPGA integrada Autor: Ángel Cea Fons Tutor: María de los Ángeles Martín Prats Dep. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2017 Trabajo Fin de Master Master Universitario en Ingeniería Aeronáutica Creación Sistemas Operativos embebidos personalizados basados en Linux para SoC con FPGA integrada Autor: Ángel Cea Fons Tutor: María de los Ángeles Martín Prats Profesor Titular Dep. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla Sevilla, 2017 Trabajo Fin de Master: Creación Sistemas Operativos embebidos personalizados basados en Linux para SoC con FPGA integrada Autor: Ángel Cea Fons Tutor: María de los Ángeles Martín Prats El tribunal nombrado para juzgar el trabajo arriba indicado, compuesto por los siguientes profesores: Presidente: Vocal/es: Secretario: acuerdan otorgarle la calificación de: El Secretario del Tribunal Fecha: Resumen En el mundo de la ingeniería la electrónica está cobrando cada vez una mayor importancia debido,entre otros motivos, a la creciente automatización de procesos, el crecimiento en número y complejidad de los sistemas de aviónica embarcados en las aeronaves y la marcada tendencia hacia el All Electric Aircraft, que requiere la sustitución de sistemas mecánicos por sistemas electrónicos. Aunque suele pasar desapercibido, los sistemas embebidos tienen una importancia capital en el mundo de la aviación, controlando y supervisando procesos de una manera eficiente. El problema de estos sistemas, por lo general, subyace en su limitada potencia de procesamiento. Aquí es donde entra en juego lo que se va a tratar en el presente documento, el cual contendrá información acerca del desarrollo de una distribución de un sistema operativo (Linux) personalizada con Yocto Project, que permita satisfacer las necesidades pertinentes de forma eficiente, lo que nos permite no desaprovechar potencia de cálculo y además personalizar las herramientas y utilidades de las que se quiere dotar al sistema embebido. I Índice Resumen I Comandos VII 1 Introducción 1 1.1 Sistemas embebidos 1 1.1.1 ZedBoard 3 1.2 Linux embebido 4 1.2.1 Código abierto (Open source) 4 1.2.2 Licencias Open source 5 1.2.3 Los cuatro elementos de Linux embebido 5 Toolchain 5 2 Toolchain 7 2.1 Tipos de Toolchain 7 2.2 Elección de la librería de C 7 2.3 Crosstool-NG 8 2.3.1 Instalación de crosstool-NG 8 2.3.2 Anatomía de una toolchain 12 2.3.3 El directorio sysroot, la librería y los archivos de cabecera 14 2.3.4 Librerías, componentes de la librería de C 14 3 Bootloader 19 3.1 ¿Cuál es la función del bootloader? 19 3.2 La secuencia de arranque 19 3.2.1 Fase 1: código ROM 19 3.2.2 Fase 2: SPL 20 3.2.3 Fase 3: TPL 20 3.3 Del bootloader al kernel 21 3.4 Device trees 21 3.4.1 La propiedad reg 23 3.4.2 Phandles e interrupciones 24 3.4.3 Inclusión de archivos en los árboles de dispositivos 24 3.4.4 Elección de un bootloader 26 4 El kernel 29 4.1 ¿Qué es el kernel? 29 III IV Índice 4.2 Elección del kernel 29 4.3 Configuración del kernel 32 4.4 Módulos kernel 33 4.5 Compilando 33 4.5.1 Compilando la imagen del kernel 33 4.5.2 Compilando arboles de dispositivos 36 4.5.3 Compilando módulos kernel 36 4.5.4 Limpiando fuentes del kernel 36 5 El sistema de archivos raiz 39 5.1 Contenido del sistema de archivos raiz 39 5.2 Estructura del directorio 40 5.3 Permisos de acceso POSIX a archivos 40 5.4 Programas para el sistema de archivos raíz 41 5.4.1 El programa init 41 5.4.2 Shell 42 5.4.3 Utilidades 42 5.4.4 Busybox 43 5.4.5 Construyendo Busybox 43 5.4.6 Librerías para el sistema de archivos raiz 45 5.4.7 Nodos de dispositivos 45 5.4.8 El sistema de archivos proc y sys/ 45 5.4.9 Módulos kernel 47 5.5 Transfiriendo el sistema de archivos raíz al dispositivo de destino 47 6 Yocto Project 49 6.1 ¿Qué es Yocto Project? 49 6.2 Instalación de Yocto Project 49 6.3 Configuaración de Yocto Project 51 6.4 Capas 53 6.4.1 Creación de una nueva capa 54 6.5 Modulos kernel 60 6.5.1 Compilando modulos kernel con Yocto Project 61 6.5.2 Drivers 66 6.5.3 Creando un character device driver 70 7 Construyendo con Yocto Poject 81 7.1 Construyendo para la ZedBoard 81 7.1.1 Antes de intentar realizar la construcción 81 7.1.2 Construyendo 83 7.1.3 Creando una capa para la ZedBoard 87 7.1.4 Despliegue en la zedboard 90 7.1.5 Prueba del sistema con QEMU 92 7.2 Construyendo para la enclustra 93 8 Conclusiones 97 9 Futuras líneas de investigación 99 Índice V Índice de Figuras 101 Índice de Tablas 103 Índice de Códigos 105 Bibliografía 109 Comandos En esta lista se incluirán comandos utilizados en el documento para facilitar la lectura a personas que no estén familiarizadas con la terminal de Linux, pero este apartado no se dedicará a explicar con detalle y detenimiento ni el funcionamiento ni la sintaxis de los comandos, para ello buscar información en fuentes externas a este documento. sudo Colocado delante de otros comandos permite ejecutarlos como superusua- rio tar Comando que sirve pare realizar ta- reas de compresión y descompresión cd <destino> Realiza el cambio al directorio que se le indique a continuación (change directory) pwd > Muestra la ubicación del directorio actual (Print Working Directory) make <opciones> Permite ejecutar archivomakefile en el directorio actual mv <opciones> <origen> <destino> Mueve y renombra ficheros o direc- torios entre otras funciones rm <opciones> <archivo1> <archivo2> Permite eliminar directorios y archi- vos dependiendo de las opciones que se le indiquen cp <opciones> <origen> <destino> Copia directorios y archivos origen en el destino atendiendo a las opcio- nes que se le indiquen cat <opciones> <archivo> Lee un archivo y muestra su conteni- do tail <opciones> <archivo> Muestra las últimas líneas de un ar- chivo ls <opciones> <destino> Muestra un listado de directorios y archivos dentro del directorio <des- tino>, y si no se le indica destino el listado lo hace del directorio actual lsmod Muestra un listado de los módulos kernel instalados actualmente en el sistema insmod <módulo kernel> Instala módulo kernel en el sistema VII VIII Comandos rmmod <módulo kernel> Desinstala módulo kernel del siste- ma df Muestra los sistemas de archivos montados, su espacio disponible y el espacio utilizado dmesg Muestra mensajes del kernel git clone <dirección> Realiza una clonación del reposito- rio que se encuentre en la dirección indicada git checkout <rama> Cambia la rama a la que apunta el re- positorio a la indicada en el comando git branch Muestra la rama a la que apunta ac- tualmente el repositorio git branch -r Muestra las ramas disponibles mknod Crea nodo de dispositivo. Es necesa- rio tener permisos de superusuario mount Para montar directorios y archivos. Es necesario tener permisos de su- perusuario umount Para desmontar directorios y archi- vos. Es necesario tener permisos de superusuario sudo apt-get install Realiza la instalación de la aplica- ción indicada a continuación ./<aplicación> Ejecuta la aplicación que se le indi- que source Ejecuta la aplicación que se le in- dique a continuación guardando los cambios realizados en elas variables de entorno tree <opciones> <dirección> Muestra estructura de directorios y ficheros en forma de árbol de la di- rección que se le indique <comando> | grep <caracteres> Toma como entrada la salida del co- mando anterior y muestra los resul- tados que contienen los caracteres indicados fdisk <opciones> Permite formatear particiones y obte-ner información acerca de dispositi- vos del almacenamiento conectados al sistema find <opciones> Comando que, como su propio nom- bre indica, sirve para realizar búsque- das de distinta índole dependiendo de las opciones que se añadan al co- mando 1 Introducción En este capítulo se aclarará la definición de sistema embebido y se mostrarán los componentesque habitualmente forman parte de los sistemas embebidos. Se introducirá a la ZedBoard como el sistema embebido que se utilizará para el desarrollo del presente proyecto y se hablará sobre lo que es Linux embebido y las partes que lo conforman. 1.1 Sistemas embebidos Un sistema embebido o empotrado (integrado, incrustado) es un sistema de computación diseñado para realizar una o algunas pocas funciones dedicadas, frecuentemente en un sistema de computación en tiempo real. Al contrario de lo que ocurre con los ordenadores de propósito general (como por ejemplo una computadora personal o PC) que están diseñados para cubrir un amplio rango de necesidades, los sistemas embebidos se diseñan para cubrir necesidades específicas. En un sistema embebido la mayoría de los componentes se encuentran incluidos en la placa base (tarjeta de vídeo, audio, módem, etc.) y muchas veces los dispositivos resultantes no tienen el aspecto de lo que se suele asociar a una computadora. Algunos ejemplos de sistemas embebidos podrían ser dispositivos como un taxímetro, un sistema de control de acceso, la electrónica que controla una máquina expendedora o el sistema de control de una fotocopiadora entre otras múltiples aplicaciones. Como ya se ha nombrado anteriormente, estos sistemas suelen disponer de una potencia de cálculo limitada y pueden llevar a cabo generalmente también una variedad de tareas limitadas en comparación a un pc habitual. Debido a esto cobra mucho sentido desarrollar una distribución de un sistema operativo que esté optimizado en cuanto a consumo de recursos y solo incluya las funcionalidades que se vayan a utilizar, ahorrando así espacio de almacenamiento requerido y complejidad del sistema operativo. Por lo general, los Sistemas Embebidos se pueden programar directamente en el lenguaje ensam- blador del microcontrolador o microprocesador incorporado sobre el mismo, o también, utilizando los compiladores específicos que utilizan lenguajes como C o C++ y en algunos casos, cuando el tiempo de respuesta de la aplicación no es un factor crítico, también pueden usarse lenguajes interpretados como Java. Las arquitecturas más empleadas en sistemas embebidos contienen en general los siguientes componentes: 1. Microprocesador: Es el encargado de realizar las operaciones de cálculo principales del sistema. Ejecuta código para realizar una determinada tarea y dirige el funcionamiento de los demás elementos que le rodean, a modo de director de una orquesta. 2. Memoria principal: En ella se encuentra almacenado el código de los programas que el sistema puede ejecutar así como los datos. Su característica principal es que debe tener un 1 2 Capítulo 1. Introducción acceso de lectura y escritura lo más rápido posible para que el microprocesador no pierda tiempo en tareas que no son meramente de cálculo. Al ser volátil el sistema requiere de un soporte donde se almacenen los datos incluso sin disponer de alimentación o energía. 3. Caché: Memoria más rápida que la principal en la que se almacenan los datos y el código accedido recientemente. Dado que el sistema realiza microtareas, muchas veces repetitivas, la caché hace ahorrar tiempo, ya que no hará falta ir a memoria principal si el dato o la instrucción ya se encuentra en la caché. Dado su alto precio tiene un tamaño muy inferior comparado la memoria principal. En el interior del chip del microprocesador se encuentra una pequeña caché (L1), pero normalmente se tiene una mayor en otro chip de la placa madre (L2). 4. Disco duro: En él la información no es volátil y además puede conseguir capacidades muy elevadas. A diferencia de la memoria que es de estado sólido éste solía ser magnético, lo que hacía inviable a veces su inclusión en sistemas embebidos, debido a su excesivo tamaño. Otro problema que presentan los dispositivos magnéticos, a la hora de integrarlos en sistemas embebidos, es que llevan partes mecánicas móviles, lo que los hace inviables para entornos donde estos estarán expuestos a ciertas condiciones de vibración. Estos problemas han sido subsanados en los últimos tiempos con la creación de tarjetas SD y micro SD (las cuales tienen un tamaño muy reducido) con capacidades cada vez mayores, disponiendo en muchos casos de más capacidad que discos duros convencionales de hace una década. 5. Disco flexible: Su función era la de almacenamiento, pero con discos con capacidades mucho más pequeñas y la ventaja de su portabilidad. Normalmente se encontraban en computadores personales estándar pero no así en una PC embebida. En nuestros días llevan varios años en total desuso en PC comunes. 6. BIOS-ROM: BIOS(Basic Input Output System, sistema básico de entrada y salida) es código que es necesario para inicializar la computadora y para poner en comunicación los distintos elementos de la placa madre. La ROM (Read Only Memory, memoria de sólo lectura no volátil) es un chip donde se encuentra el código BIOS. 7. CMOS-RAM: Es un chip de memoria de lectura y escritura alimentado con una pila donde se almacena el tipo y ubicación de los dispositivos conectados a la placa madre (disco duro, puertos de entrada y salida, etc.). Además contiene un reloj en permanente funcionamiento que ofrece al sistema la fecha y la hora. 8. Chipset: Chip que se encarga de controlar las interrupciones dirigidas al microprocesador, el acceso directo a memoria (DMA) y al bus ISA, además de ofrecer temporizadores, etc. Es frecuente encontrar la CMOS-RAM y el reloj de tiempo real en el interior del Chip Set. 9. Entradas al sistema: Pueden existir puertos para ratón, teclado, vídeo en formato digital, comunicaciones serie o paralelo, etc. 10. Salidas del sistema: Puertos de vídeo para monitor o televisión, pantallas de cristal líquido, altavoces, comunicaciones serie o paralelo, etc. 11. Ranuras de expansión para tarjetas de tareas específicas: Pueden no venir incorporadas en la placamadre, como pueden ser más puertos de comunicaciones, acceso a red de computadoras vía LAN (Local Area Network, red de área local) o vía red telefónica: básica, RDSI (Red Digital de Servicios Integrados), ADSL (Asynchronous Digital Subscriber Loop, Lazo Digital Asíncrono del Abonado), Cablemódem, etc. Un PC estándar suele tener muchas más ranuras de expansión que un sistema embebido. Las ranuras de expansión están asociadas a distintos tipos de bus: VESA, ISA, PCI, NLX (ISA + PCI), etc. Finalmente se le va a dedicar un espacio en esta sección a nombrar las ventajas que tienen estos sistemas embebidos: 1.1 Sistemas embebidos 3 • Posibilidad de utilización de sistemas operativos potentes que ya realizan numerosas tareas: comunicaciones por redes de datos, soporte gráfico, concurrencia con lanzamiento de threads, etc. Estos sistemas operativos pueden ser los mismos que para PC compatibles (Linux, Windows, MS-DOS) con fuertes exigencias en hardware o bien ser una versión reducida de los mismos con características orientadas a los PC embebidos. • Al utilizar los Sistemas Embebidos, se pueden encontrar fácilmente herramientas de desarrollo de software potentes, así como numerosos programadores que las dominan, dada la extensión mundial de las aplicaciones para computadores compatibles. • Reducción en el precio de los componentes hardware y software debido a la gran cantidad de computadores en todo el mundo. 1.1.1 ZedBoard La Zedboard (Zynq Evaluation Development Board) va a ser la placa que va a ser utilizada en este proyecto y para la cual se va a desarrollar el sistema operativo. La ZedBoard es una placa de desarrollo para el Xilinx Zynq-7000 Extensible Processing Plat- form (EPP). El Xilinx Zynq-7000 es un SoC que combina microprocesadoresde doble núcleo ARM Cortex-A9, estructura de FPGA y periféricos claves en un solo dispositivo. El procesador y la estructura del FPGA se comunican con más de 10,000 interconexiones internas, ofreciendo un rendimiento entre el microprocesador y FPGA que es físicamente imposible de lograr con un procesador discreto y un FPGA implementado en una tarjeta de circuito impreso. Figura 1.1 Diagrama del SoC Zynq-7000. Una FPGA (field-programmable gate array) es un circuito integrado diseñado para ser configu- rado por un cliente, diseñador o desarrollador. Es decir, es una matriz donde se distribuyen bloques lógicos que pueden ser programados para que se conecten de distinta manera y se puedan realizar muy diversas tareas (se puede literalmente modificar las interconexiones entre puertas y bloques lógicos). Es decir, con esta placa se tiene la posibilidad de programar las tareas que realizará el procesador mediante software y la posibilidad de programar hardware para realizar tareas de una forma mucho más rápida y eficiente de lo que lo haría un procesador. Por ejemplo se podría dejar a la FPGA la 4 Capítulo 1. Introducción tarea de filtrado de señales de entrada, ya que en lugar de que el filtrado lo realice el procesador mediante la potencia de cálculo, lo haría la FPGA de forma "física" tras haberse programado las conexiones físicas que esta utiliza. El SoC Zynq no es llamado un “FPGA” porque es el procesador el que está a cargo del sistema, en lugar de la estructura del FPGA. Es decir, el sistema de procesamiento arranca primero y controla la funcionalidad de la estructura del FPGA. Esto significa que los usuarios no tienen que estar profundamente familiarizados con técnicas de diseño de FPGA para ejecutar una aplicación en el subsistema del procesador del SoC Zynq. El SoC Zynq ofrece a los clientes la habilidad para crear sus diseños en C, C++ o SystemC usando el software de desarrollo de su elección y programar su diseño en el sistema de procesamiento del SoC Zynq. Si una parte de su diseño no se está ejecutando lo suficientemente rápido, los diseñadores pueden usar la herramienta Vivado High-Level Synthesis (HLS) de Xilinx o HANDEL-C de Mentor Graphics para traducir un algoritmo o parte de un algoritmo que desarrollaron a un nivel de C a VHDL y probar ese código ejecutándose en la sección de FPGA del SoC Zynq. Al descargar las funciones adecuadas del procesador a la estructura del FPGA y liberar el procesador para realizar las funciones que hace mejor, los clientes pueden alcanzar un incremento de 700 veces más rendimiento del sistema en comparación con los diseños basados en procesadores. 1.2 Linux embebido Para realizar este proyecto se utilizará Linux, que será el sistema operativo que se instalará en la ZedBoard. ¿Por qué utilizar este SO en nuestro dispositivo target y no otro?. A continuación se mostrarán algunos argumentos en favor de Linux: • Linux es capaz de lidiar con muchas funcionalidades, tanto necesarias en este proyecto como otras que no lo son (por lo menos en principio). Soporte para distintos tipos de conectividad (USB, Wi-Fi, Bluetooth etc), para gran variedad de dispositivos de almacenamiento, soporte para dispositivos multimedia y mucho más. • Linux es de código abierto, lo que implica que se dispone de la libertad de adquirir código fuente y modificarlo a nuestro antojo. Es decir, se pueden agregar nuevas funcionalidades que no estaban en el código fuente y/o eliminar otras que no sean necesarias en el proyecto para reducir los requisitos de procesamiento y memoria. • Linux tiene el soporte de una gran comunidad muy activa, lo que garantiza que va a estar actualizado, soportará las últimas tecnologías y se tendrá acceso a multitud de información que, entre otras cosas, ahorrará tiempo en la resolución de los problemas que vayan surgiendo en el desarrollo de este proyecto. • Las licencias de código abierto de Linux garantizan que se tiene acceso al código fuente. 1.2.1 Código abierto (Open source) Se va a hablar de manera escueta sobre qué significa que la mayoría de los componentes de Linux embebido sean Open source y qué tipos de licencia se pueden encontrar en este tipo de código. Open source tiene varios significados, o más bien, el significado de open source tiene varias implicaciones. La primera implicación es el hecho de que el código Open source se puede adquirir gratuitamente y desplegarlo en el sistema que se requiera sin ningún coste, y esto es justo lo que garantizan las licencias de software de código abierto, pero la más importante es la segunda implicación. La segunda implicación del significado de Open source es que se tiene total libertad de adquirir el código fuente y modificarlo sin ningún tipo de restricción. No confundir código Open source con licencias de shareware que permiten copiar y ejecutar archivos binarios pero no permiten el acceso al código fuente o de otras licencias que permiten el 1.2 Linux embebido 5 uso del software en ciertas condiciones como por ejemplo que su uso sea personal y no comercial. Estos dos últimos ejemplos no son Open source. 1.2.2 Licencias Open source Las licencias Open source se dividen básicamente en dos categorías: • GPL (General Public License) • BSD (Berkeley Software Distribution) La licencia BSD especifica que se puede modificar el código fuente y utilizarlo en cualquier sistema siempre que no se modifiquen los términos de la licencia. Es decir, cualquier usuario podrá tomar software bajo esta licencia libremente , sólo respetando la autoría del software, pero pudiendo decidir si liberar o no los cambios que se hayan realizado. Lo que el desarrollador logra es que su código sirva para cualquier propósito y, con código abierto o sin él, el siguiente desarrollador pueda elegir con libertad qué hacer con su propio trabajo. En cambio la licencia GPL también obliga a respetar la autoría, pero en este caso se debe liberar el código con la misma licencia. Esto quiere decir que el usuario que utilice software con esta licencia deberá publicar el resultado de su trabajo y además bajo la misma licencia. 1.2.3 Los cuatro elementos de Linux embebido Cualquier proyecto de creación y desarrollo de un sistema operativo para ser instalado en un sistema embebido comienza con la obtención, personalización y el despliegue de estos 4 elementos: 1. Toolchain: Está compuesto del compilador y otras herramientas que se necesitan para crear el código que se instalará en el dispositivo final. 2. Bootloader: Este elemento es necesario para inicializar la placa y para cargar y "arrancar" el kernel de Linux. 3. Kernel: Es el elemento central del sistema, gestiona los recursos del sistema y hace de interfaz con el hardware. 4. Sistema de archivos raíz (root filesystem): Estos son los archivos que contienen información crítica sobre el sistema como pueden ser librerías y programas que arrancan una vez que el kernel ha completado su inicialización Los capítulos dedicados a cada elemento de Linux embebido por separado estarán basados en el libro Simmonds, C. (2015).Mastering Embedded Linux Programming. Packt Publishing Ltd, por lo que en él se puede encontrar información similar y ampliada. Es un buen libro para consulta de muchos de los temas tratados en el presente documento. A continuación se hará una ampliación breve sobre la toolchain, aunque se ahondará más en capítulos posteriores. Toolchain Una toolchain es el conjunto de herramientas que compila código fuente en ejecutables que se pueden ejecutar en el dispositivo de destino e incluye un compilador, un enlazador (linker) y bibliotecas de tiempo de ejecución. Inicialmente, se necesita para construir los otros tres elementos de un sistema Linux incorporado: el bootloader, el kernel y el sistema de archivos raíz. Tiene que ser capaz de compilar código escrito en ensamblador, C y C ++ ya que estos son los lenguajes utilizados en los paquetes de código abierto base. Por lo general, las toolchain para Linux se basan en componentes del proyectoGNU (http://www.gnu.org).El proyecto GNU es un proyecto colaborativo de software libre con el objetivo de crear un sistema operativo completamente libre: el sistema GNU. Una toolchain estandar de GNU está compuesto de tres componentes principales: 6 Capítulo 1. Introducción • Binutils: Un conjunto de utilidades binarias incluyendo el ensamblador, y el linker. Está disponible en http://www.gnu.org/software/binutils/. • GNUCompiler Collection (GCC): Estos son los compiladores para C y otros lenguajes que, dependiendo de la versión de GCC, incluyen C ++, Objective-C, Objective-C ++, Java, Fortran, Ada y Go. Todos ellos utilizan un back-end común que produce el código ensamblador del que se alimenta el ensamblador de GNU. Está disponible en http://gcc.gnu.org/. • Librerías de C: Una API estandarizada basada en la especificación POSIX, que es la interfaz principal de las aplicaciones con el kernel del sistema operativo. 2 Toolchain En este capítulo se profundizará sobre la toolchain y su utilización. Se nombrarán los tipos detoolchain, cuál será la mejor elección en este proyecto y se introducirá la utilización de una toolchain específica para mostrar la manera habitual de trabajar con estas herramientas y poder tener una idea un poco más profunda de esta parte de Linux embebido. 2.1 Tipos de Toolchain Las toolchain se pueden dividir en dos tipos: • Nativa: Este tipo de toolchain se ejecuta en el mismo tipo de sistema que el sistema para el que se generan los programas. Por poner un ejemplo sencillo, si vamos a utilizar la toolchain para crear programas que serán ejecutados en el mismo ordenador o un ordenador similar (misma arquitectura de procesador y similar arquitectura general). • Cruzada: Este tipo de toolchain se ejecuta en un sistema de distinto tipo del sistema donde se ejecutarán los programas que se creen con la toolchain. En el caso de este proyecto tendremos que elegir una toolchain de tipo cruzada, ya que el sistema donde desarrollaremos el sistema operativo (nuestro sistema host) será un sistema con arquitectura x86 de 64 bits (un procesador intel más específicamente), mientras que el sistema donde se ejecutarán los programas que se creen con la toolchain (el target) será la ZedBoard que cuenta con arquitectura ARM. Se podría directamente hacer el desarrollo de los programas directamente sobre la Zedboard, con una toochain de tipo nativa, pero parece más adecuado, no solo por potencia y recursos sino por comodidad realizar el desarrollo en un ordenador convencional utilizando una toolchain de tipo cruzada. 2.2 Elección de la librería de C Las librerías son archivos con rutinas que realizan tareas comunes (y a veces muy complejas), que ahorran infinidad de trabajo al poder ser llamadas desde otros programas y no teniendo así que ser programadas cada vez que se necesita de una funcionalidad en concreto. Las bibliotecas estándares, como puede ser la biblioteca estandar de C (también conocida como libc) es exacta- mente lo anteriormente comentado, solo que esta estandarizado por laOrganización Internacional para la Estandarización (ISO) para que de esta manera pueda ser utilizado por gran cantidad de aplicaciones. 7 8 Capítulo 2. Toolchain El lenguaje C es el que hace de interfaz entre la aplicación (que está dentro del espacio de usuario) y el kernel, por tanto será muy importante tener unas librerías de C que permitan la comunicación entre los programas generados y el kernel. Hay muchas opciones en cuanto a librerías de C se refiere, a continuación se muestran las principales opciones: • glibc: Es la librería estándar de C de GNU. • eglibc: Las siglas significan embedded glibc. Agrega opciones de configuración y soporte para arquitecturas que no están cubiertas por gilbc. • ulibc: La "u" de las siglas en realidad es una "µ" haciendo referencia a que es una librería de C para microcontroladores. • musl libc: Es una nueva librería de C para sistemas embebidos. Para este proyecto se ha elegido libc, ya que es la librería estándar y es quizás la opción más completa de todas las nombradas. 2.3 Crosstool-NG Hay gran cantidad y variedad de toolchain en el mercado. Por ejemplo se pueden encontrar toolchain del proveedor del SoC que se haya adquirido, toolchain para Linux creados por terceros como Mentor Graphics, o un SDK generado por una herramienta integrada para desarrollo embebido como Yocto Project. Se tiene que evaluar si la una toolchain preconstruida satisface las necesidadesn del proyecto, y si nó se tendrá que construir una toolchain. Para mostrar la creación de la toolchain se trabajará con crosstool-NG ya que es una alternativa sencilla que encapsula el proceso en scripts. Es importante que se mantenga la toolchain una vez elegida o creada ya que el cambio inconsistente de compiladores y de librerías es una fuente de errores importante. 2.3.1 Instalación de crosstool-NG Para instalar los paquetes necesarios para el correcto funcionamiento de crosstool-NG se tendrá que ejecutar el siguiente comando en la consola: Código 2.1 Instalación de paquetes para crosstool-NG. $ sudo apt-get install automake bison chrpath flex g++ git gperf gawk libexpat1-dev libncurses5-dev libsdl1.2-dev libtool python2.7-dev texinfo Tras esto habrá que descargar la última versión de crosstool-NG visitando el enlacehttp://crosstool- ng.org/download/crosstool-ng. En el momento de escritura del presente documento la última ver- sión disponible es crosstool-ng-1.23.0. La instalación se realiza con los siguientes comandos, los cuales se pueden encontrar en la dirección http://crosstool-ng.github.io/docs/install/ . Código 2.2 Instalación de crosstool-NG. $ tar xf crosstool-ng-1.23.0.tar.bz2 $ cd crosstool-ng-1.23.0 $ ./configure --enable-local 2.3 Crosstool-NG 9 $ make $ make install Como se puede observar en el código 2.2 la primera línea descomprime el archivo descargado, la segunda línea cambia de directorio al directorio que ha quedado después de descomprimir el archivo. Al hacer make puede dar un error, en este caso dio el error configure: error: missing required tool: help2man que se soluciona simplemente insertando el comando $ sudo apt-get install help2man. Luego el comando $ ./configure –enable-local permite instalar crosstool-NG en en directorio en el que se encuentra situado el sistema (crosstool-ng-1.23.0 ) y por lo tanto ejecutarlo también en este mismo directorio. Si ejecutamos el comando $ ./ct-ng se especificarán los distintos comandos que se pueden utilizar. Se puede ver lo anteriormente comentado en el código 2.3. Código 2.3 Lista de comandos disponibles para crosstool-NG. angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng This is crosstool-NG version crosstool-ng-1.23.0 Copyright (C) 2008 Yann E. MORIN <yann.morin.1998@free.fr> This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See below for a list of available actions, listed by category: Configuration actions: menuconfig - Update current config using a menu based program nconfig - Update current config using a menu based program oldconfig - Update current config using a provided .config as base extractconfig - Extract to stdout the configuration items from a build.log file piped to stdin savedefconfig - Save current config as a mini-defconfig to ${ DEFCONFIG} defconfig - Update config from a mini-defconfig ${DEFCONFIG} (default: ${DEFCONFIG}=./defconfig) saveconfig - Save current config as a preconfigured target show-tuple - Print the tuple of the currently configured toolchain Preconfigured toolchains (#: force number of // jobs): list-samples - prints the list of all samples (for scripting) show-<sample> - show a brief overview of <sample> (list with list- samples) <sample> - preconfigure crosstool-NG with <sample> (list with list-samples) build-all[.#] - Build *all* samples (list with list-samples) and install in ${CT_PREFIX} (set to ~/x-tools by default) Build actions (#: force number of // jobs):10 Capítulo 2. Toolchain source - Download sources for currently configured toolchain build[.#] - Build the currently configured toolchain list-steps - List all build steps Clean actions: clean - Remove generated files distclean - Remove generated files, configuration and build directories Distribution actions: check-samples - Verify if samples need updates due to Kconfig changes update-samples - Regenerate sample configurations using the current Kconfig wiki-samples - Print a DokuWiki table of samples updatetools - Update the config tools Environment variables (see /home/angel/Desktop/croostool-ng/crosstool-ng -1.23.0/docs/0 - Table of content.txt): STOP=step - Stop the build just after this step (list with list- steps) RESTART=step - Restart the build just before this step (list with list-steps) CT_PREFIX=dir - install samples in dir (see action "build-all", above ). V=0|1|2|<unset> - <unset> show only human-readable messages (default) 0 => do not show commands or human-readable message 1 => show only the commands being executed 2 => show both Use action "menuconfig" to configure your toolchain Use action "build" to build your toolchain Use action "version" to see the version See "man 1 ct-ng" for some help as well Si se quiere conocer una lista de ejemplos de toolchain preconfigurada se puede hacer con el comando $ ./ct-ng list-samples. Ya que la zedboard cuenta con un procesador de 2 nucleos ARM Cortex A9, del listado de ejemplos de toolchain preconfiguradas la más similar es la opción arm-cortexa9_neon-linux-gnueabihf. Se puede consultar la configuración de esta opción como se realiza en el código 2.4. Código 2.4 Configuración de arm-cortexa9_neon-linux-gnueabihf. angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng show- arm-cortexa9_neon-linux-gnueabihf IN config.gen/arch.in IN config.gen/kernel.in IN config.gen/cc.in IN config.gen/binutils.in IN config.gen/libc.in 2.3 Crosstool-NG 11 IN config.gen/debug.in [L.X] arm-cortexa9_neon-linux-gnueabihf OS : linux-4.10.8 Companion libs : gmp-6.1.2 mpfr-3.1.5 isl-0.18 mpc-1.0.3 expat-2.2.0 ncurses-6.0 binutils : binutils-2.28 C compilers : gcc | linaro-6.3-2017.02 Languages : C,C++ C library : glibc-2.25 (threads: nptl) Tools : gdb-7.12.1 Y para seleccionar esta configuración como la que se utilizará se ejecutará la siguiente linea de comando: Código 2.5 Elección de configuración para el target. angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng arm- cortexa9_neon-linux-gnueabihf Se pueden realizar cambios de configuración con el comando del código configuration-target. Código 2.6 Elección de configuración para el target. angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng menuconfig No se entrará en más detalles de mucho de los procesos ya que el objetivo de este documento se enfoca más en la creación de un sistema con Yocto Project y no con los elementos de Linux embebido por separado (Toolchain, bootloader, kernel y el sistema de archivos raíz). Aun así se trata de forma somera para que se tenga cierto conocimiento del funcionamiento del desarrollo de Linux embebido y para estar más familiarizado con las distintas herramientas de las que se disponen para desarrollar Linux embebido. Notar que la ejecución de $ ./ct-ng siempre se realiza desde el directorio angel@ubuntu: /Desktop/croostool- ng/crosstool-ng-1.23.0, es decir, la ejecución del comando siempre se realiza en el directorio donde se ha realizado la instalación de crosstool-NG. Se recomienda ejecutar $ ./ct-ng menuconfig y realizar los siguientes cambios de configuración. • En Paths and misc options, desabilitar Render the toolchain read-only. • En Target options seleccionar en Target Architecture la arquitectura arm, y luego en Floating point seleccionar hardware (FPU). • Dentro de C-library escribir en extra config for newlib –enable-obsolete-rpc. La primera opción es necesaria si se quieren añadir librerías a la toolchain tras la instalación. La segunda selecciona la opción óptima de la implementación de punto flotante para un procesador con FPU (floating point unit). Tras esto se puede usar crosstool-NG para obtener, configurar y contruir componentes para nuestro sistema de destino haciendo uso del comando $ ./ct-ng build. Tras unas decenas de minutos se encontrará la toolchain en el directorio /home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/ 12 Capítulo 2. Toolchain 2.3.2 Anatomía de una toolchain La toolchain se encuentra en el directorio /home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin y dentro de ella se encuentra el croscompilador arm-cortexa9_neon-linux-gnueabihf.gcc. Para hacer uso de él se tendrá que añadir al path como se muestra en el código 2.7. Código 2.7 Adición del croscompilador al path. PATH=~/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH Ahora, por ejemplo, se puede tomar un programa simple llamado holamundo como el que sigue. Código 2.8 Programa holamundo. #include <stdio.h> #include <stdlib.h> int main (int argc, char *argv[]) { printf ("Hola, mundo!\n"); return 0; } Se puede croscompilar el programa de la siguiente manera. Código 2.9 Croscompilación del programa holamundo. arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundo Y se puede comprobar que el archivo se ha croscompilado con éxito con el comando del código 2.10. Código 2.10 Información sobre el ejecutable holamundo. angel@ubuntu:~/Desktop$ file holamundo holamundo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/ Linux 3.2.0, not stripped Si se precisa más información sobre el croscompilador, como puede ser tener conocimiento sobre la versión o sobre como se ha configurado, se puede adquirir información como se muestra en el código 2.11, donde también se muestra la respuesta a los comandos: Código 2.11 Información sobre el croscompilador arm-cortexa9_neon-linux-gnueabihf-gcc. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc --version arm-cortexa9_neon-linux-gnueabihf-gcc (crosstool-NG crosstool-ng-1.23.0) 6.3.1 20170109 Copyright (C) 2016 Free Software Foundation, Inc. 2.3 Crosstool-NG 13 This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc -v Using built-in specs. COLLECT_GCC=arm-cortexa9_neon-linux-gnueabihf-gcc COLLECT_LTO_WRAPPER=/home/angel/x-tools/arm-cortexa9_neon-linux- gnueabihf/libexec/gcc/arm-cortexa9_neon-linux-gnueabihf/6.3.1/lto- wrapper Target: arm-cortexa9_neon-linux-gnueabihf Configured with: /home/angel/Desktop/crosstool-ng/crosstool-ng-1.23.0/. build/src/gcc-linaro-6.3-2017.02/configure --build=x86_64-build_pc- linux-gnu --host=x86_64-build_pc-linux-gnu --target=arm- cortexa9_neon-linux-gnueabihf --prefix=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf --with-sysroot=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/ sysroot --enable-languages=c,c++ --with-cpu=cortex-a9 --with-fpu= neon --with-float=hard --with-pkgversion='crosstool-NG crosstool-ng -1.23.0' --enable-__cxa_atexit --disable-libmudflap --disable- libgomp --disable-libssp --disable-libquadmath --disable-libquadmath- support --disable-libsanitizer --disable-libmpx --with-gmp=/home/ angel/Desktop/crosstool-ng/crosstool-ng-1.23.0/.build/arm- cortexa9_neon-linux-gnueabihf/buildtools --with-mpfr=/home/angel/ Desktop/crosstool-ng/crosstool-ng-1.23.0/.build/arm-cortexa9_neon- linux-gnueabihf/buildtools --with-mpc=/home/angel/Desktop/crosstool- ng/crosstool-ng-1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/ buildtools --with-isl=/home/angel/Desktop/crosstool-ng/crosstool-ng -1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/buildtools --enable- lto --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,- Bdynamic -lm' --enable-threads=posix --enable-plugin --enable-gold -- with-libintl-prefix=/home/angel/Desktop/crosstool-ng/crosstool-ng-1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/buildtools -- disable-multilib --with-local-prefix=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/ sysroot --enable-long-long Thread model: posix gcc version 6.3.1 20170109 (crosstool-NG crosstool-ng-1.23.0) Del código 2.11 la información más destacable es la siguiente: • –with-sysroot=/home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon- linux-gnueabihf/sysroot : Esta línea informa de cual es el directorio sysroot por defecto. • –enable-languages=c,c++: Esta línea indica que tenemos disponible los lenguajes C y C++. • –with-cpu=cortex-a9 : Indica que el código se ha croscompilado para cpu cortex-a9. • –with-float=hard: Genera codigos de operación para la unidad de punto flotante y usa los registros de VFP para los parámetros. • –enable-threads=posix: habilita los hilos de ejecución POSIX. 14 Capítulo 2. Toolchain Esta es la configuración por defecto del compilador, la cual se puede sobrescribir directamente en la línea de comandos, si se ve necesario. Si por ejemplo se quiere compilar para una CPU distinta, como por ejemplo la cortex A5, se haría con la línea de comando del código 2.12. Código 2.12 Sobreescribir configuración para una ejecución a través de linea de comando. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc -mcpu= cortex-a5 holamundo.c -o holamundo 2.3.3 El directorio sysroot, la librería y los archivos de cabecera El directorio sysroot es un directorio que contiene subdirectorios para las librerías, los archivos de cabecera y otros archivos de configuración. Como se ha visto, aparece cuando se llama a las opciones con las que se ha configurado el croscompilador mostrándose la línea –with-sysroot=/home/angel/x- tools/arm-cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/sysroot , que indica donde se encuentra este directorio. En el directorio sysroot se encontrará lo siguiente: • lib: Contiene los objetos compartidos para las librerias de C y el linker/loader dinámico llamado ld-linux. • usr/lib: los archivos de la librería estática para la librería estándar de C y otras librerías que pueden ser instaladas posteriormente. • usr/include: Este directorio contiene archivos de cabecera para las librerías. • usr/bin: Contiene programas de utilidad que se ejecutan en el destino como por ejemplo el comando ldd. • usr/share: Utilizada para la localización, contiene archivos de lectura que no dependen de la arquitectura, por lo tanto podría ser compartido por varias máquinas distintas, no siendo así con distintos S.O o distintas versiones de S.O. • sbin: proporciona la utilidad ldconfig que se encarga de controlar dónde Linux busca archivos en las librerías mientras esta ejecutando programas y por tanto optimizando la carga de los paths. 2.3.4 Librerías, componentes de la librería de C En informática, una biblioteca (del inglés library) es un conjunto de implementaciones funcio- nales (como por ejemplo leer del teclado o mostrar en pantalla), codificadas en un lenguaje de programación, que ofrece una interfaz bien definida para la funcionalidad que se invoca. A diferencia de un programa ejecutable, las funcionalidades que implementan las librerías no esperan ser utilizadas de forma autónoma, es decir, no ejecutaremos estas funcionalidades de forma autónoma, sino que estas funcionalidades serán utilizadas por otros programas, de forma independiente y pudiendo ser simultánea. Es una forma de ahorrar tiempo y código al no tener que volver a programar de nuevo ciertas funcionalidades dentro de cada nuevo programa, sino que para utilizar esas funcionalidades se pueden realizar llamadas a esas funciones. Se utiliza en informática indistintamente el termino librería y biblioteca para referirse al término original en inglés library. Para que gran cantidad de programas puedan hacer uso de librerías existen librerías estándar, que simplemente son, como su propio nombre indica, librerías que se toman de forma estándar para utilizar en los programas. Esto hace que muchos programas puedan implementar las funcionalidades de la misma manera. 2.3 Crosstool-NG 15 La librería de C no solo se compone de un archivo, sino que se compone de cuatro partes principales que juntas implementan la API de funciones POSIX. Una API (Application Programming Interfaces) es una especificación formal sobre como un módulo software se comunica con otro. Es decir, es un conjunto de comandos, rutinas, protocolos, funciones y procedimientos que permiten que un programa utilice ciertas funcionalidades. Es un concepto parecido a la librería solo que es más focalizado si hablamos en general. Sería como la parte de la librería a la que accede un programa mientras usa la biblioteca. Una API puede ser implementada por distintas librerías y muchas veces la implementación interna de las funciones queda oculta al público. POSIX es el acrónimo de Portable Operating System Interface, y la X viene de UNIX como seña de identidad de la API. POSIX es una norma escrita por la IEEE. Dicha norma define una interfaz estándar del sistema operativo y el entorno, incluyendo un intérprete de comandos (o "shell"), y programas de utilidades comunes para apoyar la portabilidad de las aplicaciones a nivel de código fuente. Una vez aclarados estos conceptos se indica a continuación los cuatro elementos de la librería de C: • libc: La parte principal de la librería de C que contiene funciones conocidas como open, close, read, write, printf, etc. • libm: Contiene funciones matemáticas como cos, exp y log. • libpthread: Lo forman todas las funciones d hilos de ejecución (thread) de POSIX que comienzan con pthread_ • librt: Las extensiones para tiempo real de POSIX,incluyendo memoria compartida y I/O asíncrono. Atendiendo a la forma en la que se enlazan las librerías para ser utilizadas por un programa se pueden diferenciar dos tipos de librería, las librerías estáticas y las dinámicas. • Librerías estáticas: Se enlazan al compilar y quedan "dentro" del ejecutable final, ya que al compilarlo queda dentro del lenguaje máquina del ejecutable las funcionalidades a las que se han llamado a través de la librería. Es decir, para que sea de fácil entendimiento se puede decir que en lenguaje máquina del ejecutable se encuentra, tanto el código programado por el usuario, como el código de las librerías a las que se llama. Lo anteriormente descrito hace que el ejecutable ocupe más espacio aunque lo hace también muy robusto al no depender de archivos externos a él. En Windows tienen extensión .lib y en Linux tienen extensión .a. • Librerías dinámicas: Se enlazan al ejecutar, y por tanto el sistema operativo se debe hacer cargo de encontrarlas al ejecutar el programa, es decir, se enlazan dinámicamente en tiempo de ejecución. Esto hace que el ejecutable ocupe menos espacio en memoria porque en el ejecutable se encuentra en lenguaje máquina el código escrito por el usuario y las llamadas a las librerías, que ocupa menos código que las librerías en sí, pero tiene el problema de que, si no se han instalado bien las librerías, o en el lugar correcto pueden dar problemas. EnWindows tienen la extensión .dll y se encuentran generalmente en la dirección c;\windows\system32 y en Linux tiene la extensión .so y se encuentra habitualmente en la dirección usr/local/lib o usr/lib. Tras dar una breve explicación de los conceptos más relevantes sobre las librerías se mostrará lo que se debe tener en cuenta a la hora de utilizar el croscompilador. libc, al ser el principal de los nombrados cuando se nombró los cuatro componentes de la librería de C, está enlazado (linked) por defecto, sin tener el usuario que especificarlo a la hora de croscompilar, pero si queremos cualquier 16 Capítulo 2. Toolchain otro de los componentes de la librería de C tendremos que indicarlo explícitamente. Para indicárserlo al croscompilador solo tendremos que introducir el parámetro -l seguido del nombre de la librería, omitiendo eltérmino "lib". Es decir, para enlazar el programa que se quiera con libm, libthread o librt se hará respectivamente como se muestra a continuación en el código 2.13 . Código 2.13 Croscompilando y enlazando el ejecutable holamundo. arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa -lm arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa - lpthread arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa -lrt Si se quiere enlazar con varias de las anteriores a la vez lo único que habrá que hacer es poner seguidos libm, libthread o librt, seguidos cada uno por un espacio siempre. Si por ejemplo se tiene un ejecutable que se ha enlazado con libm, libpthread y libc (este último porque esta enlazado siempre por defecto) y se quiere saber con qué librerías ha sido enlazado, se puede averiguar con el comando del código 2.14. Código 2.14 Obteniendo información sobre librerías compartidas enlazadas con el ejecutable holamundo. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-readelf -a holamundo | grep "Shared library" 0x00000001 (NEEDED) Shared library: [libm.so.6] 0x00000001 (NEEDED) Shared library: [libpthread.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] Como se puede observar en el código 2.14, el programa holamundo ha sido enlazado con las tres librerías que se deseaban. Además, las librerías compartidas necesitan un enlazador en tiempo de ejecución (runtime linker), el cual se puede conocer con el comando de código 2.15. Código 2.15 Obteniendo información sobre el runtime linker para el ejecutable holamundo. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-readelf -a prueba2 | grep "program interpreter" [Requesting program interpreter: /lib/ld-linux-armhf.so.3] Se tendrá que tener cuidado de que en el target se encuentren tanto las librerías como el linker una vez desplegado el sistema en el target objetivo. Para realizar el enlace con las librerías de manera estática se realiza como se muestra a continua- ción en el código 2.16 : Código 2.16 Enlazando librerías de forma estática. 2.3 Crosstool-NG 17 arm-cortexa9_neon-linux-gnueabihf-gcc -static holamundo.c -o holamundo- estatico Si se recoge información acerca del ejecutable holamundo-estatico se podrá comprobar que lo que ocupa el ejecutable ha incrementado dramáticamente con respecto al ejecutable holamundo, el cual se enlazó de forma dinámica. Esto ocurre debido a lo anteriormente comentado cuando se explicó la diferencia entre enlazar las librerías de forma estática y dinámica. Se dejará a elección del lector investigar más acerca de esta herramienta ya que el objetivo del presente documento es realizar una introducción de las toolchain, poner un ejemplo de ellas y mostrar funcionalidades de ejemplo para poder tener una idea global de lo que es Linux embebido, de qué partes está formado y qué papel juega cada elemento. 3 Bootloader En este capítulo se hablará sobre los bootloaders y el proceso seguido en el arranque del sistema,así como la introducción a la utilización de U-Boot, uno de los bootloaders más versátiles que se pueden encontrar. También se introducirá el concepto de árbol de dispositivos (device tree). 3.1 ¿Cuál es la función del bootloader? En en Linux embebido el bootloader se encarga de dos tareas: la inicialización básica del sistema (también llamado con frecuencia el arranque) y la carga del kernel. Cuando el bootloader comienza a trabajar lo único que se encuentra operativo en el sistema es, típicamente, un núcleo de la cpu y algún dispositivo de memoria. Es decir, no se encuentran disponible la mayoría del hardware, ni siquiera está configurado el controlador de la RAM, por lo que el proceso de arranque será el encargado de conseguir, en varias fases, poner en funcionamiento cada vez más partes del sistema. La fase inicial de inicio o arranque finaliza cuando están funcionando las interfaces requeridas para cargar un kernel, es decir, está operativa la memoria principal (RAM) y además están operativos los dispositivos utilizados para acceder al kernel. La última fase del arranque del sistema es cargar el kernel en la RAM y crear un entorno de ejecución para él. 3.2 La secuencia de arranque Con los años el proceso de arranque se ha ido haciendo cada vez más complejo, y actualmente se suele tratar de un proceso multietapa, que tiene diferencias dependiendo del SoC, pero que en general cuenta de las siguientes fases. 3.2.1 Fase 1: código ROM En ausencia de memoria externa "confiable", el código que se ejecuta nada más que se intenta iniciar el sistema es el código ROM. Este código es programado directamente en el chip una vez que el chip se construye, y por tanto es software propietario y no se puede intercambiar por un equivalente de código abierto. En este caso la única memoria RAM a la que tiene acceso el código ROM es a la SRAM (Static Random Access Memory), ya que no tiene disponible los controladores de la DRAM (Dynamic Random Access Memory). El código ROM es capaz de cargar pequeñas porciones de códigos en una de varias ubicaciones preprogramadas en la SRAM. Cuando la SRAM de los SoCs no tiene la capacidad suficiente como para cargar un bootloader completo como U-boot se carga el SPL (Secondary Program Loader) que es un programa que se encarga de hacer el arranque intermedio. Haciendo una analogía con el 19 20 Capítulo 3. Bootloader mundo de la aeronáutica, es como el sistema de arranque intermedio que se necesita para que el turbofan (el compresor más específicamente) gire a las revoluciones necesarias para poder terminar por el mismo el proceso de arranque. Al final de esta fase se encuentra cargado el SPL en la SRAM. 3.2.2 Fase 2: SPL El SPL debe configurar y poner a punto para el funcionamiento a la memoria principal (DRAM) y otras partes esenciales del sistema, para cargar el TPL (Third Stage Program Loader) en la memoria principal. La funcionalidad del SPL se parece a la del código ROM , ya que lo que es capaz de hacer es de leer código desde distintas ubicaciones preprogramadas. Esas ubicaciones preprogramadas se basan en offsets preprogramados en ciertos dispositivos de almacenamiento o de nombres conocidos (preprogramados) como u-boot.bin. Esta fase del arranque todavía no permite ninguna interacción del usuario más allá de que probablemente se impriman por pantalla ciertos mensajes. A diferencia del código ROM el SPL puede ser de código abierto aunque no es extraño que contenga código de propiedad del fabricante. Al final de esta fase se encontrará cargado el TPL en la memoria principal del sistema. 3.2.3 Fase 3: TPL Finalmente el sistema se encuentra por fin ejecutando un bootloader completo como U-boot. En esta ocasión ya si se permite interacción entre el usuario y el sistema en tareas como elegir el kernel que se quiere cargar y otras tareas. Al final de esta fase estará el kernel presente en la memoria principal esperando a ser iniciado. Habitualmente los bootloader de sistemas embebidos desaparecen de la memoria una vez iniciado el sistema. En la siguiente figura 3.1 se muestra de forma esquemática y de seguido, las distintas fases del arranque del sistema que se acaban de explicar. Figura 3.1 Proceso de arranque del sistema. 3.3 Del bootloader al kernel 21 3.3 Del bootloader al kernel Cuando el bootloader le pasa el control del sistema al kernel tiene que aportarle cierta información básica al kernel, como la que se presentará a continuación: • En arquitecturas PowerPC y ARM se le muestra al kernel un número que identifica inequívo- camente el tipo de Soc. • Información básica acerca del hardware detectado hasta el momento, incluyendo al menos la capacidad y localización de la RAM física y la frecuencia de reloj de la CPU. • La línea de comando del kernel. La línea de comandos del kernel es una cadena ASCII simple que controla el comportamiento de Linux, configurando, por ejemplo, el dispositivo que contiene el sistema de archivos raíz. • Opcionalmente la localización y tamaño del binariode un device tree. • Opcionalmente también la localización y el tamaño del initial RAM disk, que es un sistema de archivos temporal usado por el núcleo Linux durante el inicio del sistema. Es usado típicamente para hacer los arreglos necesarios antes de que el sistema de archivos raíz pueda ser montado. La forma en la que se le proporciona esta información al kernel depende de la arquitectura. Si se quiere obtener más información acerca de esto siempre es una buena idea consultar la documentación correspondiente. La manera en la que se le proporciona esta información al kernel actualmente es principalmente mediante lo llamado árbol de dispositivos (device tree). 3.4 Device trees Un device tree es una manera flexible de definir los componentes hardware de un sistema. Normal- mente el device tree es cargado por el bootloader y pasado al kernel, pero es posible agrupar el device tree con la imagen del kernel para bootloader que no sean capaz de manejar estos de forma separada. El kernel de Linux contiene un gran número de archivos fuente de device tree en arch/$ARCH/boot/dts. Si se ha adquirido hardware de un tercero muy probablemente se disponga del archivo .dts por el paquete de soporte. El árbol de dispositivos representa al sistema como una colección de componentes unidos en una jerarquía en forma de árbol, con nodos raíz y nodos hijo. Este árbol comienza con un nodo raíz, representado por "/", el cual contiene nodos y nodos hijos que representan el hardware del sistema. Se va a representar un fragmento del device tree generado por Yocto Project para la ZedBoard en el código 3.1 y posteriormente se va a comentar. Código 3.1 Fragmento del device tree generado por Yocto Project para la ZedBoard. /dts-v1/; / { #address-cells = <0x1>; #size-cells = <0x1>; compatible = "xlnx,zynq-zed", "xlnx,zynq-7000"; model = "Zynq Zed Development Board"; 22 Capítulo 3. Bootloader cpus { #address-cells = <0x1>; #size-cells = <0x0>; cpu@0 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x0>; clocks = <0x1 0x3>; clock-latency = <0x3e8>; cpu0-supply = <0x2>; operating-points = <0xa2c2b 0xf4240 0x51616 0xf4240>; }; cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x1>; clocks = <0x1 0x3>; }; }; fpga-full { compatible = "fpga-region"; fpga-mgr = <0x3>; #address-cells = <0x1>; #size-cells = <0x1>; ranges; }; memory@0 { device_type = "memory"; reg = <0x0 0x20000000>; }; }; Se puede observar en el código 3.1 como se tiene un nodo raíz compuesto por un nodo llamado CPUs con dos nodos hijos (que son los dos núcleos que tiene la CPU de la ZedBoard), un nodo que contiene la descripción de la FPGA y otro nodo que describe la memoria RAM. En el árbol de dispositivos mostrado se puede observar nombres con una arroba seguido de una numeración, corchetes dentro de los cuales hay asignaciones de propiedades, etc. En resumen lo que se puede encontrar en un árbol de dispositivos es lo representado en la figura 3.2. Cuando se tiene un nombre_de_nodo@dirección, el cometido de la dirección es la de distinguir ese nodo de otros. Se puede observar en el código 3.1 que en los nodos de la CPU y de la FPGA se encuentra una propiedad llamada compatible que es la que utiliza el kernel de Linux para emparejar el hardware que tiene esta propiedad con sus correspondientes drivers. Se ha dicho con sus correspondientes drivers porque es común encontrarse con que la propiedad compatible tiene varios valores, esto significa que más de un driver pueden manejar esta pieza de hardware. 3.4 Device trees 23 Figura 3.2 Sintaxis básica de los arboles de dispositivos. 3.4.1 La propiedad reg Los nodos de memoria y CPU tienen la propiedad reg. Esta propiedad nos indica posición inicial en registro de memoria y la longitud que ocupa en el registro. Ambos números se anotan como enteros de 32 bits llamado celdas (cells). Por tanto en el caso del código 3.1 el nodo de memoria señala que solo hay un banco de memoria la cual empieza en 0x0 y tiene 0x20000000 bytes de longitud, que si lo pasamos a sistema decimal son 536870912 bytes, es decir, 512 MiB (mebibyte). Esto que se acaba de comentar es en el caso de sistemas de 32-bits. En sistemas con direccionamiento de 64-bits se vuelve un poco más complejo, ya que se necesitan dos celdas. un ejemplo sería el del código 3.2. Código 3.2 Ejemplo de fragmento de device tree de sistema de 64 bits. / { #address-cells = <2>; #size-cells = <2>; memory@80000000 { device_type = "memory"; reg = <0x00000000 0x80000000 0 0x80000000>; }; } La información que contiene las celdas requeridas se encuentra en #address-cells y #size-cells, es decir, que para entender la propiedad reg se tendrá que buscar #address-cells y #size-cells. Si no 24 Capítulo 3. Bootloader están estas dos propiedades definidas tendrán valor 1 por defecto. Los nodos del tipo CPU también tienen direcciones que indican el núcleo de la CPU del que se trata, es decir, el árbol de dispositivos de un procesador de 4 núcleos tendrá direcciones 0, 1, 2 y 3 respectivamente. En este caso como no se tiene profundidad, es decir solo se tienen direcciones de inicio y no se tiene longitud o rango de memoria se tiene en este caso #address-cells = <0x1> y #size-cells = <0x0> en lugar de #address-cells = <0x1> y #size-cells = <0x1> (como se puede observar en el código 3.1), lo que indica que se tiene celda de dirección pero no celda de tamaño. 3.4.2 Phandles e interrupciones Con lo que se ha explicado hasta el momento se tiene una idea del árbol de dispositivos como una jerarquía de dispositivos hardware que se enlazan con drivers, pero hasta ahora no se ha dicho nada acerca de conexiones de unos dispositivos hardware con otros. Para expresar estas conexiones entre distintos componentes se tienen los phandles, que se pueden entender como punteros a otros nodos. Código 3.3 Fragmento del device tree para mostrar phandles e interrupciones. /dts-v1/; { intc: interrupt-controller@48200000 { compatible = "ti,am33xx-intc"; interrupt-controller; #interrupt-cells = <1>; reg = <0x48200000 0x1000>; }; serial@44e09000 { compatible = "ti,omap3-uart"; ti,hwmods = "uart1"; clock-frequency = <48000000>; reg = <0x44e09000 0x2000>; interrupt-parent = <&intc>; interrupts = <72>; }; }; Tomando como ejemplo el código 3.3 podemos observar la propiedad #interrupt-cells = <1> que lo que indica es el número de valores de 4 bytes que son necesarios para representar una línea de inte- rrupción, que en este caso es uno. El phandle se puede encontrar en el nodo serial@44e09000 como interrupt-parent = <&intc>, que indica un puntero al nodo con etiqueta intc. Por esto cuando en el nodo serial@44e09000 se define la propiedad interrupts = <72> solo aparece un número, porque en el nodo al que apunta serial@44e09000, que es el nodo intc: interrupt-controller@48200000 define que solo tendrá un valor de 32 bits. 3.4.3 Inclusión de archivos en los árboles de dispositivos Los archivos de árboles de dispositivos pueden no presentarse de forma unitaria, sino que pueden estar divididos en varios archivos. Para esto se utilizan los archivos con extensión .dtsi, los cuales son los archivos de inclusión por llamarlos de alguna manera, mientras que los archivos con extensión .dts son los árboles de dispositivos finales. Normalmente los archivos .dtsi contienen definiciones a nivel de SoC (y a veces definiciones comunes a placas casi idénticas), mientras que los archivos .dts aportan la información a nivel de 3.4 Device trees 25 placa. La inclusión del archivo .dtsi se realiza simplemente superponiendo el archivo .dtsi al archivo .dts. En el caso de la ZedBoard, la información sobre el SoC zynq-7000 lo contendría el archivo .dtsi, y la información referente a la ZedBoard lo contendría el archivo .dts. La inclusión del archivo .dtsi dentro del archivo .dts se puede realizar de cualquiera de las 2 formas mostradas en el código 3.4, de la cual se recomienda la segunda de las opciones. Código 3.4 Inclusión de archivo .dtsi en el archivo .dts./include/ "archivo.dtsi" ó #include "archivo.dtsi" Como ejemplo se mostrará un fragmento de archivo .dtsi y la inclusión de este en un archivo .dts y como quedaría el código del .dtb final (si se descompilara para pasarlo a un archivo .dts). Código 3.5 Fragmento de ejemplo de un .dtsi. /{ compatible = "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { compatible = "ti,omap3-uart"; reg = <0x44e09000 0x2000>; interrupts = <72>; status = "disabled"; }; }; }; Código 3.6 Fragmento de ejemplo de un .dts que incluye el .dtsi. #include "am33xx.dtsi" /{ compatible = "ti,am335x-bone", "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; }; }; 26 Capítulo 3. Bootloader }; Compilando el archivo .dts del código 3.6 se obtiene el código 3.7 en el archivo .dtb. Notar que el archivo .dtb es un archivo en formato binario, por lo que el código que se ha representado es el texto equivalente al contenido de ese archivo .dtb Código 3.7 Fragmento de ejemplo de un .dts que incluye el .dtsi. /{ compatible = "ti,am335x-bone", "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { compatible = "ti,omap3-uart"; reg = <0x44e09000 0x2000>; interrupts = <72>; pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; }; }; }; El bootloader y el kernel necesitan de una representación binaria del árbol de dispositivos, por lo tanto el archivo .dts debe compilarse utilizando el compilador de árbol de dispositivos (DTC). El archivo resultante es el llamado binario del árbol de dispositivos como marca su extensión .dtb (device tree binary). 3.4.4 Elección de un bootloader En la próxima tabla se presentan algunos de los bootloaders más utilizados: Tabla 3.1 Principales bootloaders. Nombre Arquitecturas soportadas Das U-Boot ARM, Blackfin, MIPS, PowerPC, SH Barebox ARM, Blackfin, MIPS, PowerPC GRUB 2 X86, X86_64 RedBoot ARM, MIPS, PowerPC, SH CFE Broadcom MIPS YAMON MIPS Para el caso que ocupa se recomienda usar U-Boot ya que como se puede comprobar es la más versátil. Simplemente para introducir U-Boot se mostrará como se descarga el código, como se compila y el resultado de la compilación. Para su instalación copiamos el repositorio y realizamos un checkout a la versión más reciente como se muestra en el código 3.8. Código 3.8 Obtención del código de U-Boot. 3.4 Device trees 27 angel@ubuntu:~/Desktop$ git clone git://git.denx.de/u-boot.git angel@ubuntu:~/Desktop$ cd u-boot angel@ubuntu:~/Desktop/u-boot$ git checkout v2016.09 Hay muchísimos archivos de configuración para placas típicas dentro del directorio configs/. Normalmente se sabe cuál usar nada más revisando el nombre del fichero de configuración, aunque se puede obtener más información en los archivos README de las placas situadas en el directorio board/. Si la información no queda clara, o se requiere más información, nunca viene mal una búsqueda por Internet o una consulta en algún foro. En el caso de querer compilar el U-Boot para la zedboard, ya que en el directorio configs/ se tiene un archivo de zynq_zed_defconfig el proceso es muy sencillo. Lo único que se requiere es informar a U-Boot del archivo de configuración que se va a utilizar y el croscompilador que se va a utilizar asignando este a la variable CROSS_COMPILE del make. Código 3.9 Compilación de U-Boot para la zedboard. angel@ubuntu:~/Desktop/u-boot$ PATH=~/x-tools/arm-cortexa9_neon-linux- gnueabihf/bin:$PATH angel@ubuntu:~/Desktop/u-boot$ make zynq_zed_config angel@ubuntu:~/Desktop/u-boot$ make CROSS_COMPILE=arm-cortexa9_neon- linux-gnueabihf- Notar que se ha añadido al path el croscopilador generado por la toolchainmostrada en el capítulo 2 para que pueda ser utilizado en la compilación de U.-Boot. Tras el último comando del código 3.9 se ejecutará la compilación de U-Boot para nuestro target. Algunos de los archivos importantes generados por la compilación de U-Boot son los siguientes: • u-boot: U-Boot en formato objeto ELF, adecuado para usar con un programa de depuración. • u-boot.map: La tabla de símbolos • u-boot.bin: U-Boot en formato binario sin procesar, adecuado para ejecutarse en el dispositivo. • u-boot.img: u-boot.bin con un encabezado para ser usado por la memoria de sólo lectura de arranque (boot ROM) para determinar cómo y donde cargar y ejecutar U-Boot. • u-boot.dtb: el device tree. 4 El kernel En este capítulo se explicará qué es el kernel, que contiene este y como se puede conseguiry compilar uno para introducirlo en el dispositivo de destino. También se introduciran el concepto de módulo kernel. 4.1 ¿Qué es el kernel? El kernel es la parte del sistema operativo que se encarga de gestionar los recursos del sistema y de hacer de interfaz con el hardware. Es el que permite que el sistema funcione correctamente y de forma segura, ya que se encarga de proteger fragmentos de memoria para que no puedan ser corrompidos por acceder de forma errónea o intencionada, de manera que se pueda llegar a un estado degradado del funcionamiento de alguna funcionalidad o algún componente hardware (ya que también protege y gestiona el acceso al hardware). En resumen, el kernel tiene 3 tareas: • Gestionar recursos • Hacer de interfaz con el hardware. • Proveer de una API que permita un nivel de abstracción útil para los programas del espacio de usuario. En la figura 4.1 se representa de forma gráfica los distintos espacios en los que se pueden dividir un sistema. Las aplicaciones ejecutadas en el espacio de usuario tienen un bajo nivel de privilegio (en ejecución en CPU). Lo que realizan en mayor medida son llamadas a las bibliotecas. En concreto la biblioteca de C se puede decir que es la principal interfaz entre el espacio de usuario y el espacio kernel, ya que traduce las funciones de nivel de usuario (como las definidas por POSIX) a las llamadas al sistema kernel (kernel system calls). 4.2 Elección del kernel En el caso de que se este creando un sistema operativo para un sistema embebido de manera "artesanal", es decir, construyendo todos los componentes del sistema operativo embebido por separado, se querrá tener un kernel que sea estable y tenga soporte a largo plazo. Se puede obtener el árbol de git estable de Linux con cualquiera de los comandos que se muestran en el código 4.1. 29 30 Capítulo 4. El kernel Figura 4.1 Representación de espacio de usuario y espacio kernel. Código 4.1 Descargando el repositorio con versiones estables del kernel de Linux. $ git clone \ https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux- stable.git $ git clone \ git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git $ git clone \ https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git Tras esto se puede hacer un git checkout a una versión particular de Linux. Por lo general, el kernel estable se mantiene solo hasta la próxima versión principal, es decir, de 8 a 12 semanas más tarde. Para atender a aquellos usuarios que desean actualizaciones durante un período de tiempo más largo y tener la seguridad de que se detectarán y corregirán los errores, algunos núcleos se etiquetan a largo plazo y se mantienen por dos o más años. Hay al menos un kernel a largo plazo cada año. Si está construyendo para un producto que tendrá que mantenerse durante este período de tiempo, el último kernel a largo plazo disponible podría ser una buena 4.2 Elección del kernel 31 opción. La versiones del kernel que se mantienen a largo plazo se pueden revisar en la dirección https://www.kernel.org/. Llegados a este punto se puede pensar que lo que queda es fácil, descargar el kernel que se quiera, configurarlo e introducirlo en el dispositivo objetivo, pero la realidad es que eso no siempre es posible. De hecho, Linux solo tiene soporte sólido para un pequeño subconjunto de la infinidad de dispositivos que pueden funcionar con Linux. También se podrá encontrar soporte para la placa objetivo o SoC de proyectos independientes de código abierto, como YoctoProject, por ejemplo. Pero muchas veces es casi obligatorio tener que consultar con el proveedor del SoC o placa para un kernel funcional. En el momento de escritura del documento una de las versiones de kernel mantenidas a largo plazo es la versión 4.9.61. Por tanto se apuntará a esta versión como se indica en el código 4.2. Código 4.2 Cambiando a la versión del kernel que apunta ala rama 4.9.61. angel@ubuntu:~/Desktop/linux-stable$ git checkout v4.9.61 Checking out files: 100% (32386/32386), done. Note: checking out 'v4.9.61'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b <new-branch-name> HEAD is now at 5caae9d... Linux 4.9.61 angel@ubuntu:~/Desktop/linux-stable$ git branch * (HEAD detached at v4.9.61) master Una vez actualizado a la rama deseada se puede echar un vistazo a lo que incluye este repositorio. Los principales directorios de interés son los siguientes: • arch: Contiene archivos específicos de cada arquitectura contando con un directorio por cada arquitectura. • Documentation:Contiene ,como su propio nombre indica, documentación acerca del kernel. Siempre que se quiera tener cierta información acerca de algún aspecto de Linux se debe buscar en esta carpeta. • drivers: Contiene device drivers. Contiene un directorio por cada tipo de driver. • fs: Contiene código del filesystem. • include: Contiene archivos de cabecera del kernel, incluyendo también los que se necesitan para la construcción de la toolchain. • init: Contiene código de inicio del kernel. 32 Capítulo 4. El kernel • kernel: Contiene funciones principales, como bloqueo, los temporizadores (timers), adminis- tración de energía, etc. • mm: Contiene la gestión de memoria. • net: Contiene protocolos de red. • scripts: Contiene muchos scripts utililes como el compilador de arboles de dispositivos (dtc). • tools: Contiene muchas herramientas utiles como por ejemplo la herramienta de contadores de rendimiento de Linux llamada perf. 4.3 Configuración del kernel Uno de los puntos fuertes de Linux es el grado de personalización y configuración que tiene, ya que se puede configurar un kernel de manera que se oriente para el dispositivo más sencillo que uno pueda imaginar hasta el más complejo. El mecanismo de configuración se llama Kconfig, y el sistema de construcción con el que esta integrado se llama Kbuild. La documentación de ambós se puede consultar en Documentation/k- build. Estos dos mecanismos son utilizados también en otros proyectos, por ejemplo U-Boot y crosstool-NG, como ya se ha visto en capítulos anteriores. Las opciones de configuración se declaran en una jerarquía de archivos denominada Kconfig utilizando una sintaxis descrita enDocumentation/kbuild/kconfig-language.txt. Es decir, se pueden definir las configuraciones mediante la modificación de archivos y después la lectura de estos y la producción de un archivo .config, que como indica el punto antes del nombre es un archivo oculto. Hay muchas maneras de leer los archivos Kconfig y generar el archivo .config, algunas de ellas muestran menús en pantalla que permiten hacer elecciones de una manera interactiva. Este es el caso de menuconfig, que quizas sea el más utilizado, pero también se podrían utilizar xconfig o gconfig. Para lanzar el menú de configuración se realiza como se indica en el código 4.3. Hay que tener en cuenta que se le debe especificar la arquitectura para la que se va a configurar, llamando a unos de los nombres de los directorios que contiene el directorio arch. Código 4.3 Comando para ejecutar la interfaz de configuración del kernel. angel@ubuntu:~/Desktop/linux-stable$ make ARCH=arm menuconfig Configurar el kernel de cero puede ser un trabajo que no es razonable. Sería mas razonable empezar con alguna configuración parecida a la que se quiere, que ya esté construida buscando en arch/$ARCH/configs. Cada archivo contiene una configuración apropiada para un SoC o grupo de SoCs. En este caso, si se quiere construir un kernel para la ZedBoard el directorio en el que buscar será arch/arm/configs, ya que la arquitectura de la CPU de la ZedBoad es arm. La ZedBoard cuenta con un procesador dual core Cortex A9 que cuenta con arquitectura Armv7-A. Por tanto, se puede seleccionar el archivo multi_v7_defconfig que es una configuración realizada para una amplia variedad de SoCs que utilizan la arquitectura Armv7-A. Por lo tanto solo habrá que ejecutar el comando que se muestra en el código 4.4. Código 4.4 Comando para ejecutar la creación del .config. angel@ubuntu:~/Desktop/linux-stable$ make ARCH=arm multi_v7_defconfig 4.4 Módulos kernel 33 HOSTCC scripts/kconfig/conf.o HOSTLD scripts/kconfig/conf # # configuration written to .config # Se puede obtener la versión del kernel que se ha creado con el comando que se muestra en el código 4.5. Código 4.5 Comando para revisar que version del kernel se ha construido. angel@ubuntu:~/Desktop/linux-stable$ make kernelversion 4.9.61 # 4.4 Módulos kernel Se va a realizar una breve introducción acerca de los módulos kernel, los cuales se volverán a nombrar repetidas veces a lo largo del documento. Los módulos kernel (kernel modules en inglés) son piezas de código que se vinculan dinámi- camente con el kernel en tiempo de ejecución, extendiendo así la funcionalidad del kernel. Estas funcionalidades se cargan en tiempo de ejecución según las características requeridas y el hardware detectado (ya que se pueden conectar nuevos dispositivos una vez encendido el sistema). Si no fuera así, todos los controladores y funciones deberían estar vinculados estáticamente al kernel y cargados en el kernel de manera permanente, lo que podría hacer al kernel alcanzar un tamaño mucho mayor. Además los módulos kernel eliminan la necesidad de recompilar todo el kernel si se le quiere añadir a este una nueva funcionalidad. También se pueden añadir funcionalidades como módulos kernel para aliviar la carga de arranque y por tanto que el arranque se produzca más velozmente, dejando los drivers y funcionalidades que no sean esenciales para ser cargados más tarde. 4.5 Compilando El sistema de construcción (en este caso también se le puede llamar compilación) del kernel Kbuild lo forman un conjunto deMakefiles que reunen la información que se presenta en el archivo .config, resuelven las dependencias y compilan todo lo que sea necesario para crear una imagen del kernel, que contiene todos los componentes enlazados de forma estática y posiblemente uno o más modulos kernel. 4.5.1 Compilando la imagen del kernel Lo primero que hay que tener en cuenta es lo que cada bootloader requiere. En el caso de U-Boot, que es el bootloader que se ha utilizado en el capítulo anterior, requiere una imagen con nombre uImage, aunque las últimas versiones de U-Boot permiten cargar un archivo zImage usando el comando bootz. La mayoría de los demás bootloaders requieren imagen tipo zImage. Crear estos dos tipos de imagen es muy sencillo. Se comenzará con la imagen zImage, que se puede compilar como se muestra en el código 4.6. 34 Capítulo 4. El kernel Código 4.6 Creación de zImage. angel@ubuntu:~/Desktop/linux-stable$ PATH=~/x-tools/arm-cortexa9_neon- linux-gnueabihf/bin:$PATH angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- zImage Notar que se tiene que tener agregado a la variable de entorno PATH el binario que permite utilizar el croscompilador creado con la toolchain. Con la asignación en el comando del código 4.6 "-j 4" se le indica al sistema cuantos hilos de procesamiento se quieren dedicar a la tarea de compilación de la imagen, en este caso 4 hilos. Se puede comprobar la ubicación de zImage en la última línea que aparece
Compartir