Archivo de la categoría: tutoriales

Emergiendo

Actualizado. Hace unos días encontré una página sobre discos duros multimedia basados en procesador MIPS. Aunque no es sobre el mismo modelo que mi disco, sí tiene cosas lo suficientemente parecidas como para que funcione. Una de ellas es la idea de utilizar una Debian para MIPS mediante chroot para poder añadir cosas extra.

Aunque la idea es buena, por cuestiones personales prefería utilizar Gentoo, así que me bajé la stage3 para MIPS, la copié en el disco duro e intenté hacer un chroot. Pero como siempre, las cosas nunca son sencillas: decía que no podía ejecutar bash. Extrañado, copié el ejecutable a mi equipo de sobremesa y lo analicé con GHex, comparándolo con el ejecutable de BusyBox.

El problema resultó ser que los stages de Gentoo para MIPS están en formato big-endian, mientras que mi disco duro trabaja en little-endian (parece ser que los procesadores MIPS, aunque pueden trabajar con ambos formatos, no pueden utilizarlos a la vez, sino que se escoge a nivel hardware durante el arranque). Cuando un sistema trabaja en little-endian se dice que es un MIPSel.

Rebuscando encontré unos stages para MIPSel, pero eran del año 2005. Intenté usarlos y, aunque funcionaban, en cuanto intentaba actualizar el sistema al más reciente, fallaba (todo apuntaba a la versión de python, que era demasiado vieja para ejecutar el último portage). Seguí buscando y encontré que las estaciones de trabajo Cobalt trabajaban en little-endian, así que probé de nuevo con unos stages diseñados para ellas, pero seguía fallando.

Decidí entonces probar con la Debian de la página inicial, pero al hacer un dist-upgrade volvía a fallar.

Ante esto, decidí que sólo tenía dos opciones: o utilizaba sólo cross-compiling, o intentaba generar yo mismo un stage de Gentoo para MIPSel. Como no podía ser de otra manera, tiré por la segunda 🙂 Por supuesto, es la primera vez que lo hago, así que, aunque me ha funcionado, puede ser que haya metido la pata en algo, o que alguna cosa se pueda hacer mejor, así que los comentarios están abiertos para cualquier sugerencia o corrección.

AVISO: a partir de aquí empiezo a tocar en el sistema operativo del disco duro multimedia, lo que significa que estas acciones sólo las deben realizar aquellos que sepan muy bien lo que hacen. Si alguien se carga su disco, será el único responsable.

Avisados estáis.

Lo primero que hice fue instalar una Gentoo para x86 en mi sistema. Pero como no quería formatear mi actual Ubuntu, decidí hacer una instalación virtual. Veamos como:

Para empezar, descargamos las versiones más recientes del stage3 y el portage de Gentoo, y las descomprimimos en un directorio (el portage se descomprime dentro del directorio /usr del stage3), en mi caso llamado gentoo:

mkdir /home/raster/gentoo
sudo tar -xjvf stage3-i686-20090915.tar.bz2 -C /home/raster/gentoo/
sudo tar -xjvf portage-lastest.tar.bz2 -C /home/raster/gentoo/usr/

La razón de utilizar sudo es que, si no, no podrá crear algunos ficheros especiales en el directorio.

A continuación montamos una copia de los sistemas de archivos especiales de Linux (/proc, /dev, /dev/pts y /sys), además de copiar el fichero resolv.conf para poder conectarnos a Internet. Sin ellos no podríamos hacer muchas cosas básicas:

sudo mount -o bind /proc /home/raster/gentoo/proc
sudo mount -o bind /dev /home/raster/gentoo/dev
sudo mount -o bind /dev/pts /home/raster/gentoo/dev/pts
sudo mount -o bind /sys /home/raster/gentoo/sys
sudo cp /etc/resolv.conf /home/raster/gentoo/etc/resolv.conf

Notar el parámetro -o bind en mount: lo que hace es coger un sistema de archivos ya montado y montarlo también en el nuevo directorio, de manera que esté disponible en ambos a la vez.

Por último, hacemos un chroot para entrar en el sistema:

sudo chroot /home/raster/gentoo/ /bin/bash

Ya estamos dentro de nuestra Gentoo, pero antes de empezar tenemos que cargar el entorno:

env-update
source /etc/profile

Con ésto ya podemos empezar a trabajar. Lo primero es actualizar todo el sistema a los últimos paquetes, para lo que hacemos:

emerge --sync
emerge --update --deep world

Y ya podemos empezar a crear nuestro stage. Lo primero es instalar crossdev, un paquete que automatiza la generación de entornos de compilación cruzada (la documentación oficial está aquí y aquí):

emerge portage-utils crossdev

A continuación, editamos el fichero /etc/make.conf y añadimos la línea PORTDIR_OVERLAY=/usr/local/portage (podemos utilizar el editor nano, o vi) y, opcionalmente, la línea MAKEOPTS=»-jX» (siendo X el número de procesadores más 1; yo, como tengo un doble núcleo, uso -j3). Por último, creamos el directorio /usr/local/portage, y ya podemos generar un compilador para MIPSel, linux, y uclibc:

crossdev -v --target mipsel-unknown-linux-uclibc

Las distintas combinaciones de procesadores, núcleos y bibliotecas están en la documentación de crossdev, en una tabla.

Se tirará un buen rato compilando, tras el cual podemos comprobar si todo fue correctamente con:

gcc-config -l

En mi caso devuelve:

[1] i686-pc-linux-gnu-4.3.2 *
[2] mipsel-unknown-linux-uclibc-4.3.4 *

Con este toolchain podremos compilar cosas de manera nativa pra el disco duro. Para probarlo escribimos el típico Hola mundo y lo compilamos con:

mipsel-unknown-linux-uclibc-gcc -o holamundo holamundo.c

lo copiamos al disco duro multimedia y probamos a ejecutarlo. Debería funcionar a la primera.

Una vez que tenemos el toolchain para compilación cruzada, vamos a crear nuestra stage. Para ello tenemos que instalar el paquete crossdev-wrappers, tal y como se explica en su página, e inicializarlo. Por desgracia, por defecto está enmascarado, así que tenemos que desenmascararlo para que podamos instalarlo. Para ello escribimos:

echo "sys-devel/crossdev-wrappers ~* *" >> /etc/portage/package.keywords/i686-unknown-linux-gnu

Asumiendo, por supuesto, que /etc/portage/package.keywords es un directorio. Si es un fichero, entontes sería

echo "sys-devel/crossdev-wrappers ~* *" >> /etc/portage/package.keywords

Ahora ya podemos instalar crossdev-wrappers:

emerge crossdev-wrappers
emerge-wrapper --init

Este paquete nos permite instalar cualquier paquete de Gentoo, pero compilándolo para el sistema para el que tenemos compilador cruzado, e instalando el sistema de archivos en un directorio aparte (por defecto en /usr/arquitectura; o sea, /usr/mipsel-unknown-linux-uclibc en este caso), que podremos comprimir con tar e instalar en el sistema destino. Pero antes de empezar a utilizarlo tenemos que configurarlo adecuadamente. Lo primero es editar el fichero /usr/mipsel-unknown-linux-uclibc/etc/make.conf y ajustarlo a nuestros intereses. En mi caso, por defecto estaba así:

CHOST=mipsel-unknown-linux-uclibc
CBUILD=i686-pc-linux-gnu
ARCH=mips

HOSTCC=i686-pc-linux-gnu-gcc
E_MACHINE=EM_MIPS

ROOT=/usr/${CHOST}/

ACCEPT_KEYWORDS="mips ~mips"

USE="${ARCH} zlib bindist make-symlinks minimal"

#MARCH_TUNE="-march=armv4t -mtune=arm9tdmi"     #arm-softfloat-linux-uclibc
#MARCH_TUNE="-march=armv5t -mtune=xscale"       #armv5teb-softfloat-linux-gnueabi

CFLAGS="-Os -pipe ${MARCH_TUNE} -fomit-frame-pointer -I${ROOT}usr/include/ -I${ROOT}include/"
CXXFLAGS="${CFLAGS}"
LDFLAGS="-L${ROOT}lib -L${ROOT}usr/lib"

FEATURES="-collision-protect sandbox buildpkg noman noinfo nodoc"
# Be sure we dont overwrite pkgs from another repo..
PKGDIR=${ROOT}packages/
PORTAGE_TMPDIR=${ROOT}tmp/

ELIBC="glibc"

PKG_CONFIG_PATH="${ROOT}usr/lib/pkgconfig/"
#PORTDIR_OVERLAY="/usr/portage/local/"

LIBDIR_${ARCH}="lib"
LIBDIR_amd64=lib64
MAKEOPTS=-j2

Hay varios cambios a realizar. Para empezar, hay que añadir MARCH_TUNE=»-march=4kec», de manera que optimicemos para el procesador específico de mi disco duro (esto hay que cambiarlo para el modelo específico del procesador del disco duro que se quiera utilizar). Por otro lado, hay que cambiar MAKEOPTS=-j2 por MAKEOPTS=-j3, porque yo tengo dos núcleos. Al final tenemos:

CHOST=mipsel-unknown-linux-uclibc
CBUILD=i686-pc-linux-gnu
ARCH=mips
MARCH_TUNE="-march=4kec"

HOSTCC=i686-pc-linux-gnu-gcc
E_MACHINE=EM_MIPS

ROOT=/usr/${CHOST}/

ACCEPT_KEYWORDS="mips ~mips"

USE="${ARCH} zlib bindist make-symlinks minimal"

CFLAGS="-Os -pipe ${MARCH_TUNE} -fomit-frame-pointer -I${ROOT}usr/include/ -I${ROOT}include/"
CXXFLAGS="${CFLAGS}"
LDFLAGS="-L${ROOT}lib -L${ROOT}usr/lib"

FEATURES="-collision-protect sandbox buildpkg noman noinfo nodoc"
# Be sure we dont overwrite pkgs from another repo..
PKGDIR=${ROOT}packages/
PORTAGE_TMPDIR=${ROOT}tmp/

ELIBC="glibc"

PKG_CONFIG_PATH="${ROOT}usr/lib/pkgconfig/"
#PORTDIR_OVERLAY="/usr/local/portage"

LIBDIR_${ARCH}="lib"
LIBDIR_amd64=lib64
MAKEOPTS=-j3

También hay que ajustar el perfil deseado. Por defecto nos ha puesto embedded, que es perfecto para nuestro caso; pero si queremos cambiarlo basta con borrar /usr/mipsel-unknown-linux-uclibc/etc/make.profile y enlazarlo al perfil deseado de los disponibles en /usr/portage/profiles.

Y ya sólo queda por compilar todos los paquetes que queramos, utilizando el comando emerge-mipsel-unknown-linux-uclibc. Así, el primero que, probablemente, queramos instalar será system.

emerge-mipsel-unknown-linux-uclibc system

que instalará BusyBox con todas las librerías de uClinux. Una vez hecho esto sólo tenemos que crear los directorios que faltan con:

cd /usr/mipsel-unknown-linux-uclibc
mkdir dev proc root sys

y ya tendremos un sistema de ficheros que podremos probar. Para ello basta con empaquetarlo con tar (no comprimirlo, porque al menos mi disco duro no tiene ni gzip ni bzip2), descomprimirlo en el disco duro, montar proc, dev, dev/pts y sys como al principio de esta entrada, copiar /etc/resolv.conf y hacer un chroot, aunque esta vez con /bin/ash, porque busybox no dispone de bash.

Otra cosa que podemos hacer es añadir en/usr/mipsel-unknown-linux-uclibc/etc/make.conf una línea USE con las opciones que nos interesen de cara a compilar (opciones que se pueden consultar aquí).

Tuneando discos

Esta semana hice varias pruebas del disco duro. Para empezar, probé varios formatos diferentes, y el resultado es que DivX lo soporta perfectamente, en SD, HD y FullHD. Parece que en FullHD (probado con Big Buck Bunny) se atasca una pizca en algún que otro momento, mientras que en HD y SD va todo como la seda.

Sin embargo, probé a cambiar el sistema de archivos por EXT3 en lugar de NTFS y parece que los atascos han desaparecido.

Respecto al tema de que se encendiese él sólo al encender la luz del salón, lo he solucionado cambiando la bombilla por otra de otra marca.

Formatear la partición de EXT3

Las razones por las que he cambiado la partición principal a EXT3 son dos: por una parte me da más confianza ese sistema porque, a fin de cuentas, es nativo de Linux y se conoce perfectamente, mientras que NTFS es un sistema que se conoce por ingeniería inversa; por otro lado, parece que consigo una pizca más de rendimiento (2,2MB/seg frente a 2,0MB/seg de NTFS), lo que deduzco que es lo que elimina los pequeños tirones que se notaban al reproducir DivX en FullHD.

AVISO: a partir de aquí empiezo a tocar en el sistema operativo del disco duro multimedia, lo que significa que estas acciones sólo las deben realizar aquellos que sepan muy bien lo que hacen. Si alguien se carga su disco, será el único responsable.

Avisados estáis.

Para cambiar el sistema de archivos basta con seguir los siguientes pasos:

Si echamos un vistazo a como están montadas las particiones, vemos lo siguiente:

/ # mount
/dev/root on / type squashfs (ro)
none on /dev type devfs (rw)
none on /proc type proc (rw,nodiratime)
devpts on /dev/pts type devpts (rw)
none on /sys type sysfs (rw)
/dev/mtdblock/2 on /usr/local/etc type jffs2 (rw,noatime)
none on /tmp type ramfs (rw)
/dev/rd/0 on /mnt/rd type vfat (rw,nodiratime,fmask=0022,dmask=0022,codepage=cp437,iocharset=iso8859-1)
/dev/ide/host0/bus0/target0/lun0/part2 on /tmp/hdd/root type ext3 (rw)
/dev/ide/host0/bus0/target0/lun0/part3 on /tmp/hdd/volumes/HDD1 type NTFS (rw)

La partición /dev/ide/host0/bus0/target0/lun0/part2 (/dev/hda2) contiene parte del sistema operativo; en concreto los módulos SMB para acceso por red y el cliente bittorrent, mientras que /dev/ide/host0/bus0/target0/lun0/part3 (/dev/hda3) contiene la partición visible desde el exterior.

Antes de nada copiamos a otro lado todos los ficheros que tengamos en el disco (pues se borrará todo lo que contenga), desmontamos la partición, formateamos en EXT3, volvemos a montarla y creamos el directorio BT:

umount /tmp/hdd/volumes/HDD1
mke2fs -j /dev/ide/host0/bus0/target0/lun0/part3
mount /dev/ide/host0/bus0/target0/lun0/part3 /tmp/hdd/volumes/HDD1
mkdir /tmp/hdd/volumes/HDD1/BT

Vemos que, si apagamos el disco y lo volvemos a encender, la partición sigue montada, lo que parece sugerir que el sistema es capaz de montarla sea del tipo que sea. Por desgracia, en realidad esto no es así: cuando le damos al botón de apagado, el disco se hiberna en lugar de apagarse, por lo que, cuando se enciende, no arranca desde el principio sino que vuelve al estado en que estaba cuando se apagó. Sin embargo, si lo apagamos «de verdad» (por ejemplo desenchufándolo de la red eléctrica, o porque se va la luz), entonces, cuando volvamos a encenderlo, veremos que no ha montado la partición en EXT3.

Para solucionarlo vamos a modificar los scripts de arranque para que monte la partición automáticamente si es necesario. Para ello nos vamos a /tmp/hdd/root/scripts y vemos que hay varios ficheros. Nos interesan uno de éstos: o btpd o samba. Estos scripts se lanzan durante el arranque para arrancar los servicios de BitTorrent y Samba. En principio podría parecer que basta con añadir un nuevo script para que lo ejecute, pero no lo es: sólo ejecuta esos scripts durante el arranque. Así pues, lo que haremos será modificar uno de ellos para que, al encender el equipo, monte la partición correspondiente. Para ello editamos, utilizando vi, uno de ellos y añadimos al principio las líneas:

if [ -e "/tmp/hdd/volumes/HDD1/lost+found" ]
then
    echo "Particion EXT3 ya montada"
else
    mount /dev/ide/host0/bus0/target0/lun0/part3 /tmp/hdd/volumes/HDD1
    echo "Montando particion EXT3"
fi

Este código comprobará si existe el directorio /tmp/hdd/volumes/HDD1/lost+found (que siempre existe en cualquier partición EXT3), en cuyo caso la partición estará montada y no hará nada; pero si no existe, entonces es que no está montada, así que procederá a hacerlo.

La próxima entrada espero empezar a intentar programar cosas para él.

Multimediando

Paseando por un centro comercial cualquiera acabé dentro de una cadena de electrodomésticos cualquiera, en la que había un interesante disco duro multimedia de 1 tera con conexión de red y bittorrent integrado: un MemUp LX Series. Por si fuera poco anunciaban que soportaba MKV y H.264 en FullHD, con lo que no me lo pensé y me lo compré. Luego resultó que esos dos formatos sólo los soporta si pones un ordenador a trascodificar para que le envíe los datos masticados por red, pero aún así, el cliente bittorrent integrado tenía buena pinta. Además, con suerte usaría un Linux embebido (empotrado siempre me sonó mal 🙂 ), con lo que incluso era posible que pudiese hacerle modificaciones. Y aunque no fuese así, al menos podría ver mis series favoritas en la tele sin tener que andar de un lado para otro con un DVD regrabable.

La primera prueba, reproducir un DivX, fue excelente: buena calidad de imagen y sonido, buenos menús (incluso tiene una previsualización y todo)… buena pinta. El acceso por red, por su parte, lo considero simplemente aceptable: es por SMB (red de windows), lo que no me convence mucho porque, aunque una vez que has entrado funciona bien, a veces da algún problemilla, así que añadir un servidor FTP sería una buena idea.

Por desgracia, en cuanto intenté usar el cliente BitTorrent desde la (espartana) interfaz web, empezaron los problemas: la descarga no arrancaba, e incluso el aparato se colgaba en según qué momentos (!). Un poco mosca, decidí dejar de lado el cliente BT de momento e investigar por otro lado a ver qué descubría.

AVISO: a partir de aquí empiezo a tocar en el sistema operativo del disco duro multimedia, lo que significa que estas acciones sólo las deben realizar aquellos que sepan muy bien lo que hacen. Si alguien se carga su disco, será el único responsable.

Avisados estáis.

Comencé con un escaneo de puertos, y apareció nada menos que un servicio de telnet. La cosa se ponía interesante. Me conecté, puse root como usuario, y p’adentro, sin clave ni nada (así da gusto). Lo primero que hice fue echar un vistazo a /proc/version, /proc/cpuinfo y /proc/meminfo, que devolvieron lo siguiente (he marcado en rojo las partes interesantes):

Trying 192.168.1.103...
Connected to 192.168.1.103.
Escape character is '^]'.
Venus login: root
warning: cannot change to home directory

BusyBox v1.1.3 (2008.12.15-01:58+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

/ # cat /proc/version
Linux version 2.6.12.6-VENUS (root@localhost.localdomain) (gcc version 3.4.4 mipssde-6.03.01-20051114) #13 Mon Mar 9 14:40:01 CST 2009

/ # cat /proc/cpuinfo
system type             : Realtek Venus
processor               : 0
cpu model               : MIPS 4KEc V6.4
BogoMIPS                : 199.06
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 16
extra interrupt vector  : yes
hardware watchpoint     : yes
ASEs implemented        :
VCED exceptions         : not available
VCEI exceptions         : not available

/ # cat /proc/meminfo
MemTotal:        56984 kB
MemFree:          9460 kB
Buffers:           760 kB
Cached:           6436 kB
SwapCached:          0 kB
Active:           6812 kB
Inactive:         2976 kB
HighTotal:           0 kB
HighFree:            0 kB
LowTotal:        56984 kB
LowFree:          9460 kB
SwapTotal:          32 kB
SwapFree:           32 kB
Dirty:               0 kB
Writeback:           0 kB
Mapped:           7208 kB
Slab:             3476 kB
CommitLimit:     28524 kB
Committed_AS:     7848 kB
PageTables:        368 kB
VmallocTotal:  1048560 kB
VmallocUsed:       696 kB
VmallocChunk:  1047804 kB

¡Sí, un Linux con entorno BusyBox! Eso simplificaba mucho las cosas. Por otra parte, un procesador MIPS es buena cosa también. Habría sido mejor un ARM, sin duda, pero éste también está bastante bien soportado. Sin embargo, lo que no me cuadraba era la memoria: ¿sólo 57MB de RAM y sin Swap? Un poco raro. De hecho, esa falta de memoria podría tener algo que ver con los cuelgues.

Al principio sospeché que el sistema podría ser un uClinux, el cual, al ser para sistemas sin MMU, no puede utilizar Swap. Pero por otro lado, hasta donde yo sabía, todos los micros MIPS llevan gestor de memoria, por lo que no tenía mucho sentido.

Decidí echar un vistazo al sistema de archivos para ver si podría probar a crear una partición de Swap que aliviase el escaso margen de memoria, y entonces apareció la segunda sorpresa:

/ # fdisk /dev/hda

The number of cylinders for this disk is set to 121601.
There is nothing wrong with that, but this is larger than 1024,
and could in certain setups cause problems with:
1) software that runs at boot time (e.g., old versions of LILO)
2) booting and partitioning software from other OSs
 (e.g., DOS FDISK, OS/2 FDISK)

Command (m for help): p

Disk /dev/hda: 1000.2 GB, 1000204886016 bytes
255 heads, 63 sectors/track, 121601 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

 Device Boot    Start       End    Blocks   Id  System
/dev/hda1               2          21      160650   82  Linux swap
/dev/hda2              22          41      160650   83  Linux
/dev/hda3              42      121581   976270050    7  HPFS/NTFS

¡Ya había una partición de Swap definida! ¿Pero por qué no la usaba? Probé a activarla a mano con swapon /dev/hda1 pero sólo conseguí un mensaje de error. Sospechando que algo raro pasaba con ella, decidí hacer un volcado a mi ordenador de sobremesa y examinarla con Ghex2 (un editor hexadecimal) para ver qué contenía, no fuese a ser una partición de datos con un tipo incorrecto. La sorpresa fue mayúscula cuando descubrí ¡que estaba completamente a cero! La partición de Swap no estaba formateada, y por eso no la reconocía el sistema como tal.

Mi primer impulso fue formatearla manualmente, pero entonces recordé que en el menú Ajustes -> Sistema había una opción para formatear el disco. Decidí probarla a ver si también hacía algo con ésta, y así fue: después de ordenar el formateo desde el menú del disco multimedia, no sólo se borró la partición principal, sino que formateó también la partición de Swap y la montó correctamente. Sin embargo, la interfaz web había desaparecido, y en su lugar sólo aparecía una página de demostración de CGIs.

¿Qué había pasado? Nada más terminar de formatear, el disco pregunta si queremos instalar BT/SAMBA, y nos da cinco escasos segundos para pulsar OK si queremos hacerlo. Yo me despisté, el temporizador venció, y no instaló nada. Así que decidí formatearlo de nuevo y esta vez estuve atento para pulsar el OK en el momento adecuado. Una vez hecho todo quedó perfecto: la interfaz Web apareció y el cliente BitTorrent funcionó perfectamente, sin cuelgues.

Así pues, si teneis un disco de este tipo y os da problemas, probad a formatearlo desde el menú principal. Con un poco de suerte puede ser que se solucione (pero no os olvidéis de copiar antes los archivos que tuvieseis en el disco duro multimedia, ya que se perderán por completo durante el formateo).

Dado que la entrada me está saliendo algo larga pararé aquí y seguiré con mis descubrimientos sobre el sistema de archivos, el entorno de desarrollo y otras hierbas en otra entrada.

Vala de plata

Hace un par de meses descubrí la existencia de Vala, un nuevo lenguaje de programación similar a C# pero con una característica muy interesante: en lugar de compilar directamente a código máquina o a código de una máquina virtual, compila a código C, usando GObject para implementar el sistema de clases y objetos. La gran ventaja de  esto es que permite crear bibliotecas y clases GObject sin necesidad de lidiar con la complejidad de este sistema, lo que, para los fans de Gnome nos resulta especialmente atractivo. También permite trabajar en un lenguaje moderno, lo suficientemente parecido a C# como para que casi no se note la diferencia, pero sin perder ni un ápice de rendimiento (lo siento, sigo sin creerme que un JIT consiga siempre mejores rendimientos, fuera de casos patológicos  y pruebas de laboratorio).

Otro detalle que me ha parecido fascinante es que simplifica mucho la gestión de memoria. Al contrario de lo que podría parecer, no utiliza un recolector de basura como el de Java, sino conteo de referencias. Lo interesante es que todo el proceso es muy transparente, pues el propio lenguaje se encarga de utilizarlo casi siempre. Así, un trozo de código como éste:

using GLib;

class cPrueba:Object {

}

int main() {

    cPrueba miObjeto,miObjeto2;

    miObjeto=new cPrueba();
    stdout.printf("Referencias: %udn",miObjeto.ref_count);
    miObjeto2=miObjeto;
    stdout.printf("Referencias: %ud %udn",miObjeto.ref_count,miObjeto2.ref_count);
    miObjeto=new cPrueba();
    stdout.printf("Referencias: %ud %udn",miObjeto.ref_count,miObjeto2.ref_count);
    return 0;
}

cuando se ejecuta, devuelve la siguiente salida por pantalla:

Referencias: 1d
Referencias: 2d 2d
Referencias: 1d 1d

Vemos que, cuando se hace la primera asignación, se crea un nuevo objeto de tipo cPrueba y se asigna a miObjeto, con lo que su contador de referencias es 1. Luego hacemos la segunda asignación. Como miObjeto y miObjeto2 son, realmente, punteros al mismo objeto (y apuntan, por tanto, a la misma zona de memoria), lo único que hace Vala es incrementar en uno el contador de referencias; por eso los contadores de referencias de miObjeto y miObjeto2 valen 2 en la segunda línea de la salida: porque en realidad son el mismo objeto, pero está referenciado por dos punteros.

Cuando, finalmente, asignamos un nuevo objeto a miObjeto, primero Vala libera el objeto al que apunta, lo que en la práctica consiste en decrementar su contador de referencias. Si en ese momento éste pasase a valer cero, entonces ese objeto quedaría huérfano (ningún puntero apunta a él), por lo que Vala procedería a destruirlo automáticamente, liberando su memoria; sin embargo, en este ejemplo eso no ocurre porque miObjeto2 está apuntando al objeto viejo, así que lo único que ocurre es que su contador de referencias pasa a valer 1. Por su parte, se crea un nuevo objeto de tipo cPrueba, apuntado por miObjeto, cuyo contador de referencias también vale 1, como vemos en la tercera línea de la salida.

Si observamos el código fuente generado (usando valac -C prueba.vala) su función MAIN queda como:

gint _main (void) {
    cPrueba* miObjeto;
    cPrueba* miObjeto2;
    cPrueba* _tmp0;
    cPrueba* _tmp2;
    cPrueba* _tmp1;
    cPrueba* _tmp3;
    gint _tmp4;
    miObjeto = NULL;
    miObjeto2 = NULL;
    _tmp0 = NULL;
    miObjeto = (_tmp0 = cprueba_new (), (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), _tmp0);
    fprintf (stdout, "Referencias: %udn", ((GObject*) miObjeto)->ref_count);
    _tmp2 = NULL;
    _tmp1 = NULL;
    miObjeto2 = (_tmp2 = (_tmp1 = miObjeto, (_tmp1 == NULL) ? NULL : g_object_ref (_tmp1)), (miObjeto2 == NULL) ? NULL : (miObjeto2 = (g_object_unref (miObjeto2), NULL)), _tmp2);
    fprintf (stdout, "Referencias: %ud %udn", ((GObject*) miObjeto)->ref_count, ((GObject*) miObjeto2)->ref_count);
    _tmp3 = NULL;
    miObjeto = (_tmp3 = cprueba_new (), (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), _tmp3);
    fprintf (stdout, "Referencias: %ud %udn", ((GObject*) miObjeto)->ref_count, ((GObject*) miObjeto2)->ref_count);
    return (_tmp4 = 0, (miObjeto == NULL) ? NULL : (miObjeto = (g_object_unref (miObjeto), NULL)), (miObjeto2 == NULL) ? NULL : (miObjeto2 = (g_object_unref (miObjeto2), NULL)), _tmp4);
}

(He omitido toda la parte de la definición GObject de la clase ejemplo porque no aporta nada). Vemos en las distintas líneas como cada vez que se hace una asignación de un objeto a una variable, primero se procede a liberar lo que hubiese en dicha variable utilizando una llamada a g_object_unref, para asegurarse de que el contador de referencias decrece correctamente. Luego llama a g_object_ref con la variable que se copia para incrementar su contador de referencias. Como vemos, este proceso automático nos simplifica la vida lo suficiente como para que nos podamos olvidar de la gestión automática de la memoria…

O casi, porque existe, al menos, un caso en el que no se cumple todo ésto: las listas. Sin embargo, tras buscar y rebuscar, encontré que es un bug en Vala, así que ya está reportado (http://bugzilla.gnome.org/show_bug.cgi?id=586577) y debería estar corregido en breve, asi que no añadais una llamada a unref a mano, sino simplemente esperad a que lo corrijan y recompilad para eliminar el memory leak.

Existe, de todas formas, una opción alternativa, que es utilizar LibGee y su ArrayList. Este tipo de listas no tiene el bug que comento, por lo que, si se utiliza, no hay riesgo de memory leaks.

Pese a todo, es importante señalar un par de detalles sobre las listas de Glib en Vala: si vemos la documentacion, hay varias formas de declararlas, en concreto:

  • var milista = List<contenido_de_lista>();
  • List<contenido_de_lista> milista = List<contenido_de_lista>();
  • List milista = List<contenido_de_lista>();

Siendo contenido_de_lista una clase de elementos que contendra la lista. Asi, si quiero hacer una lista de strings la definiria como List<string>.  La cuestion es que, en base a las pruebas que he hecho, solo se realiza gestion de memoria si la lista se define como en la primera o la segunda opcion, porque solo ahi el compilador podra estar seguro de que el contenido es una clase derivada de Object y tiene, por tanto, contador de referencias. Si definimos la lista como en el tercer caso no se realizara gestion de memoria, por lo que podriamos incluso encontrarnos con que metemos en una lista global un elemento definido localmente y que, al terminar la funcion en donde se definio dicho elemento y volver a la funcion principal, dicho elemento es liberado, consiguiendo un hermoso core dump en cuanto intentemos acceder a dicho dato en la lista. Por tanto, mi consejo es utilizar siempre listas bien definidas, salvo que se sepa perfectamente lo que se esta haciendo.

Ubuntu y el Asus F5GL

Actualizado el fichero de prioridades. Finalmente conseguí hacer que el Asus F5GL de mi amigo funcione como es debido. Para ello, ha «bastado» con instalar la versión 9.04 (la actual estable) y añadir el núcleo de la versión 9.10, con el correspondiente sistema de pinning para que se actualice pero sin tocar nada del resto del sistema. Para los que tengan el mismo problema, explicaré cual es el proceso:

Seguir leyendo Ubuntu y el Asus F5GL

Llega GtkBuilder

Actualizado. Acabo de terminar una nueva versión de DeVeDe; sin embargo no os abalanceis como locos a por ella porque todavía no está disponible. La razón es que uno de los (pocos) cambios que hice fue migrar el código a GtkBuilder; el problema es que varias de las funcionalidades que aporta se han añadido en la nueva GTK 2.16, la cual viene en Ubuntu 9.04, la cual no sale oficialmente hasta mañana. Eso significa que si saco ahora esta nueva versión, nadie podrá usarla a menos que (como yo) instale una versión Release Candidate de Ubuntu. Por esa razón no la sacaré hasta el sábado (por aquello de dar tiempo a la gente a que migre de manera calmada y ordenada).

Sin embargo, aprovecho para escribir una breve introducción a GtkBuilder (breve porque tampoco hay mucho que explicar, es muy sencilla de usar).

¿Qué es GtkBuilder?

GtkBuilder es una biblioteca que llevará a cabo las mismas funciones que actualmente realiza LibGlade: a partir de una serie de ficheros XML creados con Glade u otro editor, generará las ventanas de una aplicación, incluyendo todos sus widgets internos.

¿Pero si va a hacer las mismas funciones, por qué sacar una nueva biblioteca en lugar de seguir con la vieja?

Por una razón muy simple: LibGlade no forma parte de GTK, sino que sigue un desarrollo completamente independiente. Eso la limita en algunos aspectos (por ejemplo, velocidad a la hora de soportar nuevos widgets y propiedades). Por otro lado, no tenía sentido que una biblioteca tan fundamental fuese un elemento externo. Por último, aunque LibGlade siempre funcionó muy bien y está bien implementada, había algunas cosas que se podían mejorar.

Por todo ésto los desarrolladores de GTK decidieron crear una biblioteca integrada en GTK y que fuese lo más parecida posible a LibGlade, con el objetivo de reemplazarla: GtkBuilder había nacido.

Trabajando con GtkBuilder

La forma de trabajar con GtkBuilder es muy similar a LibGlade. La principal diferencia es que en GtkBuilder no es posible generar sólo una parte del árbol (aunque se están planteando el soportar esta opción). Esto significa que es más eficiente poner cada ventana en un fichero XML independiente.

Si ya tienes un fichero .glade puedes convertirlo fácilmente al nuevo formato usando el script gtk-builder-convert. Como primer parámetro recibe el fichero .glade a convertir; como segundo parámetro, el fichero de salida en formato GtkBuilder; por último, opcionalmente se le puede pasar un parámetro -r seguido del identificador de un widget (usualmente una ventana), de manera que sólo convertirá dicho widget y todos sus hijos. Esta última opción permite separar una antigua interfaz Glade con todas las ventanas en un único fichero, en varios ficheros GtkBuilder, cada uno con una sola ventana.

Un ejemplo: gtk-builder-convert viejo_proyecto.glade ventana_ppal.ui -r main_win creará un fichero ventana_ppal.ui que contendrá la ventana main_win y todos sus elementos, del proyecto viejo_proyecto.glade.

Por cierto, la extensión de los nuevos archivos es .ui. Es un detalle que me costó encontrar.

La primera gran diferencia con Glade es que no hace falta importar ningún módulo, pues ya está dentro de GTK.

Trabajar con GtkBuilder es tan sencillo como:

class MiClase:
    __init__(self):
        builder=gtk.Builder()
        builder.set_translation_domain("mi_aplicacion")
        builder.add_from_file("path/to/file.ui")
        builder.connect_signals(self)
        ventana=builder.get_object("nombre_ventana")

En la primera línea creamos un objeto de tipo GtkBuilder; en la segunda especificamos cual es el nombre de nuestra aplicación de cara a usar GETTEXT para las locales; a continuación le indicamos qué fichero .ui contiene la definición de la interfaz que queremos generar. Estas tres líneas reemplazan a la única línea que utilizábamos con LibGlade.

En la siguiente línea le indicamos que conecte las señales con los métodos de nuestra clase; es igual que el método signal_autoconnect de LibGlade.

Por último, pedimos una referencia a un objeto concreto; es igual que el viejo método get_widget de LibGlade.

Trabajando con Glade

Las nuevas versiones de Glade-3 no sólo soportan el nuevo formato, sino que incluso trabajan por defecto en él. En general se sigue trabajando exactamente igual que antes; la única diferencia es que es recomendable que cada ventana vaya en un fichero (y, por tanto, en un proyecto) diferente.

Un detalle muy interesante es la comprobación del soporte de versiones de GTK en el nuevo Glade: si tenemos abierta una interfaz y escogemos Editar->Preferencias, nos saldrá el siguiente cuadro de diálogo:

glade

En la parte inferior vemos la opción Versiones de los toolkit necesarias. Dicha opción nos permite comprobar si en esta interfaz estamos usando algun widget que no esté soportado por alguna versión antigua de GTK o de GtkBuilder. Esto es precisamente lo que me ocurrió con esta versión de DeVeDe: el nuevo constructor de menús sólo está soportado a partir de GTK 2.16, que viene a partir de Ubuntu 9.04.

En otra entrada explicaré como se trabaja con las GtkComboBox y sus listas asociadas, pues GtkBuilder permite generar todo desde Glade, sin necesidad de picar apenas código.

Al rico teclado de piña para el niño y la niña

Actualizado. Si ya tuve una entrada sobre ratones, ahora le toca el turno al teclado. Y es que el de mi flamante Asus EEE PC tiene un detalle algo molesto. Véase la siguiente fotografía:

Resulta que la tecla Mayúsculas derecha está completamente a la derecha y es corta (en lugar de larga, como en los teclados normales), y la tecla Cursor arriba está justo a la izquierda. El resultado es que, cuando se teclea al tacto, es inevitable pulsar con el meñique el cursor cuando se quiere poner una mayúscula.

La solución a este problema es tan sencilla como intercambiar el funcionamiento de dichas teclas, lo cual es muy sencillo gracias a XModMap. Para hacerlo no tenemos más que usar el siguiente script:

#!/bin/bash

xmodmap -e "remove Shift = Shift_R"
xmodmap -e "keycode 62 = Up"
xmodmap -e "keycode 111 = Shift_R"
xmodmap -e "add Shift = Shift_R"
xset -r 111
xset r 62

Los cuatro comandos de XModMap se encargan de remapear los códigos de las teclas con las nuevas funciones deseadas. Luego usamos xset, primero para eliminar la autorrepetición de la nueva tecla Mayúsculas derecha, y luego para añadirla a la nueva tecla Cursor arriba. Es precisamente el hecho de  que la autorrepetición no se pueda controlar desde XModMap lo que obliga a utilizar un script en lugar de un fichero .Xmodmap, como cuando cambiamos los botones del ratón.

Grabamos el script en un lugar cómodo, le damos permisos de ejecución, y ¡voila! Problema resuelto.

O quizás no, porque cada vez que arrancamos el ordenador tendríamos que ejecutar a mano este script para intercambiar las teclas. Lo interesante sería, sin embargo, que se lanzase él solo cada vez que entrásemos en nuestra cuenta. Por fortuna es muy sencillo de hacer en Gnome (en KDE habrá otra manera igual de fácil, pero como no lo uso, no conozco los pasos. ACTUALIZACIÓN: según comenta CoskiBukowsky en los comentarios, se puede copiar el script en ~/.kde/Autostart): basta con ir a Sistema -> Preferencias -> Sesiones y pulsar el botón Añadir, para añadir un nuevo script a ejecutar durante el arranque. Nos saldrá una ventana como ésta, en la que sólo debemos poner la ruta a nuestro script (que, en mi caso, se llama teclas.sh):

¡Y ahora sí, todo listo!

La nueva Ubuntu y mi viejo ratón

(Actualizado 2. Ver al final del artículo) Ayer actualicé mi sistema a la nueva Ubuntu 8.10 y me encontré con el primer problema: mi ratón empezó a funcionar correctamente.

Para explicarlo un poco mejor: mi ratón tiene un total de siete botones: los dos de siempre, el central en la rueda, y dos extra en el pulgar. La rueda, además, se puede mover a derecha e izquierda: dos botones más. Hasta ahora Ubuntu (o más concretamente, las X-Windows) no reconocían los botones extra, y los interpretaba como si fuesen el botón central, lo que para mí era muy cómodo. En efecto, si uso el botón central de la rueda para pegar texto o abrir un enlace de Firefox en una nueva pestaña, es raro que no se me mueva algo y acabe pinchando donde no quiero, problema que resuelvo usando el botón del pulgar.

Por desgracia la nueva versión utiliza HAL para detectar y gestionar el ratón, y éste sí reconoce los nuevos botones, con lo que el botón del pulgar ahora me lleva a la página anterior cada vez que lo pulso.

Por suerte el nuevo sistema también permite reconfigurar el sistema con más comodidad, y lo que es más interesante, sin necesidad de reiniciar las X, sino simplemente desenchufando y volviendo a enchufar el ratón. La manera es mediante unos ficheros XML (con extensión .fdi ) almacenados en /etc/hal/fdi/policy. Estos ficheros permiten modificar y personalizar completamente el funcionamiento de cualquier dispositivo, aunque la documentación es algo confusa.

Para los impacientes, la solución es tan simple como crear un fichero cualquiera en ese directorio (por ejemplo, /etc/hal/fdi/policy/mouse.fdi ) que contenga las siguientes lineas:

<device>
    <match key="info.capabilities" contains="input.mouse">
        <merge key="input.x11_options.ButtonMapping" type="string">1 2 3 4 5 6 7 2 2</merge>
    </match>
</device>

Para los que quieran personalizarlo más, la asignación de eventos es:

  1. Botón izquierdo
  2. Botón central
  3. Botón derecho
  4. Rueda arriba
  5. Rueda abajo
  6. Rueda izquierda
  7. Rueda derecha
  8. Página anterior (en un navegador)
  9. Página siguiente (en un navegador)

En el fichero anterior lo que hago es especificar que los eventos 8 y 9 deben responder como un evento 2, mientras que los demás permanecen inalterados.

Actualización: modifiqué el fichero FDI para que filtre los dispositivos, quedándose sólo con los ratones (tag match).

Actualización 2: Por desgracia la solución no sirve porque sólo funciona cuando primero se cargan las X y luego el módulo del ratón (por ejemplo, al desenchufarlo y volverlo a enchufar con las X ya cargadas). Cuando se arranca el ordenador desde cero, como se carga primero el módulo USB y luego las X, no hace nada.

La solución, al final, es más sencilla:

  • Se crea un fichero .Xmodmap en el directorio del usuario, que contenga la línea pointer = 1 8 3 4 5 6 7 2
  • Se sale de las X y se vuelve a entrar
  • Preguntará si se quiere utilizar un fichero Xmodmap, y mostrará la lista de los que ha encontrado (estará el que acabamos de crear)
  • Lo seleccionamos y lo añadimos a la lista de activos, y nos aseguramos de marcar la opción «No volver a preguntar».

Y con esto sí se resolverá el problema. El único defecto es que no se pueden repetir eventos, por lo que tuve que, simplemente, invertir los eventos 2 y 8, en lugar de asignar el 2 a todos los que me interesaba. Pero menos da una piedra…

Núcleo duro

En la última versión de DeVeDe añadí por fin soporte para multithreading en Mencoder. Por desgracia no es algo tan simple como decirle «Usa threads», sino que hay que decirle cuantos queremos usar.

Alguno dirá: «¡Pues tantos como núcleos tengamos en nuestro ordenador, por supuesto!»

Y efectivamente, esa es la respuesta. El problema viene cuando queremos saber cuantos núcleos tenemos en nuestro ordenador. La primera solución que consideramos es leer /proc/cpuinfo y contar el número de veces que aparece la palabra processor. Por desgracia la cosa no es tan sencilla por culpa de un infame invento de Intel: el HyperThreading. Esta tecnología lo que hace es simular dos núcleos en procesadores de un único núcleo.

Los procesadores actuales no siempre ejecutan las instrucciones en el mismo orden en que están escritas en la memoria, sino que pueden enviar antes algunas que están después, siempre y cuando no dependan de los resultados de ninguna instrucción anterior. Esto permite aprovechar mejor tiempos muertos en la ejecución de instrucciones (por ejemplo, el tiempo que tiene que esperar una instrucción de carga a que llegue un dato desde la memoria). Pues bien, la mayor independencia de instrucciones se da entre procesos diferentes: es posible entremezclar las instrucciones de dos procesos cualesquiera sin que haya riesgo de dependencia, precisamente porque son completamente independientes (valga la redundancia). Los ingenieros de Intel lo entendieron rápidamente y se les ocurrió que podrían llenar huecos en la ejecución de un proceso con instrucciones de otro, aprovechando así al máximo las distintas unidades funcionales del procesador. Desde fuera el procesador parecería tener dos núcleos, y así lo verían los sistemas operativos, cuando en realidad sólo habría uno. Esto permitía incluso no tener que modificar nada en el software.

Sobre el papel la idea parecía muy prometedora, y de hecho Intel afirmaba que se conseguían aumentos de rendimiento de hasta el 30%. Pero en la práctica no sólo era raro acercarse a dicha cifra, sino que había veces que las aplicaciones iban incluso más lentas. La razón no residía en el hyperthreading en sí, sino en su interacción con el Replay System de los procesadores Pentium 4 (que son los únicos que, hoy por hoy, incorporan HT). En X-bit labs explican muy bien en qué consiste, así que no lo repetiré aquí porque quedaría muy largo y peor explicado.

¿Y qué ocurre en el caso particular de Mencoder? En general la gente no se pone de acuerdo. Algunos aseguran que no hay ninguna mejora, pero que tampoco empeora, mientras que otros afirman que va peor si se utilizan multiples hilos. Ante la duda decidí tomar la opción conservadora, lo que implica calcular con precisión el número real de núcleos. Por desgracia no he encontrado una manera «oficial» de saber esa cantidad, sino que hay que deducirla de los datos que ofrece /proc/cpuinfo. Para entender bien la nomenclatura me referiré como procesador a cada uno de los chips que hay en un ordenador, como núcleo real a cada uno de los núcleos físicos de un procesador, y como núcleo virtual al número de núcleos que cree ver un sistema operativo. Así, en un sistema con dos procesadores, en el que cada uno hay dos núcleos, tendremos un total de cuatro núcleos reales. Si encima todos tienen HyperThreading, tendremos un sistema con ocho núcleos virtuales.

Estos son los cuatro parámetros importantes en un sistema con múltiples núcleos y/o procesadores:

  • Physical id: identificador único de cada procesador (chip).
  • Core id: identificador único de cada núcleo real dentro de un mismo procesador.
  • Cpu cores: número de núcleos reales en este procesador.
  • Siblings: número de núcleos virtuales en este procesador.

A la vista de estas definiciones podría parecer que basta con contar el número de combinaciones diferentes de Physical id y Core id para saber cuantos núcleos tenemos. Eso hice en la versión 3.8 de DeVeDe y el batacazo no tardó en llegar: un usuario con un procesador AMD Phenom (cuatro núcleos) se quejaba de que sólo usaba un núcleo.

Tras recibir una copia de su /proc/cpuinfo la sorpresa fue mayúscula: ¡las cuatro entradas processor tenían exactamente el mismo Physical id y Core id, y el número de cores en cada una era uno en lugar de cuatro!

Era necesaria una nueva aproximación al problema, así que recopilé las descripciones de todas las máquinas que pude, lo que complicó aún más la cosa porque los valores bailaban y diferían mucho de lo que sería lógico: en sistemas con dos núcleos Cpu cores vale 2, pero en el sistema con cuatro núcleos sólo vale 1. Lo mismo ocurría con el valor de Siblings. Probé varias aproximaciones, cada cual más complicada, hasta que encontré la solución: cada entrada en cpuinfo se corresponde con un núcleo virtual, así que basta con calcular a qué porcentaje de un núcleo real se corresponde. Ese valor lo obtenemos con el cociente entre Cpu cores y Siblings.

En efecto, en mi procesador Athlon X2 y en un Intel Core 2 Duo, Cpu cores y Siblings valen ambos 2 en cada entrada processor, con lo que 2/2=1. En el procesador Phenom de cuatro núcleos ambos valen 1 en cada entrada, con lo que 1/1=1. Pero en un procesador con HyperThreading Siblings vale 2, mientras que Cpu cores vale 1, con lo que 1/2=0,5. Cada entrada processor cuenta como medio núcleo en la máquina con HyperThreading, mientras que en el resto de sistemas cuenta como un núcleo.
¿Y qué pasa en una máquina con un sólo procesador de único núcleo? Pues que no aparece ninguna de esas dos entradas, por lo que hay que asumir que Cpu cores y Siblings valen 1 salvo que aparezcan en la descripción del procesador.

Así pues, basta con ir sumando el cociente entre Siblings y Cpu cores para cada una de las entradas para, al final, obtener el número de núcleos reales del sistema. Este es el sistema que usaré en DeVeDe 3.9, que saldrá dentro de unas semanas.

Selección multiple

En Trabajando con GtkTreeView en Python explicaba como utilizar el complejo pero versatil widget GtkTreeView. Sin embargo, faltaba un detalle importante: ¿como usar selección múltiple?

Lo primero es poner el selector del GtkTreeView en modo de selección múltiple:

treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)

Ahora ya podremos marcar más de una fila simultáneamente usando las teclas Mayúsculas y Control.

Leer los elementos marcados es tan sencillo como obtener primero el iterador y el arbol:

tree,iter = self.z88tree.get_selection().get_selected_rows()

Si el iterador es None, no hay ninguna fila seleccionada, pero si lo está, no tenemos más que recorrer los elementos del iterador para acceder a las filas. Con ellas, podemos usar el método get_value del arbol para obtener el valor de cada una.

En este ejemplo almacenamos en la lista ret el valor de la columna cero de cada fila seleccionada:

ret = []
for item in iter:
    ret.append(tree.get_value(tree.get_iter(item),0))

Fácil, fácil, para toda la familia.