μPOSIX: Una biblioteca POSIX para microcontroladores Pablo Ridolfi, Leandro Kollenberger Laboratorio de Procesamiento Digital - Departamento de Ingeniería Electrónica Universidad Tecnológica Nacional - Facultad Regional Buenos Aires Buenos Aires, Argentina {pridolfi, lkollenberger}@frba.utn.edu.ar 1. Introducción Actualmente el mercado de microcontroladores ofrece una gran variedad de arquitecturas con configuraciones de periféricos y memoria muy diferentes entre sí, ya sea que se trabaje con un mismo o diferentes fabricantes de circuitos integrados. Esta variabilidad de hardware resulta en una escasa capacidad de portabilidad del firmware o software embebido que el desarrollador debe enfrentar (Fig. 1), muchas veces diseñando su propia Capa de Abstracción de Hardware (HAL, por sus siglas en inglés), y por lo tanto desperdiciando tiempo que podría dedicarse a la implementación de su aplicación. En este trabajo se propone la utilización del estándar POSIX para la implementación de una HAL orientada a su uso en microcontroladores, que provea una interfaz estándar y unificada para el manejo de periféricos y dispositivos, así como el desarrollo de aplicaciones multihilo de tiempo real a partir del uso de las funciones Pthread (POSIX Threads) (Fig. 2). Figura 1. Modelo actual de API para microcontroladores Figura 2. Modelo de API propuesto por μPOSIX 2. Implementación de POSIX Threads (Fig. 3) Figura 3. Posibles estados de un Thread de μPOSIX Al llamarse a pthread_create(), se piden recursos de memoria al sistema para el almacenamiento del estado de ejecución del nuevo thread. Acto seguido se setea en estado READY: el hilo está en la cola de ejecución esperando acceder a recursos de CPU. Desde ese momento, el scheduler selecciona la próxima tarea a ser ejecutada cambiando su estado a RUNNING y le cede el uso del CPU. El scheduler podrá pausar esta tarea para resumirla más tarde según sea necesario y según lo dicte su política de scheduling. Esta tarea podrá pasar al estado BLOCKED si en algún momento requiere el acceso a algún recurso bloqueante. En este caso el scheduler le asignará el CPU a otra tarea que esté en la cola de tareas en estado READY, buscando a partir de las de prioridad más alta, hasta que la tarea anterior en estado BLOCKED consiga el acceso a su recurso pedido, caso en el que volverá a estado READY y a la cola de espera antes de pasar a ejecución. El thread seguirá un ciclo de ejecución hasta que finalice o sea cancelado externamente por otro thread. Si el thread finaliza, llamará internamente a la función pthread_exit(), quien se ocupará de marcarlo como ZOMBIE, y esperar un pthread_join() de otro thread para pasarle su valor de retorno, y finalmente pasar a estado DETACHED. Si se requiere, se puede llamar a la función pthread_detach(), que pasará a la tarea a este último estado sin la necesidad de devolver un parámetro. Por último, el scheduler se encargará de liberar los recursos de los threads en estado DETACHED en su próximo barrido de la lista de tareas. 3. Ejemplos de uso /* Uso de GPIO */ int fd_gpio = open("/dev/gpio", 0); /* Set SysTick user IRQ Handler */ int fd_systick = open("/dev/systick", 0); /* GPIO0 Off */ devGPIO_pin_t led = devGPIO_pins[0]; led.value = 0; ioctl(fd_gpio, devGPIO_REQ_WRITE_BIT, &led); ioctl(fd_systick, devSysTick_REQ_SET_CALLBACK, (void *)mySysTickCallback); /* GPIO0 Toggle */ ioctl(fd_gpio, devGPIO_REQ_TOGGLE_BIT, &led); /* Crear Thread */ pthread_t id; pthread_create(&id, 0, thread_entry, (void*)argumento); Más información en github.com/pridolfi/uposix