Páginas

domingo, 26 de febrero de 2012

PyMite from Interruption Space

Se que ultimamente estoy hablando mucho de PyMite en lugar de sobre Gaia, mi verdadero proyecto para el concurso de este año, pero puesto que en cierto modo se me quedo la espinita clavada por la tonteria del bug por usar una version de Python demasiada moderna, lo cierto es que lo he estado usando como campo de pruebas para despues aplicar las mejoras a Gaia directamente. Sin embargo, esta vez mi trabajo en PyMite se merece un post por derecho propio:

PyMite capturando los scancodes del teclado mediante eventos. Si niños: he conseguido capturar y procesar interrupciones directamente desde dentro de Python :-D

¿Que como lo he hecho? Bien, vayamos por partes:

  • En primer lugar, hace falta tener funcionando un gestor de interrupciones en C. Esto no es demasiado problema, puesto que tambien es necesario para poder capturar las interrupciones del reloj del sistema (que eso ya lo habia conseguido) y para poder capturar las interrupciones del teclado para poder usarlo como entrada estandar, asi que eso ya estaba listo. Trivial, vamos.
  • Una vez hecho eso, el siguiente es añadir handlers para las interrupciones que queremos manejar (en principio todas). Para ello, me he hecho una funcion init() nativa dentro de events.py (un nuevo modulo que he hecho para gestionar los eventos, luego hablo mas de ello) que se encarga de inicializar la cola de eventos, de registrar el handler para las interrupciones en los que estamos interesados para que las guarde en la cola de eventos, y tambien de guardar un puntero a la funcion que se va a encargar despues de extraer los eventos y procesarlos, esta vez ya en Python. Todo el codigo esta sacado de Gaia con pequeñas modificaciones ya que la filosofia de diseño de hacerlo puramente orientado a eventos es comun para los dos, asi que he decidido reutilizar la idea de usar una unica cola global de eventos y que despues se vayan "bombeando" y procesando secuencialmente uno detras de otro como pequeñas unidades de codigo atomicas e independientes, muy inspirado en como funcionan Node.js o el motor de eventos Twisted (del cual soy un fan declarado... :-D ).
  • Una vez que se produce una interrupcion, se lanza el handler que hemos definido para manejarlos (tambien nativo) el cual simplemente añade un nuevo evento a la cola de eventos, llama al bombeador de eventos y sale de la interrupcion (esta fue sencilla... ;-) ).
  • Finalmente, el bombeador de eventos se asegura de que nadie mas los esta procesando (principalmente porque se haya producido una interrupcion mientras todavia no se habian terminado de procesar los pendientes) y en tal caso, va vaciando la cola de eventos y procesandolos uno a uno ejecutando las funciones que se hubiesen registrado para cada uno de ellos. Al tener que acceder a la cola de eventos y estar esta declarada en C estoy usando un par de funciones nativas para comprobar si esta vacia y para extraer uno de los eventos, nada mas.
Simple, facil y para toda la familia :-D Y por supuesto, todo subido al repositorio... :-)

Sin embargo hay truco: por un lado, no estoy generando eventos en Python propiamente dichos, sino que solo estoy guardando el nombre del evento. Esto es asi para que el handler fuera mas pequeño al posponer la conversion a objetos Python y volviera antes de la interrupcion, pero sin lugar a dudas si quiero usar una cola de eventos unica tendre que cambiarlo. Otra opcion seria el tratar las interrupciones en dos pasos, con una cola de interrupciones y despues convertirlas y añadirlas a la cola de eventos en un paso posterior. Esto aislaria por completo el procesamiento de interrupciones de el de eventos con lo que quedaria muchisimo mas limpio, pero tambien añadiria mas retrasos... habra que estudiarlo. Otro truco que usa que me ha sugerido el propio autor de PyMite (aunque al final no lo estoy haciendo como el me ha propuesto) y que es mas interesante, es que al tener PyMite su propio gestor de hilos y ademas puede cambiar automaticamente de uno a otro si ha estado uno de ellos mucho tiempo en la CPU (¿hilos preemptivos? eso en mi barrio se le llama procesos... :-P ) lo estoy aprovechando y en lugar de procesar los eventos directamente (con lo que el "proceso" que estaba en ejecucion cuando se produjo la interrupcion se queda esperando) los estoy lanzando en un nuevo hilo para que se procesen de forma autonoma mas adelante y asi poder volver inmediatamente de la interrupcion :-)


Por otra parte, hay espacio para limpiar codigo y para optimizar para aburrir. En primer lugar, actualmente no es threadsafe puesto que no estoy usando ninguna estructura que lo haga. En Gaia el bombeo de eventos esta protegido con un lock, pero al necesitar guardar la funcion a usar para el bombeo desconozco si podria usar una funcion nativa (supongo que si), por lo que estoy usando un simple booleano (¡¡¡error!!! :-S ), asi que eso tengo que arreglarlo pronto. Tambien estaria la posibilidad de usar la clase Queue de Python, pero tiene muchas dependencias que la libreria estandar de PyMite no me satisface, asi que por el momento descartado. Por ultimo, claro esta, lo que he comentado anteriormente de separar la cola de interrupciones de la de eventos, pero esa es otra historia... :-D En resumen, que en unos commits estara listo ;-)

Y eso es todo por ahora, me falta explicar como se usan dichas interrupciones desde Python pero eso lo explicare en otro post (aunque lo cierto es que ahora deberia centrarme en estudiar para los examenes... :-/ ). Lo que si me he dado cuenta es que el problema de la limitacion de memoria para el kernel en x86 es real, ya que me ha vuelto a pasar con PyMite aunque increiblemente en PyMite ha tardado mucho mas en aparecer que con Gaia: ha sido eliminar las funciones de acceso a los puertos de entrada/salida que no estaba usando y arreglarse el problema... :-/

P.D.: para los que no lo hayan entendido, el titulo del post es una parodia de Plan 9 from User Space, el port para Unix/Linux de las librerias del sistema operativo Plan 9, con el cual tengo una relacción de amor-odio digna de poner en el Facebook como "es complicado"... :-P