FEOS: Sistema operativo experimental para PDA daVinci.







Índice:
 

MOTIVACIÓN

El Procesador MC68EZ328 permite implementar a través de su lógica de CHIP-SELECT una mínima protección del kernel del sistema operativo. Sin embargo, el sistema operativo que trae el daVinci de fábrica no aprovecha en absoluto esas posibilidades, y dado que los tribunales han fallado que este sistema operativo es un plagio del Palm-Pilot, he de entender que el sistema operativo del Palm-Pilot es igualmente malo, y que si no revienta más a memudo es tan solo debido a que las aplicaciones están bien hechas.

En concreto los aspectos negativos del sistema operativo del davinci son:

Por otra parte, tenemos la implementación de Linux para sistemas 68000 sin MMU: el uCLinux. Sin embargo, los requisitos de memoria hacen que un port de este Linux al davinci sea totalmente descartado. El segmento de datos del kernel ya ocupa la práctica totalidad de la memoria RAM disponible. Además un sistema propio  de estaciones de trabajo no parece lo más adecuado para un PDA. El concepto de memoria Flash como medio de almacenamiento choca con un sistema orientado a los discos.

CARACTERÍSTICAS

El sistema operativo FEOS pretende aprovechar las posibilidades del procesador MC68EZ328 para desarrollar un sistema operativo seguro. Esto supone limitar el acceso de las aplicaciones a los recursos del sistema que podrían suponer un peligro para el kernel. Desafortunadamente el procesador dragonball no incorpora una MMU, lo que permitiría un aislamiento total entre aplicaciones, sin embargo sí que es posible tener un kernel protegido de las aplicaciones, aunque las aplicaciones no estén protegidas entre sí, y este ha sido el camino seguido para el desarrollo del FEOS.
Por otra parte he pretendido hacer un sistema multitarea, más como un reto personal que como una necesidad. Pero una vez implementada, la multitarea ha mostrado su utilidad práctica.

Las principales características  del FEOS son:


 

DESCRIPCIÓN
 


Organización de la memoria.

Como ya he comentado antes, la organización de la memoria es distinta para el daVinci de 2Mb que para el de 256Kb. La asignación de cada área a un dispositivo concreto se consigue mediante la reprogramación de la lógica de "Chip-Select" del 68ez328, cosa que se hace inmediatamente tras el arranque.

El mapa de memoria para el daVinci de 2MB queda como sigue:

Hay que destacar que el kernel ocupa tan sólo 28Kb de los 64 que tiene reservados. Las aplicaciones incluidas ocupan 57Kb, con lo que están a punto de ocupar otro sector de la flash a costa del sistema de ficheros 0. En la memoria flash secundaria se ubica la tabla de vectores, que ocupa sólo 1Kb, pero dado que el primer sector de la Flash es de 16Kb no podemos utilizar el resto de este sector para el sistema de ficheros 1, ya que los sistemas de ficheros deben comenzar justo en la frontera de un sector de la Flash.

El mapa de memoria para el daVinci de 256Kb es el siguiente:

En este caso, debido a la ausencia de la memoria Flash secundaria, no existe el sistema de ficheros 1, y la tabla de vectores está ubicada en la memoria RAM en una zona no protegida de las aplicaciones.

Scheduler

La multitarea en el sistema operativo FEOS se implementa de una forma muy simple. En primer lugar cada tarea tiene una o dos áreas de pila: una para el modo usuario y otra para el modo supervisor.  Una conmutación de tarea supone:
 

  1. Pasar a modo supervisor por una interrupción del temporizador o por TRAP #1.
  2. Guardar en la pila del supervisor de la tarea saliente el valor de todos los registros del procesador (incluyendo USP).
  3. Cambiar el valor del puntero de pila por el de la pila del supervisor de la nueva tarea.
  4. Recuperar el valor de todos los registros de la pila de la tarea nueva.
  5. Ejecutar la instrucción RTE (retorno de excepción). Esto recupera PC y SR de la pila.
Cada tarea tiene una entrada en una tabla en la que se almacena el último valor de su puntero de pila del supervisor. Además de este importante dato, en la tabla de tareas se almacena también el estado de la tarea, su prioridad, su nombre y el tiempo de CPU consumido.

Una tarea puede estar en varios estados: KILLED (-2), SLEEPED (-1), RUNNING (0) , o temporizando (>0). Las entradas de la tabla de tareas marcadas como KILLED no se corresponden con tareas en ejecución, y pueden ser reutilizadas al arrancar una nueva tarea. Las tareas en el estado SLEEPED no pasan nunca a ejecución. Tampoco pasan a ejecución las tareas que están en temporización, pero por cada interrupción de reloj se decrementa el valor de su estado, de modo que tarde o temprano cambiaran al estado RUNNING.

Para seleccionar la siguiente tarea que pasará a ejecución de entre todas las tareas en el estado RUNNING se debe tener en cuenta su prioridad. Para ello cada tarea tiene un contador al que se suma el valor de su prioridad cada vez que dicha tarea sea rechazada al seleccionar la nueva tarea.  Se elige por lo tanto la tarea con el mayor valor de contador, a la vez que dicho contador pasa a cero. De este modo las tareas con mayor prioridad pasan a ejecución más frecuentemente que las de baja prioridad, y todas las tareas tienen alguna probabilidad de ejecutarse.

Cuando no hay ninguna tarea en el estado RUNNING se pasa a la tarea número 0 (idle), que detiene el reloj del 68000 hasta la siguiente interrupción para reducir el consumo de corriente.

Las conmutaciones de tarea pueden deberse a las interrupciones de reloj o a que una tarea dada renuncie a su cuanto de CPU. En el primer caso tenemos el temporizador de propósito general del 68ez328 que genera 64 interrupciones por segundo. Este temporizador funciona en el modo "free running" a una frecuencia 1/8 de la de la CPU, y también se utiliza para llevar un recuento del tiempo de CPU consumido por las tareas en una variable de 64 bits. El tiempo de CPU de la tarea 0 (idle) nos da una idea del consumo de CPU del sistema.

Cuando una tarea no puede continuar su ejecución por esperar un evento ajeno a ella, pasa al estado SLEEPED y ejecuta una instrucción TRAP #1, que conmuta a otra tarea.

Por último, hay que destacar que la interrupción del temporizador del 68ez328 es muy prioritaria (nivel 6), de modo que podría interrumpir a rutinas de  interrupción de menor prioridad que necesiten una atención urgente, como es por ejemplo el caso de la interrupción de la UART (nivel 4). Para evitar largas esperas por parte de estas rutinas, que no finalizarían hasta que la tarea conmutada pase de nuevo a ejecución, en la rutina de interrupción del temporizador se comprueba el valor del registro  SR y si se detecta que se ha interrumpido a otra interrupción se evita la conmutación de tareas, que queda aplazada hasta la finalización de la rutina de interrupción de menor prioridad.

Llamadas al Sistema

Las aplicaciones que se ejecutan en modo usuario necesitan pasar a modo supervisor cuando pretenden utilizar alguno de los recursos protegidos. Las llamadas a las funciones del kernel se realizan a través de la instrucción TRAP #0. Los parámetros, y el código que identifica la función llamada se pasan en la pila de usuario de la tarea. La función, en lenguaje C, que realiza la llamada al sistema es la siguiente:

int syscall(int cix,...)
{
        asm volatile ("trap #0");
}

Los códigos de función están definidos en "syscall.h", y para ver los parámetros de cada función puede analizarse el código de "ksyscall.c".
Tras la ejecución de TRAP #0 la pila del usuario de la tarea queda de la siguiente forma:

Si la llamada se realiza en un programa escrito en lenguaje ensamblador se puede ignorar el valor del "PC de retorno" y llamar directamente a TRAP #0. Tras la llamada hemos de restaurar la pila. Sirva el siguiente ejemplo de "prim_dv.asm":

        *Buscamos 16Kb de memoria para matriz
        pea.l  $4000         *Tamaño del bloque
        pea.l  2                  *KMALLOC
        pea.l 0                  * Dir de retorno (no importa el valor)
        trap   #0                * SYSCALL
        lea.l  12(sp),sp    * Restauramos la pila
        move.l d0,a0        * El valor retornado está en D0

Terminales Virtuales.

Los terminales virtuales nos permiten conmutar la consola de una aplicación a otra simplemente pulsando la tecla "find" o a través del menú de tareas. Pulsando en la parte superior de la pantalla táctil se conmuta al terminal de la tarea "shell". Cada terminal incluye memoria para la pantalla, que puede funcionar en modos de 2, 4 o 16 niveles de gris, información del modo de video, del estado y posición del cursor, y una cola de eventos desde la que llegarán a la tarea propietaria del terminal las pulsaciones del teclado o de la pantalla táctil.

Sólo uno de los terminales permanece en primer plano. Gracias a la posibilidad de cambiar la base de la memoria de video del 68ez328 resulta fácil la conmutación de un terminal virtual a otro.

Hay que señalar un problema (posiblemente BUG) del 68ez328, que compromete la seguridad de los terminales de las tareas en modo supervisor: La zona reservada para la memoria de video debe estar ubicada siempre en la memoria RAM del usuario, ya que si se ubica en la memoria del supervisor aparece basura en la pantalla mientras la CPU ejecuta tareas en modo usuario. Este problema se debe sin duda a que las lineas FC0-FC2 durante los accesos de DMA son generadas por la CPU y no por el controlador de DMA como debería ser (otro suspenso para los diseñadores del 68ez328). Como resultado de este problema resulta que las tareas en modo usuario pueden corromper la pantalla de tareas de modo supervisor pues la memoria de video se ubica en la zona de RAM no protegida.

Colas de Eventos

En el sistema FEOS la interacción con el usuario del daVinci se realiza a través de eventos. El sistema maneja dos tipos de eventos: los eventos "crudos" y los "cocinados". Los primeros se generan en rutinas de interrupción y pueden ser debidos a las siguientes causas:

Estos eventos se añaden a una cola de donde son extraídos y procesados por la tarea "eventd". Esta tarea tiene como función convertir los eventos de bajo nivel en otros más elaborados ("eventos cocinados") y rutarlos a la tarea cuyo terminal virtual está en primer plano. Cada terminal virtual incluye por lo tanto una cola de eventos cocinados.

Una de las principales funciones de "eventd" es la de procesar las pulsaciones en la pantalla táctil, que supone en primer lugar una conversión de coordenadas desde los valores de los conversores A/D del chip "nexus" a unidades de la pantalla. Para ello se necesitan datos de la calibración de la pantalla táctil, aunque por defecto se usan unos parámetros que son buenos para mi daVinci particular, pero que pueden no serlo para otros. A continuación, dependiendo de las coordenadas obtenidas para la pulsación, se puede generar un evento EV_LCD_TAP_PRESS  si la pulsación cae sobre el área del LCD, o se realiza la emulación del teclado si la pulsación cae sobre el área inferior de la pantalla táctil (EV_KEYBOARD).

La tarea eventd también realiza funciones de registrador ("log"). Para ello mantiene abierto un terminal virtual que puede localizarse pulsando el botón "find" (es la propia tarea eventd la que cambia el terminal que está en primer plano). Cuando una tarea termina debido a una excepción eventd nos informa del tipo de excepción y del valor del PC cuando se produjo la excepción, lo que puede ayudar a investigar las causas del problema.

Emulación del Teclado.

El teclado se emula pulsando con el lápiz en el área de la pantalla táctil que queda bajo el LCD, y que originalmete se usaba  para el reconocedor de caractéres. Para no teclear a ciegas es necesario colocar la plantilla de la siguiente figura sobre la pantalla táctil:

    

Los botones del cursor del daVinci también generan eventos de teclado, idénticos a los que se obtienen pulsando sobre las teclas de las flechas.
 

Programación de la Memoria FLASH.

La programación y el borrado de las memorias Flash requiere ciertas precauciones. En primer lugar hay que señalar que el propio sistema operativo reside en una Flash, y que durante las operaciones de programación y borrado dicha Flash no se puede leer. Por lo tanto las rutinas de programación y borrado de la flash residen en la memoria RAM, donde se copian al arrancar el sistema junto con las variables estáticas no inicializadas (sección ".data", ver flash_prg.s). Mientras dura la programación o el borrado de las memorias flash no esta permitida ninguna interrupción, ya que la tabla de vectores o las propias rutinas de interrupción pueden no estar presentes al no poder leerse las memorias Flash.

Sistema de Ficheros.

El sistema operativo FEOS puede manejar uno o dos sistemas de ficheros residentes en las memorias flash. El primer sistema de ficheros ocupa la misma memoria que el sistema operativo, mientras que el segundo ocupa la mayor parte de la flash secundaria y sólo está presente en el daVinci 2MB.

Cada sistema de ficheros debe comenzar en un sector de la memoria Flash. Esto es así ya que el borrado de la memoria flash se realiza sector a sector, y por ello no queremos que un sector de la flash contenga además del sistema de ficheros otros datos.

La estructura del sistema de ficheros es sumamente simple. Un fichero no es más que una zona de datos, no fragmentada, precedida de una cabecera. En dicha cabecera se almacena información relativa al fichero, como su tipo, tamaño y nombre. La secuencia de ficheros forma una lista que se va incrementando cada vez que se añade un fichero. Cuando se borra un fichero lo que en realidad se hace es cambiar su tipo por el valor 0. Aprovechamos para ello una de las características de las memorias flash, que una vez borradas contienen todos sus bits en 1, y que al programarse escriben los bits 0 de los datos.

Los ficheros borrados no retornan al sistema de ficheros el espacio ocupado, dado que siguen estando presentes en la memoria flash. Para recuperar este espacio es necesario realizar una desfragmentación de los sistema de ficheros. La utilidad necesaria para dicha desfragmentación está incluida en la tarea "shell", y no conviene abusar de ella, ya que supone un riesgo para los datos. Durante la desfragmentación los ficheros no borrados y sus cabeceras se copian a la memoria RAM, se borran los sectores de la flash ocupados por el sistema de ficheros, y finalmente se vuelve a programar la memoria flash con los ficheros que estaban en RAM. Esta operación, en principio parece simple, pero hay que tener en cuenta que el sistema de ficheros puede ser mucho mayor que la memoria RAM disponible, lo que complica las cosas. Antes de relizar la desfragmentación conviene asegurarse de que las pilas tienen suficiente carga, que está libre al menos la mitad de la memoria RAM del supervisor, y que no se están transfiriendo ficheros desde el PC. Cuantas menos tareas estén en marcha antes de la desfragmentación mucho mejor.
 

Comunicaciones.

Las comunicaciones del daVinci con el PC se realizan a través de la UART del 68ez328. El formato elegido para los datos es de 8 bits sin paridad y un único bit de stop. La velocidad es la máxima posible en el PC: 115200 baudios. En el sistema operativo FEOS se utiliza la interrupción de la UART para implementar el protocolo SLIP mediante una "máquina de estados". Asimismo se aprovechan las ventajas de las FIFOS de la UART para reducir el número de interrupciones.

Sobre el protocolo SLIP no se ha implementado ningún otro protocolo como IP, ICMP, UDP o TCP,  ya que en una comunicación como ésta no es necesario pues complica mucho el sistema operativo y enlentece las comunicaciones por el gran tamaño de las cabeceras de los paquetes.

La tarea remoted se encarga de atender a las peticiones realizadas desde el PC, y por lo tanto opera como servidor. Mediante la tarea remoted se pueden leer los ficheros del daVinci, se pueden pasar desde el PC ficheros nuevos y se pueden capturar fotos de la pantalla del daVinci. El protocolo es muy simple: "remoted" espera una orden del PC, y contesta con el resultado de la ejecución de dicha orden. Cada mensaje ocupa un único paquete SLIP, de tamaño inferior o igual a 256 bytes de datos útiles (configurable) . Los códigos de los comandos (ver "remote.h") son de 16 bits para mantener los datos de los paquetes alineados a direcciones pares.

Por supuesto, en el PC se debe ejecutar una aplicación cliente. Se ha escrito dicho cliente ("dvtrf.c") para sistemas Linux con procesador "Little Endian" (es decir para PC's y tal vez alphas. Para procesadores "Big Endian", como SPARC, hay que reescribir las funciones "swapw" y "swapl"). En dicha aplicación se implementa el protocolo SLIP, de modo que no es necesario configurar un interfaz SLIP en el puerto serie del cradle.

A continuación se comenta el uso de "dvtrf" para comunicar el daVinci y el PC:

Para leer todos los ficheros del daVinci al PC se ejecutará en el PC:

dvtrf get 0 0

Los argumentos que siguen a "get" son el tipo y subtipo de los ficheros y actúan como filtro (ver "file.h"). El valor 0 significa todos los tipos o todos los subtipos. Si por ejemplo queremos recuperar sólo los ficheros de tipo texto (3) y todos los subtipos ejecutaríamos:

dvtrf get 3 0

Hay que destacar que en el daVinci no es necesario arrancar ninguna aplicación de tipo "smart/dumb Sync" para atender al PC, ya que la tarea "remoted" está siempre a la escucha. Es la ventaja de la multitarea.

Para pasar ficheros del pc al daVinci se utiliza "dvtrf put <ficheros>", como en el siguiente ejemplo:

dvtrf put prim_dv.pic frank.4bpp

El programa dvtrf genera el tipo y subtipo de los ficheros a partir de la extensión de los mismos. Así  ".pic" significa "código máquina ejecutable, independiente de la posición" y ".4bpp" significa "Volcado de pantalla de 4 bits por pixel (16 niveles de gris)". Los nombres de los ficheros se truncan si superan los 15 caracteres (sin extensión), pero no pasa nada aunque se tengan nombres repetidos (aunque no es una buena idea a la hora de identificar los ficheros).

Por último, para obtener una foto de la pantalla del daVinci ejecutaremos:

dvtrf dump foto.pgm

Esto generará un fichero "foto.pgm" que puede ser convertido a otros formatos con aplicaciones como "xv" o "gimp".
 
 

Aplicaciones

En el sistema operativo FEOS podemos tener hasta 3 tipos de aplicaciones:

  1. Tareas de modo supervisor del kernel.
  2. Aplicaciones "built-in".
  3. Ficheros ejecutables.
Entre las aplicaciones del primer tipo tenemos tareas como "eventd", el procesador de eventos, "remoted", el servidor de comunicaciones con el PC, el propio entorno de menús o "shell", y los visores de ficheros de texto y gráficos. Todas estas tareas funcionan siempre en modo supervisor, de modo que no están sujetas a las protecciones del kernel, y pueden obviar todo el mecanismo de las llamadas al sistema, aumentando por lo tanto su velocidad de ejecución. Por otra parte han de ser sumamente fiables, pues una simple excepción en una de estas tareas echaría abajo todo el sistema.

En el espacio de memoria del daVinci se ha previsto un área para aplicaciones de tipo "built-in". Estas aplicaciones se ejecutan en modo usuario, por lo que necesariamente han de recurrir a las llamadas "syscall". Por otra parte, al residir en una zona fija de la memoria pueden compilarse sin recurrir a una versión de GCC que genere código PIC (código independiente de la posición). Esta es la principal razón de su existencia (no tengo todavía un GCC que genere código PIC para el 68000). Otra ventaja de estas aplicaciones es que al estar todas ellas "linkadas" juntas, comparten sus funciones de librería, lo cual reduce el espacio de memoria ocupado, y sólo resulta problemático cuando hay funciones no reentrantes. En particular se comparten todas las funciones de la emulación de la coma flotante y de la librería "libm", que ocupan unos cuantos Kb de memoria.

Por último, tenemos también la posibilidad de cargar en el daVinci ficheros de tipo ejecutable. Hasta el momento el único tipo de fichero ejecutable soportado es el de "Código Máquina independiente de la posición" (extensión .pic para dvtrf). Estos ficheros se ejecutan en modo usuario, con una pila de 512 bytes de largo, y los direccionamientos empleados deben ser tales que se ejecuten correctamente en cualquier posición de la memoria, pues no sabemos a priori cual va a ser su ubicación en la Flash. Se presenta un ejemplo escrito en lenguaje ensamblador, el programa prim_dv.asm.