Archive for the 'Programación' Category

Pantalla tactil

jueves, agosto 22nd, 2013

Por fin he conseguido acceder a la pantalla táctil. Esta tablet lleva un controlador GSL1680, para el cual no hay casi documentación. Sin embargo, pude encontrar un presunto driver, y de él sacar mucha información.

Después de muchas pruebas fui capaz de leer las coordenadas, pero sólo si arrancaba Linux en caliente (o sea, si arrancaba primero Android y luego reiniciaba). Si apagaba a machete la tablet y encendía directamente en Linux, no funcionaba.

Tras investigar, descubrí que el motivo es que el chip necesita que le envíen el firmware para funcionar. Si arrancaba Android, éste le metía dicho firmware, y al reiniciar todo seguía funcionando. Pero si apagaba la tablet, al quedarse sin corriente, el firmware se borraba, y es necesario volver a cargarlo.

Encima, parece que el firmware es específico para cada modelo. Afortunadamente, en el caso de mi tablet éste estaba disponible en un fichero de texto, en /system/etc.

Los detalles específicos de programación los he escrito en la página sobre el GSL1680 en linux-sunxi, y como no me apetece repetirlo todo, me limitaré a poner un enlace.

Arrancando Linux en la tablet

lunes, agosto 5th, 2013

Actualizado: Hoy me puse a intentar meter Linux en la tablet de la que hablé en la anterior entrada. Y lo he conseguido, he aquí la prueba:

tablet_linux

Dado que esta tablet está equipada con un Allwinner A13, necesitamos un kernel y un cargador adecuados. Lo interesante de este chip es que no hace falta tocar la flash interna, sino que basta con ponerlos en una tarjeta SD, y arrancará desde ésta directamente. Y si luego queremos volver al sistema original, basta con retirar la tarjeta y reiniciar.

El núcleo y el cargador los podemos obtener del repositorio GIT linux-sunxi, orientado precisamente hacia este tipo de SoCs. Las instrucciones básicas para compilar y generar todo las saqué de la página de Olimex, y las he adaptado a este modelo.

Supondré que tienes una distribución basada en Debian. Lo primero que hay que hacer es instalar los paquetes de compilación cruzada y otras bibliotecas necesarias:

    sudo apt-get install gcc-4.7-arm-linux-gnueabi gcc-4.7-arm-linux-gnueabi-base gcc-arm-linux-gnueabi libncurses5-dev uboot-mkimage build-essential git libusb-dev libusb-1.0-0-dev

Ahora bajamos el u-boot modificado con:

    git clone -b sunxi https://github.com/linux-sunxi/u-boot-sunxi

Entramos en el directorio u-boot-sunxi y compilamos el código:

    cd u-boot-sunxi
    make CROSS_COMPILE=arm-linux-gnueabi- a13-olinuxino
    ls -l u-boot.bin spl/sunxi-spl.bin
    cd ..

Ahora que tenemos compilado el cargador, vamos con el núcleo. Nos lo bajamos desde el repositorio, preparamos una configuración base y entramos en modo configuración:

    git clone -b sunxi-3.0 https://github.com/linux-sunxi/linux-sunxi
    cd linux-sunxi
    make ARCH=arm a13_defconfig
    make ARCH=arm menuconfig

tablet_config

Entre las opciones que he tocado están las siguientes:

  • General Setup
    • POSIX message queues -> Y
  • System Type
    • Emulate SWP/SWPB instructions -> N
  • Device Drivers
    • Network Device Support
      • Ethernet (10 or 100Mbit) -> N
    • Character Devices
      • Non-standard serial port support -> N
      • Serial drivers
        • 8250/16550 and compatible serial support -> N
    • Console Display driver support
      • Map the console to the primary display device -> Y
    • Bootup logo -> Y
  • Security Options
    • Enable the securityfs filesystem -> N

 

Una vez configurado, procedemos a compilar el núcleo:

    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- uImage
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- INSTALL_MOD_PATH=out modules
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabi- INSTALL_MOD_PATH=out modules_install

Ahora llega el momento de generar la tarjeta SD con el sistema básico. Para ello, primero necesitamos dividirla en, al menos, dos particiones, una en formato VFAT (donde irá el núcleo) y otra en formato EXT2/3/4 con el sistema en sí. Así que arrancamos fdisk y empezamos. Es importante que la primera partición empiece, al menos, en el sector 2048, para dejar sitio para el arranque.

Una vez formateadas ambas particiones, descargamos un sistema Debian base, lo descomprimimos en la partición EXT2/3/4 (que supondremos que está montada en /media/particion2) y copiamos los módulos (asumiendo, también, que la versión de núcleo que tenemos es la 3.0.76):

    wget http://rcn-ee.net/deb/rootfs/wheezy/debian-7.1-console-armhf-2013-07-22.tar.xz
    tar xvf debian-7.1-console-armhf-2013-07-22.tar.xz
    sudo tar -C /media/particion2 -xvf debian-7.1-console-armhf-2013-07-22/armhf-rootfs-debian-wheezy.tar
    cd ../linux-sunxi
    sudo cp -a linux-allwinner/out/lib/modules/3.0.76/ /media/particion2/lib/modules/

Es importante descomprimirlo como root para que conserve los nombres de usuario correctos.

Ahora bajamos los ficheros de definición de placas y las utilidades, y las compilamos:

    git clone https://github.com/linux-sunxi/sunxi-boards
    git clone https://github.com/linux-sunxi/sunxi-tools
    cd sunxi-tools
    make

Ahora vamos a generar el fichero script.bin, necesario para configurar el núcleo durante el arranque. Para ello utilizaremos las utilidades que acabamos de compilar y el fichero de configuración a13_mid.fex, situado en el repo sunxi-boards, por ser el más parecido a nuestra tablet:

    ./fex2bin ../sunxi-boards/sys_config/a13/a13_mid.fex script.bin

El siguiente paso consiste en copiar el fichero uImage y script.bin en la partición VFAT de nuestra tarjeta SD:

    cp script.bin /media/particion1
    cd ../linux-sunxi
    cp arch/arm/boot/uImage /media/particion1

Por último sólo queda instalar u-Boot en la tarjeta, para lo cual hacemos (asumiendo que la tarjeta está en /dev/sdX):

    cd ../u-boot-sunxi
    sudo dd if=spl/sunxi-spl.bin of=/dev/sdX bs=1024 seek=8
    sudo dd if=u-boot.bin of=/dev/sdX bs=1024 seek=32

Y con esto ya está. Sólo queda meter la tarjeta SD en la tablet, encenderla, y conectar un teclado USB.

El próximo paso será intentar activar la WiFi, algo que me está dando bastantes quebraderos de cabeza.

Simplificando Vala y CMake

viernes, abril 5th, 2013

He estado tres semanas currando sin parar en mi trabajo-que-paga-las-facturas, y aunque no pude disfrutar ni findes ni vacaciones (¡Gracias, gracias, ministros que pusieron la fecha de entrega de los innterconecta para justo después de semana santa! ¡Así os salga un cardo en el sobaco!), sí pude aprovechar algunos ratos muertos en casita para hacer un proyectillo personal: Autovala.

La idea detrás de Autovala surgió de casualidad de una lista de correo: un usuario se quejaba de que trabajar con CMake era muy pesado, y que obligar a usarlo para los proyectos de la lista sólo conseguía reducir la cantidad de desarrolladores que estarían dispuestos a colaborar. Y la verdad es que tiene algo de razón: empezar un proyecto nuevo con CMake es una tarea realmente aburrida, por la sencilla razón de que es muy repetitiva.

Pero cuando una tarea es repetitiva… es ideal para que la haga una máquina. Así que mi idea fue: ¿por qué no dejar que sea el propio ordenador quien genere de manera automática los ficheros CMake?

Y esa es, precisamente, la idea detrás de Autovala: generar de manera automática todos los ficheros para CMake, en base a una serie de heurísticos. Así, por defecto, todos los ficheros .vala que haya en el directorio src (o subdirectorios dentro de él) se compilarán juntos para hacer un único binario. Además, añadirá automáticamente los paquetes necesarios para cada fichero, para lo que echa un vistazo a las directivas using del propio código. Lo mismo para otros elementos: por ejemplo, para los iconos comprobará qué tamaño tienen y, en base a ello, decidirá automáticamente en qué carpeta deben ir. Y más, mucho más.

Por supuesto, es posible añadir excepciones a mano para ajustar aquellas cosas en las que las reglas no funcionen bien; por ejemplo, si lo que queremos es generar una biblioteca, o si nuestro proyecto está compuesto de varios ejecutables. La ventaja en estos casos es que sólo hay que hacer eso: indicarle que queremos que sean dos, tres, o los que sean, ejecutables y donde buscar las fuentes; autovala seguirá haciendo el resto automáticamente (buscar paquetes necesarios, etc). Además, los cambios que haga el usuario se recuerdan, por lo que si se añaden o eliminan ficheros, basta con recrear el fichero de proyecto, y se tendrán en cuenta todas las excepciones hechas antes.

Obviamente Autovala no sirve para todos los proyectos, pero sí para la gran mayoría, que es lo interesante.

Subtitulando

jueves, noviembre 15th, 2012

Estos días estoy liado buscando alternativas a periscope para bajar subtítulos de manera automática. El motivo es que no es un proyecto que esté siendo mantenido de manera razonable.

De casualidad encontré subliminal, un programa similar pero con más fuentes de subtítulos, y decidí echarle un ojo. La verdad es que funciona muy bien, pero tiene el inconveniente de que, al igual que periscope, está escrito en python, lo que significa que hay que meter una máquina virtual completa en mi WebTV.

Pero de casualidad encontré submarine, otro programa similar pero escrito en Vala, lo que significa que puedo compilarlo en un ejecutable y ahorrarme todo el interprete de python. Por desgracia, este sólo soporta búsquedas en OpenSubtitles.org y en Podnapisi.net. Sin embargo, su modelo interno es tan sumamente sencillo que he conseguido añadir soporte para SubDB (el cual ya está subido a mi repositorio GIT personal y solicitado un commit al oficial).

Extraoficialmente también he conseguido implementar soporte para BierDopje, pero ese código todavía no lo puedo subir porque sigo a la espera de que me concedan una clave específica para la API (en estos momentos estoy usando prestada la de subliminal, pero cada aplicación tiene que usar la suya propia).

El uso de este portal tiene su complicación, en buena medida porque mientras que SubDB utiliza un hash del archivo para buscar subtítulos, BierDopje pide el nombre de la serie, la temporada y el capítulo. En subliminal se utiliza la impresionante biblioteca guessit, que, a partir del nombre del fichero, y mediante una serie de heurísticos, extrae toda la información posible sobre un fichero. Obviamente dicha biblioteca está escrita en python, así que tuve que hacer una versión de andar por casa en Vala. El concepto básico es el mismo (de hecho, fusilé todo lo que pude), pero, al estar hecha en una noche, no es tan precisa. Obviamente acepto parches para mejorarla.

Funciones asíncronas en Vala

jueves, agosto 30th, 2012

Actualizado. Acabo de modificar un poco el código de Cronopete para que haga uso de las funciones asíncronas de Vala. El motivo es eliminar, en la medida de lo posible, el uso de threads.

Las funciones asíncronas de Vala son una ayuda muy interesante a la hora de realizar tareas que requieren mucho tiempo, a la vez que se quiere evitar que la interfaz se bloquee. La solución más inmediata en estos casos consiste en crear un thread y realizar en él todo el proceso, pero en ocasiones es una solución muy compleja, pues se necesita intercambiar datos entre éste y el thread principal, o sincronizar ambos. La solución está en las funciones asíncronas. Estas son funciones que pueden interrumpirse y reanudarse a voluntad en cualquier punto, lo que permite realizar una forma de multitarea cooperativa de manera sencilla.

Imaginemos que definimos una función o método asíncronos así:

public async int AsyncFnc(int p1, int p2, out int p3, out String p4) {
    // código de la función asíncrona (también puede ser un método de una clase)
    [...]
}

Para llamar a esta función, lo haremos de esta manera:

AsyncFnc.begin(p1, p2, callback_finalizacion);

Vemos que sólo incluimos los parámetros de entrada, pero no los de salida. Por otro lado, tampoco recogemos el valor devuelto. Por último, tenemos un misterioso callback_finalizacion. Este parámetro es opcional, y sólo hay que añadirlo cuando se desea ejecutar algún código cuando la función asíncrona finalice. Es en dicha función donde recogeremos los valores de salida y cualquier excepción que se produjese durante la ejecución de la función asíncrona. Dicho callback es de tipo GLib.AsyncReadyCallback, y su formato es el siguiente:

public delegate void AsyncReadyCallback (Object? source_object, AsyncResult res)

Sin embargo, Vala nos ofrece algunas ventajas, como son las funciones anónimas y los closures, que nos dan como ventaja el poder acceder desde el callback a variables externas a éstas. Así pues, utilizando una función anónima podemos llamar a nuestra función asíncrona así:

AsyncFnc.begin(p1, p2, (obj,res) => {

    int retval;
    retval=AsyncFnc.end(res, out p3, out p4);

    // resto del código del callback
    [...]
});

De esta manera, cuando la función asíncrona llegue al final y retorne, se ejecutará el código de callback contenido en el closure. Vemos también que llamamos a AsyncFnc.end; esta es, precisamente, la manera que tenemos de obtener el valor devuelto por la función asíncrona, así como los parámetros de salida. También es esta llamada la que debemos rodear con un try/catch() si queremos capturar cualquier excepción lanzada desde dentro de la función asíncrona.

Así pues, ya sabemos llamar a una función asíncrona, y sabemos como recibir sus resultados. ¿Pero como hacemos para que ceda el control y devolvérselo? No olvidemos que NO se tratan de varios threads paralelos, sino de un único hilo de ejecución.

La respuesta está en yield y .callback(). La primera es una llamada que, al realizarla dentro de una función asíncrona, devuelve el control a la función llamante. La segunda es un método de la función asíncrona (igual que .begin y .end; podemos comparar una función asíncrona con un objeto que ofrece esos tres métodos públicos) que lo que hace es continuar la ejecución de dicha función justo después del último yield ejecutado. Un detalle interesante es que las llamadas a yield se pueden situar absolutamente en cualquier parte de una función asíncrona, incluso dentro de un bucle, lo que nos permite, de una manera sencilla, devolver el control en cada iteración para que se vayan ejecutando otras partes del código, y recuperarlo para la siguiente iteración.

Ahora que ya tenemos la imagen completa podemos ver como se ejecuta el código: cuando una función normal llama a una función asíncrona, le cede el control y ésta comienza a ejecutarse hasta alcanzar el primer yield. En ese punto la llamada retorna a la función normal, y sigue ejecutándose el código situado a continuación de la llamada. Cuando, en algún punto posterior, se llame al .callback() de la función asíncrona, el control pasará al código situado justo después de dicho primer yield, y seguirá ejecutándose hasta que encuentre otro yield, momento en que el control de la ejecución pasará al código situado justo después de la llamada a .callback(). Este juego de ping-pong continuará hasta que la función asíncrona llegue al final y retorne. En ese momento se ejecutará el código del callback y continuará la ejecución después de la última llamada a .callback().

Un ejemplo gráfico

Para entender mejor este proceso, veámoslo en forma gráfica. Por cuestiones de claridad lo dividiremos en dos imágenes: en la primera veremos el flujo desde que se inicia la función asíncrona hasta que está a punto de acabar, y en la segunda desde que se ejecuta el return al final.

En la imagen anterior vemos la primera parte del ciclo de vida de una función asíncrona. Cada función está representada por un rectángulo, y dentro de ella, el flujo de ejecución natural es de arriba a abajo.

Desde el Main Loop de la aplicación se llama a un callback (por ejemplo, porque el usuario ha pulsado un botón en la interfaz gráfica), llamado Function en este ejemplo, y en éste se llama a nuestra función asíncrona (llamada AsyncFnc en el ejemplo), que comienza a ejecutarse. En el momento en el que se alcanza el primer yield, el control vuelve al callback que la lanzó, que sigue su ejecución hasta que, en un punto, éste llama a AsyncFnc.callback(). En ese momento el flujo de ejecución vuelve a la función asíncrona, que continúa justo en la instrucción que sigue al primer yield. Un poco después se llega a un segundo yield, que devuelve el control al callback original.

Cuando el callback termina, se devuelve el control al Main Loop, como siempre. Pero la función asíncrona aún no ha terminado. Vemos entonces que en dos puntos determinados se llama a AsyncFnc.callback(). Estas llamadas no son automáticas, sino que tuvieron que ser preprogramadas (por ejemplo con un temporizador, o con una llamada a Idle.add(AsyncFnc.callback) para que se ejecuten en un momento en que no hay eventos por despachar). Cada vez que se llama a dicha función, la ejecución de la función asíncrona continúa justo después del último yield, hasta que encuentra otro, o bien hasta que finaliza la función.

En el siguiente esquema, las flechas en rojo muestran el flujo de ejecución cuando la función asíncrona llega al final: en ese momento se ejecuta el callback registrado cuando se lanzó la función (si es que existe, claro; si no se definió, se retorna directamente), y cuando se termina éste, se retorna al punto de llamada.

Llamar a una función asíncrona desde otra función asíncrona

A la hora de llamar a una función asíncrona desde otra función asíncrona, la primera idea que nos puede venir a la cabeza es hacer

// ATENCION, ESTE CODIGO NO FUNCIONARA CORRECTAMENTE

async void AsyncFnc1() {
    int retval;

    [...]
    AsyncFnc2( (ob,res) => {
        retval=AsyncFnc2.end();
        AsyncFnc1.callback();
    });
    yield;
    [...]
}

Aparentemente, este código lanzaría AsyncFnc2 y suspendería la ejecución de AsyncFnc1, pero de manera que, al terminarse la ejecución de AsyncFnc2, continuaría la ejecución de AsyncFnc1.

Por desgracia, ese código es probable que no funcione correctamente, como mínimo en un caso concreto: si, por la razón que sea, la función AsyncFnc2 finaliza sin llamar a yield al menos una vez, se llamará antes a AsyncFnc1.callback() que al yield, por lo que el código fallará. Una solución a este problema sería utilizar Idle.add(AsyncFnc1.callback) en su lugar, pero sólo funcionará si tenemos un Main Loop en nuestro programa, pues será éste quien llame al callback después de que la función asíncrona retorne tras ejecutar el yield.

Afortunadamente, los diseñadores de Vala eran conscientes de que es muy común llamar a una función asíncrona desde otra, por lo que simplificaron notablemente este caso concreto. Así, para llamar a AsyncFnc2 desde AsyncFnc1, de manera que ésta última continúe su ejecución cuando acabe la otra, sólo hay que hacer

async void AsyncFnc1() {
    int retval;

    [...]
    retval=yield AsyncFnc2();
    [...]
}

Otra ventaja es que, en este caso, no necesitamos llamar a AsyncFnc2.end() para obtener los resultados, porque se hace directamente.

El flujo de ejecución sería así:

Vemos que desde el Main Loop llamamos a nuestra primera función asíncrona, AsyncFnc1, y después de un trozo de código llamamos, mediante yield, a nuestra segunda función asíncrona, AsyncFnc2. En ésta ejecutamos algo de código y llegamos a un yield. Sin embargo, éste no retorna a AsyncFnc1, sino a la llamada anterior. Esto es así porque AsyncFnc1 lo que quiere es dormir hasta que AsyncFnc2 termine.

También vemos que para reanudar la ejecución es preciso llamar a AsyncFnc2.callback(). Esto debe tenerse muy en cuenta: en este instante es incorrecto llamar a AsyncFnc1.callback(). Por eso es fundamental que cada función asíncrona se encargue de programar su llamada para recuperar el control, y no delegarlo a funciones externas, pues es muy fácil cometer errores.

Cuando AsyncFnc2 llega al final, se cede el control al código de AsyncFnc1, que sigue ejecutándose, y al terminar, se llamaría a la función de callback de finalización (si la hubiera), y seguiría la ejecución justo a continuación de la llamada a AsyncFnc2.callback() (que, en este caso, simplemente seguiría con la ejecución del Main Loop).

Casos de uso de funciones asíncronas

El primer caso de uso de las funciones asíncronas es para llamar a otras funciones asíncronas. Esto, que puede parecer una perogrullada, toma sentido en cuanto descubrimos las funciones asíncronas de Gio. Un ejemplo es el de File.copy_async: este método de la clase File copia un fichero de manera asíncrona. De no existir, tendríamos que crear un hilo y ejecutar en él la función síncrona de copia, porque de no hacerlo así, si el fichero fuese muy grande la interfaz gráfica de nuestro programa se quedaría congelada durante la operación.

Hay dos maneras de llamar a estas funciones asíncronas: la primera es la clásica, incluyendo un callback (bien como puntero a función, bien como función anónima o closure) que se llame cuando termine la operación; la segunda, más cómoda en según que casos, es llamarla mediante yield desde una función asíncrona de Vala. En este segundo caso hay que saltarse el parámetro del callback.

El segundo caso de uso es para llamar a servicios externos mediante DBus. De esta manera podemos aprovechar el tiempo entre el envío de la petición y la llegada de la respuesta. Para ello, basta con añadir async en la definición de la interfaz DBus. Un ejemplo de como definir la interfaz DBus para formatear un disco, mediante el demonio UDisks:

[DBus (name = "org.freedesktop.UDisks.Device")]
interface Device_if : GLib.Object {
	public abstract async void FilesystemCreate(string type, string[] options) throws IOError;
}

Por desgracia, en este caso concreto no podemos aprovechar la funcionalidad de la llamada mediante yield, debido a que existe un timeout en las llamadas a métodos DBus de, aproximadamente, 25 segundos, y se necesita más tiempo para formatear algunos soportes. Se supone que se puede ajustar este timeout mediante un modificador de Vala, pero después de consultar en la lista de correo y de buscar en internet, no he conseguido que funcione. Si alguna alma caritativa nos puede iluminar…

El siguiente caso de uso es cuando se quiere realizar una tarea larga y que consuma CPU, pero no se quieren usar threads o lanzar un proceso paralelo, sino ejecutarlo en los momentos en los que el proceso actual esté inactivo (en el caso de una aplicación gráfica, cuando no hay eventos que procesar en la cola). La solución consiste en crear una función asíncrona que realice la tarea, insertando llamadas a yield de manera regular. Para que la función se despierte cuando la cola está vacía, usamos Idle.add() para llamar al método .callback() de nuestra función asíncrona, así:

public async void AsyncFnc() {

    // Inicialización de variables y demás
    [...]

    // Comienza la tarea
    while(cond==true) {

        // Realizamos una iteración de la tarea
        [....]

        // Avisamos al Main Loop que queremos que nos despierte
        // cuando no haya eventos pendientes
        Idle.add(AsyncFnc.callback);

        // Y devolvemos el control al búcle principal o Main Loop
        yield;
    }

    // La tarea ha finalizado
    // Limpiamos todo
    [...]
}

Por supuesto, podemos utilizar también un temporizador en lugar de Idle, o cualquier otro sistema que decidamos.

Un detalle interesante que se puede ver en este ejemplo es que la llamada a yield puede ir dentro de un bucle while o for sin ningún problema.

Por último, también son útiles para sincronizar threads. El truco en este caso está en almacenar un puntero al método de callback de la función asíncrona que queremos sincronizar, lanzar el thread, y al final de éste, añadir dicho callback a la cola de mensajes. El esquema es éste:

async void AsyncFunc() {

    // Aquí almacenamos el método .callback() de nuestra función asíncrona
    SourceFunc callback = AsyncFunc.callback;

    // Creamos la función para el thread. La hacemos anónima (closure) para que tenga acceso a la
    // variable local callback.
    ThreadFunc<void*> run = () => {
        // Código del thread. Aquí hacemos las operaciones deseadas
        [...]

        // Aquí ya hemos terminado las operaciones, así que
        // añadimos el método .callback() a la cola para que se llame inmediatamente
        Idle.add((owned) callback);
        return null;
    };

    // Lanzamos el thread
    Thread.create<void*>(run, false);

    // Y esperamos a que se llame a nuestra función callback
    // que será cuando termine el thread
    yield;
}

En la documentación oficial se encuentran tres ejemplos de funciones asíncronas, muy útiles para entender aún mejor esta útil característica de Vala. En concreto aparece un ejemplo del método de sincronización de threads, donde se puede ver mucho mejor la sintaxis concreta.

DBus en Vala

martes, agosto 23rd, 2011

Actualizado Estoy trabajando en un nuevo proyecto, escrito íntegramente en Vala, y la verdad es que cuantas más cosas aprendo sobre él, más me gusta.

Lo último que descubrí fue como trabajar con DBus en Vala, y resulta que es extremadamente sencillo y elegante, aunque tuve que investigar bastante hasta encontrar como hacer algunas cosas, pues los ejemplos que vienen en la página tratan de como trabajar con un servidor hecho por uno mismo, cuando yo necesitaba acceder a un servicio del sistema.

Así pues, y sin más dilación, veamos como se puede usar DBus para obtener una lista de los discos duros conectados por USB al ordenador, y como formatearlos.

Lo primero que vemos es que toda esa información la gestiona el demonio UDisk, el cual exporta una serie de funciones y objetos a través de DBus para que cualquier programa pueda acceder a ellos. Si echamos un vistazo a la documentación de la API, vemos que se exporta el método EnumerateDevices, el cual nos devuelve un array de objetos.

¿Y como accedemos a este método del objeto UDisk desde Vala? Pues con este código:

[DBus (name = "org.freedesktop.UDisks")]
interface UDisk_if : GLib.Object {
    public abstract ObjectPath[] EnumerateDevices() throws IOError;
}

UDisk_if udisk = Bus.get_proxy_sync<UDisk_if> (BusType.SYSTEM, "org.freedesktop.UDisks","/org/freedesktop/UDisks");
var retval = udisk.EnumerateDevices();

Aquí vemos que primero definimos una interfaz con todos los métodos a los que vamos a querer acceder (en este caso sólo uno), sacando de la documentación los parámetros y demás, y le añadimos antes una cabecera con el nombre del objeto. Luego, creamos un proxy síncrono, conectándonos a través del bus del sistema (BusType.SYSTEM), al servidor UDisk (org.freedesktop.UDisks) y pedimos acceso al objeto deseado (/org/freedesktop/UDisks). Finalmente, llamamos al método deseado, almacenando el resultado en una nueva variable.

Es importante recalcar que cualquier método que se defina puede emitir, siempre, una excepción de tipo IOError, por lo que siempre se debe añadir al final de la definición la coletilla throws IOError. De no hacerlo se producirá un error de compilación. También comentar que dado que esta función sólo devuelve un parámetro, la he escrito en formato resultado metodo(parametros); sin embargo, también es perfectamente válida la sintaxis void metodo (parametros,…, out resultado). Esta segunda forma es obligatoria cuando un método devuelve más de un resultado. Con esa sintaxis, la interfaz tendría esta forma:

[DBus (name = "org.freedesktop.UDisks")]
interface UDisk_if : GLib.Object {
    public abstract void EnumerateDevices(out ObjectPath[] path) throws IOError;
}

En retval tenemos ahora un array de objetos ObjectPath, cada uno de tipo Device, representando una unidad extraíble. ¿Y ahora, qué? Pues ahora vamos a imprimir el punto de montaje de cada uno de ellos. Si miramos la documentación del objeto Device, vemos que dicha información se almacena como una propiedad, así que definimos nuestra interfaz así:

[DBus (name = "org.freedesktop.UDisks.Device")]
interface Device_if : GLib.Object {
    public abstract string IdLabel { owned get; }
    public abstract string[] DeviceMountPaths { owned get; }

    public abstract void FilesystemUnmount(string[] options) throws IOError;
    public abstract void FilesystemCreate(string type, string[] options) throws IOError;
    public abstract void PartitionModify (string type, string label, string[] options) throws IOError;
    public abstract void FilesystemMount(string type, string[] options, out string mount_path) throws IOError;
}

Ponemos { owned get; } en lugar de {owned get; set; } porque los campos son de sólo lectura. También he añadido los cuatro métodos necesarios para poder formatear una partición, aunque en el ejemplo no los voy a utilizar.

Ahora llega el momento del código que accede a los datos, que es éste:

Device_if device2;
foreach (ObjectPath o in retval) {
    device2 = Bus.get_proxy_sync<Device_if> (BusType.SYSTEM, "org.freedesktop.UDisks",o);
    GLib.stdout.printf("Disco %s montado en:n",device2.IdLabel);
    foreach (string s in device2.DeviceMountPaths) {
        GLib.stdout.printf("    %sn",s);
    }
}

Así, primero recorremos la lista de objetos que obtuvimos con la anterior llamada (en retval), y creamos un proxy síncrono para cada uno de ellos, contra el servidor de UDisk (él es quien nos dio la lista de objetos, por lo que estos tienen que estar en él). Luego imprimimos la etiqueta simplemente leyendo la propiedad del objeto, y por último imprimimos todos los puntos de montaje (una misma partición puede estar montada en varios sitios a la vez).

Actualización: Uno de los problemas que tuve fue que la llamada a FilesystemCreate (que formatea la unidad especificada) tarda varios segundos en ejecutarse, lo que hacía que la GUI se quedase colgada. La primera solución que encontré fue ejecutarla en un thread aparte, y usar espera activa para detectar cuando terminó, pero era muy poco elegante. La solución definitiva consiste en realizar llamadas asíncronas a DBus, explicadas en el tutorial de Vala. La idea consiste en añadir la palabra reservada async en la definición de la función en la clase, con lo que la llamada a DBus será no-bloqueante, y definir un callback anónimo.

[DBus (name = "org.freedesktop.UDisks.Device")]
interface Device_if : GLib.Object {
  [...]
  public abstract async void FilesystemCreate(string type, string[] options) throws IOError;
  [...]
}

  // Llamamos a la función, añadiéndo como último parámetro
  // una función anónima que se ejecutará al terminar la
  // ejecución
  device2.FilesystemCreate.begin(format,options, (obj,res) => {
    try {
      // La llamada a .end recoge los valores devueltos (de haberlos)
      // y dispara cualquier excepción que se haya producido
      device2.FilesystemCreate.end(res);
      return;
    } catch (IOError e) {
      // Captura de excepciones (en este caso, fallo durante el formateo)
    }
  });

Una vez que hemos hecho la llamada, podemos volver al bucle principal de GTK o llamar a la función run() de un cuadro de diálogo, y éste seguirá funcionando en paralelo a la ejecución de nuestra llamada a DBus.

Enlace dinamico

sábado, febrero 19th, 2011

Acabo de lanzar la versión 8.0 de FPT_BT4LXMedia. La primera novedad es la nueva versión de Transmission, la 2.21. La segunda es que, por fin, los ejecutables utilizan enlazado dinámico.

Como recordareis, las versiones anteriores utilizaban enlazado estático porque no conseguía que arrancasen desde el disco, a pesar de utilizar LD_LIBRARY_PATH para apuntar al directorio con las nuevas bibliotecas. Para los que no sepan qué es eso, es una variable de entorno que permite añadir nuevas rutas en donde buscar bibliotecas de enlace dinámico, y éstas tienen prioridad sobre las «normales» del sistema. Así, si tengo un programa enlazado con una versión concreta de libc, diferente de la del sistema, puedo instalarla en otro directorio (por ejemplo, /tmp/libs) y lanzar el programa con:

LD_LIBRARY_PATH=/tmp/libs mi_programa

Por desgracia, esto no funcionaba en mi disco duro. Tras investigar mucho, descubrí que el problema estaba en el enlazador dinámico en tiempo de arranque. Resulta que el núcleo sólo es capaz de arrancar, por sí solo, binarios que estén enlazados estáticamente. Para los dinámicos lo que hace es delegar en /lib/ld-linux.so (o /lib/ld-uclibc en la biblioteca uClib). A pesar de llevar extensión .so, este fichero es realmente un programa (enlazado estáticamente, claro) diseñado para cargar en memoria el programa dinámico deseado, analizar qué bibliotecas necesita, cargarlas en memoria si aún no están, realizar toda la operación de enlazado y, finalmente, lanzar el código.

Por desgracia, la versión del enlazador dinámico de la gentoo que utilizo para desarrollar es más reciente que la de mi disco duro, lo que hacía que recibiese un error cada vez que intentaba ejecutar un programa compilado directamente. Para complicar aún más las cosas, la ruta del enlazador dinámico está especificada en cada ejecutable de manera inamovible, estableciéndose dicho valor durante la compilación. Y por si fuera poco, el enlazador de Busybox no permite su ejecución directa, por lo que tampoco podía hacer un simple /lib/ld-uclibc mi_programa.

Ante todo esto, la única solución que me quedó fue la de copiar el enlazador dinámico del entorno de desarrollo en una carpeta diferente y, al compilar bftpd, transmission y ctransmission, especificar la ruta deseada. Esto se hace en la etapa final de enlazado mediante la opción -Wl,–dynamic-linker=path/al/enlazador. De esta manera bastó con copiar el enlazador y el resto de bibliotecas a /tmp/hdd/root/libs y lanzar los programas con

LD_LIBRARY_PATH=/tmp/hdd/root/libs mi_programa

Con esto espero ahorrar algo de memoria. También haré algunas pruebas para ver si los programas funcionan bien con la versión de la uClib del propio disco duro, en cuyo caso podría ahorrar aún más.

Entre signos anda el juego

miércoles, enero 12th, 2011

Una de las frases preferidas de mi madre es Está el viejo muriendo, y está aprendiendo; y como en la gran mayoría de los refranes, hay una grandísima verdad oculta en ella.

Y es que estos días estoy peleándome con el micro ARM de un TomTom, y me encontraba con que el código que iba perfectamente en mi PC daba un fallo rarísimo en el GPS: al imprimir el carácter ‘/’ no lo hacía bien; pero sólo ese, el resto los renderizaba perfectamente. Después de mucho depurar, descubrí que el fallo era debido, nada menos, que al signo de una variable. Y es que, a pesar de utilizar el mismo compilador (GCC) para ambos casos, resulta que un char a secas en PC es signed, como todo el mundo asume, pero en ARM es unsigned, por lo que un valor que tenía que ser -1 se convertía en 255 (la segunda parte del problema es que luego operaba con ese valor y con otro de 32 bits, con lo que, al añadirle los 24 bits restantes, no expandía el bit de signo). Una vez añadido el modificador correspondiente, todo pasó a funcionar perfectamente.

Así pues, nunca deis por supuesto el signo de una variable. Consejo de amigo.

Desplazando bits

viernes, diciembre 31st, 2010

Hace mucho tiempo, cuando empecé a programar en C, leí una curiosa advertencia sobre los operadores de desplazamiento de bits, los famosos >> y <<. Se afirmaba allí que, al desplazar una variable con ellos, los bits que salían se perdían (lógico, siendo un desplazamiento), pero que los bits que entraban por el lado opuesto tenían un valor indefinido, que podía ser cero o uno según la implementación.

Es cierto que todos los programadores asumen que lo que entra es cero siempre; pero pese a todo, yo, que tiendo a ser demasiado cauto, opté por añadir siempre una máscara AND para poner a cero los bits entrantes y así evitarme sorpresas.

Sin embargo, hace unos días, mientras optimizaba unas rutinas que hacen uso de desplazamientos, decidí investigar más a fondo el tema, porque a fin de cuentas una máscara consume ciclos de reloj, así que si había alguna forma de evitarla siempre sería una mejora. Y lo que descubrí me dejó gratamente sorprendido, porque en realidad el valor de los bits que entran no depende del compilador, sino que está perfectamente definido en el estándar.

Cuando se utiliza el desplazamiento hacia la izquierda (<<), por el lado derecho siempre entran ceros. Ahí nunca hay problemas. Por otro lado, cuando se utiliza el desplazamiento hacia la derecha (>>) en una variable sin signo (por ejemplo, en un unsigned int), por la izquierda también entran siempre ceros.

Sin embargo, cuando utilizamos variables con signo (por ejemplo, short int) en un desplazamiento hacia la derecha, los bits que entren por la izquierda serán una copia del bit de mayor peso del valor original. Así, si teníamos un número negativo (su bit de mayor peso está a uno), al desplazarlo hacia la derecha seguirá siendo negativo, porque los bits entrantes serán unos; en cambio, si era un número positivo (su bit de mayor peso está a cero) los bits entrantes serán cero.

Supongo que la razón de hacerlo así es que, si se utilizan rotaciones para hacer divisiones, se conserva el signo.

Este código ilustra perfectamente lo dicho aquí:

#include <stdio.h>

void desplaza(unsigned int o) {

  signed int s1,s2;
  unsigned int u1,u2;

  s1=s2=u1=u2=o; // asignamos el mismo valor a las cuatro variables

  s1>>=4; // desplazamos cuatro veces cada una de ellas
  s2<<=4;
  u1>>=4;
  u2<<=4;

  printf("Con signo:ntdesplazamiento a la derecha   %08Xn",s1);
  printf("tdesplazamiento a la izquierda %08Xn",s2);
  printf("Sin signo:ntdesplazamiento a la derecha   %08Xn",u1);
  printf("tdesplazamiento a la izquierda %08Xnn",u2);
}

int main() {

  printf("nDesplazamiento de 4 posiciones.n");
  printf("Valor original: 0xA50000A5 (bit de mayor peso a uno).n");
  desplaza(0xA50000A5);
  printf("nValor original: 0x5A00005A (bit de mayor peso a cero).n");
  desplaza(0x5A00005A);

  return 0;
}

La salida es la siguiente, donde se aprecia claramente como el bit superior se replica en los desplazamientos a la derecha de las variables con signo, pero no en el resto de los casos:

Desplazamiento de 4 posiciones.
Valor original: 0xA50000A5 (bit de mayor peso a uno).
Con signo:
  desplazamiento a la derecha   FA50000A
  desplazamiento a la izquierda 50000A50
Sin signo:
  desplazamiento a la derecha   0A50000A
  desplazamiento a la izquierda 50000A50

Valor original: 0x5A00005A (bit de mayor peso a cero).
Con signo:
  desplazamiento a la derecha   05A00005
  desplazamiento a la izquierda A00005A0
Sin signo:
  desplazamiento a la derecha   05A00005
  desplazamiento a la izquierda A00005A0

Trabajando con señales

domingo, abril 11th, 2010

Estos días he seguido trasteando con Bftpd, y me puse a trabajar en resolver un problema bastante molesto: la cantidad de procesos zombie que deja tras de sí.

Para entender lo que pasa, primero hay que explicar que Bftpd lanza un nuevo proceso por cada nuevo usuario que se conecta, en lugar de utilizar un solo proceso para gestionar todas las conexiones. Así, tenemos un proceso padre que se limita a escuchar, y cada vez que llega una petición de conexión crea un proceso hijo para que la gestione; también comprueba cuando un hijo se muere (porque se haya cerrado la conexión).

Y aquí es de donde surge el problema: cada vez que un hijo termina, emite una señal (en concreto SIGCHLD) hacia el padre, el cual, al recibirla, ejecutará una pequeña función o callback y luego seguirá su ejecución normal. En el caso de Bftpd, dicho callback comprueba qué hijo es el que se ha muerto, pide su valor de retorno (para evitar que se quede zombie) y libera una serie de recursos que le reservó. En concreto, el código de dicha función comienza con un:

pid = wait(NULL);
[codigo para liberar los recursos del proceso PID]

Hasta aquí todo parece correcto; por desgracia, después de cada sesión con Bftpd quedaban varios procesos zombie. La razón era, obviamente, que el proceso padre no había leído su valor de retorno, por lo que el sistema operativo no los podía hacer desaparecer; sin embargo, en el callback se lee siempre dicho valor, así que es obvio que algo raro estaba pasando.

Para depurar el código empecé por añadir unos printfs: uno en el punto en que se crean los procesos hijo, mostrando su PID; otro en el punto de finalización de dichos hijos, y otro en la función de callback de SIGCHLD. Ejecuté el servidor, hice unas cuantas operaciones, vi la salida por pantalla, y… ¡Sorpresa! ¡Había menos llamadas a la función de callback que muertes! Si todo funcionase como se esperaba, el número debería ser exactamente el mismo (cada muerte debería emitir una señal SIGCHLD, la cual haría que se ejecutase el callback). Sin embargo, por alguna misteriosa razón, algunos procesos morían sin emitir la señal.

Para aquellos a los que les de igual el por qué, y simplemente necesiten saber el como, daré primero la solución: se trata de leer los PIDs de todos los hijos muertos en cada llamada al callback, en lugar de leer el de uno sólo. En otras palabras, el código del callback para SIGCHLD debe ser:

do {
        pid = waitpid(-1,NULL,WNOHANG);
        if (pid>0) {
            [codigo para liberar los recursos del proceso PID]
        }
} while (pid>0);

Vale, pero ¿por qué ocurre esto?

Para entender lo que ocurre hay que irse un poco a las profundidades del núcleo, porque se debe a un problema de cómo están implementadas las señales.

En Linux, cada proceso tiene un conjunto de bits, y cada uno representa una señal. Cuando un proceso quiere enviar una señal a otro, lo que hace realmente es poner a uno el bit de dicha señal en el proceso receptor.

¿Y cuando comprueba el receptor la llegada de una señal? En cada cambio de contexto: en un sistema operativo multitarea, los procesos se van turnando en el uso de la CPU, de manera que primero la usa unos milisegundos el proceso A, luego otros milisegundos el proceso B, y así sucesivamente hasta que se acaba la lista y se vuelve al proceso A. Cada vez que se cambia de un proceso al siguiente se realiza un cambio de contexto, en el que primero se guarda el estado del proceso actual, se busca quien será el siguiente proceso, se carga su estado y se le cede el control. Pero, y aquí está la cuestión, antes de este último paso se comprueba la máscara de bits de las señales, y si alguna está activa se ejecutará primero el callback correspondiente.

¿Y por qué se pierden entonces señales SIGCHLD? Pues porque dos o más procesos se mueren «a la vez»; esto es, se mueren dentro del mismo intervalo entre dos ejecuciones del proceso padre. Así, lo que ocurre es que cuando se muere el primero, pone a 1 el bit de la señal SIGCHLD del proceso padre; el repartidor de tareas, al ver que ha muerto, libera todo lo que puede y pasa al siguiente proceso; éste muere también, y también pone a 1 el bit de la señal SIGCHLD del proceso padre… pero ese bit ya estaba a uno, por lo que se queda como está. Finalmente, el repartidor decidirá que es el momento de ejecutar de nuevo el proceso padre, pero antes verá que el bit de la señal SIGCHLD está activo, por lo que lo pondrá a cero y  llamará al callback. El resultado: se murieron dos procesos pero sólo se ejecutó una vez la función asociada a la señal, por culpa de que las señales se almacenan con un único bit.

Es por esto que la solución indicada arriba funciona: la señal nos indica que se ha muerto AL MENOS un proceso hijo, así que debemos comprobar todos, y no asumir que cada uno enviará una señal.

Este caso nos demuestra que las señales son un sistema de comunicación bastante frágil, por lo que no se debe abusar de él. Cosas como «contar el número de veces que llega una señal» o similares pueden dar problemas incluso si hay un único emisor, porque si emite varias veces la misma señal antes de que el padre recupere el control de la CPU, contarán como una única señal. Y como muestra para los incrédulos, un pequeño programita que ejemplifica todo esto:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int v;

void senal(int valor) {

 printf("Recibidan");
 v++;
}

int main(int argc, char **argv) {

 int pid1,pid2,pid3,pid4,loop;

 v=0;
 signal(SIGUSR1,senal);
 pid1=getpid();
 pid2=fork();
 if(pid2==0) {
 fork();
 fork();
 printf("hijon");
 kill(pid1,SIGUSR1);
 kill(pid1,SIGUSR1);
 kill(pid1,SIGUSR1);
 kill(pid1,SIGUSR1);
 kill(pid1,SIGUSR1);
 sleep(2);
 } else {
 printf("padren");
 for(loop=0;loop<21;loop++) {
 sleep(1);
 }
 printf("Total: %dn",v);
 }
}

Este código crea un primer hijo, el cual se divide en cuatro hijos en total, y cada uno emite, de golpe, cinco señales SIGUSR1 al padre. Sin embargo, al ejecutarlo veremos que nunca se detectan todas ellas, sino, como mucho, una por proceso hijo (y a veces ni eso).


Utilizamos cookies para garantizar que tenga la mejor experiencia en nuestro sitio web.