Capítulo 2: Fundamentos teóricos

Autor: Emiliano P. López - Santa Fe (Argentina)

Sistemas embebidos

Los sistemas embebidos son dispositivos de propósito específico que han sido diseñados para realizar un conjunto reducido de operaciones. En contraste, una computadora de propósito general como las computadoras personales pueden realizar una gran cantidad de tareas dependiendo de los programas que ejecuten.
Un sistema embebido se caracteriza por sus dimensiones reducidas, bajo costo y bajo consumo eléctrico.
Raghavan [1] enuncia los factores claves que diferencian un sistema embebido de una computadora de escritorio,

  • Los sistemas embebidos generalmente son de costo reducido
  • La mayoría poseen restricciones de tiempo-real
  • Existen múltiples arquitecturas de procesadores (MIPS, ARM, PowerPC, etc.) cuyas características varían según la aplicación específica del sistema embebido (procesamiento de imágenes, transmisión de datos, etc.)
  • Poseen recursos limitados de memoria RAM, ROM u otros dispositivos de Entrada / Salida (E/S) en comparación con una computadora tipo PC.
  • Un sistema embebido es diseñado desde dos perspectivas, hardware y software, teniendo en cuenta su aplicación específica.

Este tipo de dispositivos son utlilizados en diferentes áreas; frecuentemente en equipamiento de redes de datos como routers, firewalls, switchs; en sistemas de comunicaciones como teléfonos celulares, PDAs, cámaras digitales; o para fines hogareños como reproductores de MP3, sistemas de entretenimiento, etc.

Esta gran variedad de dispositivos realizan un conjunto de operaciones limitadas en función de las características que posee el software embebido que ha sido desarrollado por el fabricante. Especificamente en el área de redes, estos dispositivos son vistos como una caja negra que ofrecen funcionalidades acotadas, restringiendo al usuario la posibilidad de incrementar o personalizar la capacidad del mismo.

Arquitectura

Los sistemas embebidos se encuentran implementados en placas únicas (SBC) y sobre estas se encuentran integrados los recursos de hardware como el microprocesador, la memoria RAM, controladores ethernet, etc.
Estos recursos no son ampliables (al menos no fácilmente) como en el caso de las computadoras de escritorio, salvo por buses y conectores diseñados para un tipo específico de periféricos (miniPCI) o expansiones de memoria de almacenamiento (compact flash) como luego veremos en el equipo seleccionado para este proyecto, Routerboard 532 suya imagen observamos en la siguiente figura.

rb-presentacion2.jpg

Procesadores integrados y autónomos

La gran mayoría de los sistemas embebidos poseen algún tipo de procesador integrado, también denominados SOC (del inglés System On Chip). A diferencia de aquellos procesadores cuya función es solo la de procesamiento, denominados autónomos (traducción de Stand-Alone), los procesadores SOC poseen integrados controladores DRAM, UART, PCI, Ethernet, etc

La arquitectura de procesadores autónomos, utilizados en computadoras de propósito general, ha sido diferenciada de la de procesadores embebidos por Hallinan [1]. En la siguiente figura observamos su arquitectura.

uC-stand-alone.png

Estos procesadores poseen componentes extras (chipsets) para satisfacer los requerimientos de conexión y permitir dispositivos periféricos externos como el sistema de memoria principal (DRAM), ROM, flash, sistema de buses PCI, puertos seriales, interfaces IDE, etc.

En el siguiente diagrama de bloques observamos la anatomía general de un sistema embebido, en donde el procesador posee integrado una UART ( del inglés Universal Asynchronous Receiver-Transmitter) para una interfaz serial y un controlador ethernet, además de memorias (RAM y NAND) y un bus para la conexión de una memoria compact flash.

anatomy.png

Existen cientos de marcas en el mercado que fabrican soluciones SOCs de arquitectura RISC (del inglés, Reduced Instruction Set Computer). Algunos ejemplos de estas arquitecturas soportadas por Linux son:

  • PowerPC, principalmente utilizado en sistemas embebidos para redes y telecomunicaciones.
  • ARM, generalmente utilizados en teléfonos celulares y equipamiento de redes.
  • MIPS, arquitectura con implementaciones de 32 y 64 bits utilizado en una gran cantidad de productos populares, televisores Sony de alta definición, access points inalámbricos Linksys y la popular consola de video juegos Sony Play Station 2.

Esta última arquitectura es en la que haremos énfasis debido a que el Routerboard 532 posee un procesador MIPS 32 bits.
Una de las ventajas de Linux como sistema operativo embebido es el rápido soporte de nuevos chipsets, sin embargo, en el mercado de sistemas embebidos Linux, estas tres arquitecturas son las más importantes y utilizadas, prueba de esto es la encuesta realizada por el portal de Linux embebido Linuxdevices.com,

cputrends.jpg

GNU/Linux embebido

Un sistema GNU/Linux embebido simplemente hace referencia a un sistema embebido basado en el kernel de Linux. Es importante destacar que no existe un kernel específico para sistemas embebidos, es decir, no necesitamos crear un kernel especial para sistemas embebidos. A menudo se utilizan las versiones oficiales del kernel de Linux para construir un sistema, por supuesto, es necesario configurar al mismo para dar soporte especial al hardware de un determinado equipo. Fundamentalmente, un kernel utilizado en un sistema embebido difiere de un kernel usado en una computadora de escritorio o servidor en la configuración del mismo al momento de compilarlo.
La arquitectura de un sistema GNU/Linux esta formado por un conjunto de componentes, y el kernel Linux es solo una parte de este conjunto.

Arquitectura de un sistema GNU/Linux

La arquitectura de un sistema GNU/Linux involucra diferentes componentes. Como se describe en Yaghmoun [2] la arquitectura, desde un punto de vista genérico, de un sistema GNU/Linux se puede describir mediante diferentes capas que van desde el hardware hasta las aplicaciones. En la siguiente figura observamos estos niveles de abstracción.

linux-arch.png

Inmediatamente sobre el hardware se sitúa el kernel. El kernel es el componente central del sistema operativo. Su funciones son principalmente administrar el hardware de manera coherente y justa mientras se le otorga un nivel de abstracción familiar, a través de las APIs, a las aplicaciones de nivel de usuario.
Entre otras tareas relevantes de un sistema operativo, el kernel Linux maneja dispositivos, administra los acceso de E/S, controla los procesos y administra el uso compartido de memoria.
Dentro del kernel, la interfaz de bajo nivel es específica para cada configuración de hardware, sobre la cual, el kernel ejecuta y provee control directo de los recursos hardware. Típicamente, los servicios de bajo nivel manejan operaciones específicas de la CPU (del ingles Central Unit Process), operaciones de memoria específicas a la arquitectura, y provee interfaces básicas para dispositivos.
Los capa de alto nivel provee abstracciones comunes a todos los sistemas Unix, incluyendo procesos, archivos, sockets y señales. Este nivel de abstracción se mantiene constante aunque difiera el hardware.
Entre estos dos niveles de abstracción, el kernel necesita lo que se denomina componentes de interpretación
para comprender e interactuar con datos estructurados provenientes de, o hacia ciertos dispositivos.
Los diferentes tipos de sistemas de archivos y los protocolos de red son ejemplos de fuentes de datos estructurados. El kernel necesita interpretarlos e interactuar a fin de proveer acceso a los datos provenientes desde estas fuentes o hacia las mismas.

Los servicios brindados por el kernel no son soporte suficiente para cargar y ejecutar las aplicaciones. Es necesario contar con librerías, éstas proveen APIs familiares y abstracciones de servicios que interactúan con el kernel en nombre de las aplicaciones para obtener la funcionalidad deseada.

La librería principal, utilizada en la mayoría de las aplicaciones Linux, es la librería C GNU (glibc).
Típicamente las librerías son enlazadas dinámicamente en el momento en el que se ejecutan las aplicaciones. Esto es, no son parte de las aplicaciones binarias, sino que se cargan dentro del espacio de memoria de las aplicaciones durante el inicio de las mismas. Esto permite a varias aplicaciones utilizar una misma instancia de una librería en vez de realizar una copia en memoria por cada aplicación que se ejecuta.
Según lo expuesto anteriormente es lógico pensar la conveniencia de enlazar dinámicamente las librerías, sin embargo, en los sistemas embebidos esto no es del todo cierto. El motivo radica en que las aplicaciones no utilizan la librería C en forma completa, sino que dependiendo de la aplicación puede utilizar partes de la librería y no otras. De este modo, en algunas aplicaciones parte de la librería se encuentra en la misma aplicación binaria. Este es el fundamento por el cual es preferible utilizar un enlazamiento estático, sin embargo nos encontramos con un inconveniente, para sistemas Linux embebidos la librería glibc consume demasiados recursos de la memoria RAM del sistema, por este motivo, reemplazar esta librería puede significar un ahorro de espacio en memoria. Usualmente se la reemplaza por librerías alternativas diseñadas para sistemas embebidos.

Kernel Linux

En base a lo que hemos desarrollado en el item anterior sabemos que el kernel es solo un componente de una gran estructura organizada funcional y jerárquicamente que es el sistema operativo. Si bien solo es un componente, es el componente central y de su comportamiento depende la robustez y flexibilidad del sistema operativo.
Para comprender esto de mejor manera veamos algunas definiciones sobre sistemas operativos.
Tanenbaum [3] desarrolla el concepto de sistema operativo desde dos ópticas:

  • El sistema operativo como máquina extendida
  • El sistema operativo como administrador de recursos

En la primer definición hace referencia al sistema operativo como intermediario entre el hardware y las aplicaciones, facilitando el acceso de lectura y escritura a los diferentes recursos (memoria RAM, procesador, impresoras). Actuando como una capa de abstracción de alto nivel, más fácil de programar que el hardware subyacente.
Desde el segundo punto de vista, la función del sistema operativo es la de administrar todos los componentes de un sistema complejo (memorias, procesadores, discos, mouse, interfaces con redes, etc). Aquí la misión es asegurar un reparto ordenado de los recursos, atender solicitudes, contabilizar la utilización y mediar entre solicitudes en conflicto provenientes de diferentes programas y usuarios.

En otras palabras, Stallings[4] describe al sistema operativo como un programa que controla la ejecución de los programas de aplicación y actúa como interfaz entre el usuario de una computadora y el hardware de la misma. Resume los objetivos de un sistema operativo en tres funciones:

  • Comodidad
  • Eficiencia
  • Capacidad de evolución

Describamos la obviedad de cada uno de estos términos. El primero hace referencia a que un sistema operativo debe ser cómodo de usar. La eficiencia es deseable desde el punto de vista del uso de los recursos y por último, la capacidad de evolución, nos dice que un sistema operativo debe contruirse de modo que permita el desarrollo efectivo, la verificación y la introducción de nuevas funciones en el sistema y, a la vez, no interferir en los servicios que brinda.

Como vimos, estos grandes autores realizan descripciones muy cercanas, ambos concuerdan en la definición de un sistema operativo como interfaz de usuario y administrador de recursos.
Ahora bien, como se imaginará el lector, las funciones que realiza el kernel Linux son exactamente éstas!
Corbet [5] describe de modo más específico el rol que lleva a cabo el kernel Linux,

  • Administración de procesos
  • Administración de memoria
  • Sistemas de archivos
  • Control de dispositivos
  • Redes

Esta división del rol que lleva a cabo el kernel Linux encuadra perfectamente en las definiciones previas, pero se debe destacar que además de las cuestiones técnicas el kernel Linux cumple con el tercer precepto que menciona el sabio William Stallings. El kernel Linux es distribuido bajo la licencia GNU GPL por lo que su capacidad de evolución es una cualidad que posee desde su surgimiento, hecho por el cual su desarrollo es muy activo, brindando soporte para cientos de protocolos de red, decenas de arquitecturas de hardware y por supuesto, obteniendo un rendimiento eficiente y robusto.

Una clasificación generalmente utilizada para clasificar a los sistemas operativos tiene que ver con el modo de compartir el espacio de memoria. Se diferencian tres tipos, de tiempo real, monolíticos y microkernel.
De forma resumida las características son las siguientes:

  • Realtime (RTOS): El espacio de direcciones es plano o lineal, no posee protección de memoria entre las aplicaciones y el kernel, es decir, el nucleo del kernel, el subsitema del kernel y las aplicaciones comparten el mismo espacio de memoria. Se denominan Realtime debido a que no hay sobrecarga por llamadas al sistema, pasaje de mensajes o copia de datos. (Sin protección de memoria)
  • Monolítico: está diferenciado el espacio de memoria de usuario y kernel. Las aplicaciones que operan en el espacio de usuario lo hacen sobre direcciones de memoria virtuales por lo tanto no pueden corromper la memoria de otras aplicaciones o del kernel. Sin embargo, los componentes del kernel comparten el mismo espacio de direcciones y por ende, un driver o módulo mal programado puede causar la inestabilidad del sistema. La mayoría de los sistemas operativos Unix son de este tipo.
  • Microkernel: hace uso de un pequeño SO que provee los servicios básicos y el resto del kernel se ejecuta como aplicaciones. La clave del microkernel surge a partir de un esquema robusto de pasaje de mensajes. (Protección de memoria)

Es necesario tener en cuenta que la mayoría de los procesadores ejecutan los procesos en dos modos, en Modo Usuario o en Modo Kernel. Cuando un programa es ejecutado en modo usuario este no puede acceder directamente a programas o estructuras de datos del kernel, sin embargo, cuando el mismo se ejecuta en modo kernel estas restricciones no existen.

El kernel Linux es monolítico y su arquitectura desde este punto de vista puede observarse en la siguiente figura:

monolitico.png

Sistema de archivos y módulos

El sistema de archivos es el encargado de realizar la organización y almacenamiento de los archivos en los diferentes dispositivos disponibles en el sistema. En función de las características del dispositivo de almacenamiento y del tipo de información que se va a guardar es preferible utilizar un sistema de archivos u otro. Linux da soporte a varios sitemas de archivos, dentro de los mas utilizados se encuentran ext2, ext3, reiserfs, etc. Estos sistemas de archivos son manejados por una capa denominada Sistema de Archivos Virtual (VFS, del inglés Virtual File System). Esta capa de abstracción provee una visión consistente de los datos almacenados en diferentes dispositivos del sistema. Esta visión es lograda separando el nivel de usuario de los sistemas de archivos, utilizando llamadas estandars al sistema, permitiendo sistemas de archivos lógicos sobre cualquier dispositivo físico. Por lo tanto esta capa abstrae los detalles físicos del dispositivo permitiendo un acceso a los mismos a través de archivos de una manera consistente.
Por debajo de esta capa VFS, el kernel interactúa con dispositivos de E/S a través de controladores de dispositivos (del inglés devices drivers). Estos controladores se encuentran incluidos en el kernel y consisten en estructuras de datos y funciones que controlan uno o más dispositivos como discos rígidos, teclados, mouses, monitores, interfaces de red, dispositivos SCSI. Observamos esta estructura que ha sido descripta por Bovet [6] en el siguiente gráfico.

device_drivers.png

Uno de los propósitos fundamentales de los controladores de dispositivos es aislar los programas de usuario del acceso a estructuras de datos críticas del kernel y dispositivos de hardware. Además, un controlador de dispositivo oculta al usuario la complejidad y variabilidad de un dispositivo hardware. Por ejemplo, un programa que quiere escribir datos en un disco rígido no tiene en cuenta si el mismo posee sectores de 512 bytes o 1024 bytes. El usuario simplemente abre el archivo y realiza el comando de escritura. El controlador manejará los detalles y aislará al usuario de las complejidades y riesgos de programar directamente sobre el dispositivo de hardware. Estos controladores proveen la representación de los dispositivos a través de archivos, en GNU/Linux y sistemas operativos Unix todo hardware es representado por un archivo.

Linux posee la capacidad de agregar y quitar componentes del kernel en tiempo de ejecución. Como hemos descripto anteriormente, el kernel Linux posee una estructura de kernel monolítico, con una interfaz para agregar y quitar módulos de controladores de dispositivos dinámicamente luego del arranque del mismo. Esta característica no solo agrega flexibilidad al usuario, sino que además, en sistemas embebidos adquiere una especial importancia debido a su capacidad de actualización y adaptación a dispositivos de E/S nuevos.

Sistema de archivos raíz

Todo dispositivo, ya sea que se encuentre en un sistema embebido o una pc de escritorio, necesita al menos un sistema de archivos. Dos razones que describe Yaghmoun [2] son las siguientes:

  • Las aplicaciones poseen programas separados, independientes por ende necesitan espacio de almacenamiento en un sistema de archivos.
  • Los dispositivos de bajo nivel también son accedidos mediante archivos.

De esto se desprende la necesidad de poseer un sistema de archivos maestro, un sistema de archivos raíz (RFS, del inglés Root Filesystem).
El RFS es una estructura de directorios jerárquica en donde se almacenan las aplicaciones, librerías y archivos relacionados para ejecutar el sistema. Estos archivos pueden ser binarios, contener solo datos o también vincularnos con estructuras de datos haciendo de nexo con los dispositivos de E/S que hemos descripto en el punto anterior.
El RFS de un sistema Linux embebido y de un Linux de escritorio o servidor no varían demasiado, solo que el primero es reducido por evitar un consumo excesivo de recursos. Este sistema de archivos raíz es montado al momento del arranque de Linux.
Posee una estructura en forma de árbol, en donde todos los directorios se desprenden del directorio principal denominado raíz y simbolizado con una barra inclinada (/). La siguiente figura nos muestra este tipo de estructura.

Compilación nativa y cruzada

Como hemos expresado en el capítulo 1, en el proceso de desarrollo de un sistema GNU/Linux embebido se debe realizar en un primer punto las configuraciones correspondientes de los componentes que formarán parte del sistema embebido (kernel, los módulos del mismo, el sistema de archivos raíz, etc), luego de esto, el paso previo a la prueba del sistema en el dispositivo es la etapa de compilación.
Cuando se realiza el desarrollo de una aplicación, usualmente lo compilamos en una computadora tipo PC, ya que dicha aplicación será ejecutada por un sistema de características de hardware similares. Otra práctica habitual ocurre cuando deseamos añadir soporte extra al kernel Linux, ya sea para hardware o para protocolos de red, en este caso realizamos la compilación en la misma computadora donde luego será ejecutado.
Este tipo de compilación, en donde el software es compilado y ejecutado en una misma arquitectura de hardware se denomina compilación nativa.
Esta alternativa es la más utilizada, y se la denomina de este modo aún si la aplicación ha sida compilada en un equipo diferente al que luego la ejecutará siempre y cuando la arquitectura de hardware sean compatibles. Un ejemplo de esto son las aplicaciones compatibles para microprocesadores AMD e Intel.
En el caso de los equipos que frecuentemente se utilizan para sistemas embebidos, poseen recursos de hardware limitados y específicamente diseñados para realizar cierto tipo de tareas. Por este motivo, en general, no es una buena alternativa compilar un sistema completo utilizando el hardware de un router.
En este punto es donde surge la necesidad de realizar la compilación del sistema embebido en una computadora cuyos recursos no sean escasos. Es común hoy en día tener una PC capacidades de procesamiento de 3 Ghz y 1GB de memoria RAM, lo que nos supone una gran ahorro de tiempo al momento de realizar pruebas y cambios de configuraciones, solo basta diseñar un "ambiente" que le permita al compilador producir código ejecutable para un microprocesador diferente al que se está utilizando.
La compilación de software que será ejecutado en una arquitectura diferente, por ende incompatible, a la que está produciendo el código ejecutable se denomina compilación cruzada y este "ambiente" que permite realizar la misma se denomina entorno de compilación cruzada o entorno de desarrollo cruzado.
Yaghmoun [2] describe este proceso identificando al equipo que realiza la compilación mediante el término Host o Huesped y al dispositivo que ejecuta el software, como sistema Target u Objetivo. Lo observamos en la siguiente figura.

cross.png

Entorno de compilación cruzada

Para implementar un entorno de compilación cruzada es necesario un conjunto de librerías, utilitarios y binarios. En la bibliografía relacionada con sistemas GNU/Linux embebido a este conjunto de componentes se los denomina toolchain components.
Estos componentes son:

  • Compilador C : compilador de C básico, generador de código objeto (tanto del kernel como de aplicaciones)
  • Librería C: implementa las llamadas al sistema mediante APIs.
  • Binutils: conjunto de programas necesarios para la compilación, enlazado, ensamblado y depuración de código. Entre otros, los binarios principales son: ld (GNU linker), as (GNU assembler).
Bibliography
1. Hallinan Cristopher - Embedded Linux Primer, A Practical Real-World Approach
2. Yaghmoun Karin - Building Embedded Linux Systems
3. Andrew S. Tanenbaum, Albert S. Woodhull - Sistemas Operativos, Diseño e Implementación
4. William Stallings - Sistemas Operativos
5. Corbet Jonathan, Rubini Alessandro, Kroah-Hartman Greg - Linux Device Drivers
6. Bovet Daniel, Cesati Marco - Understanding the Linux Kernel
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License