Sistemas operativos alternativos interesantes

Minientrada

Algunos sistemas operativos que son software libre o código abierto que sigo, de cara a ver tendencias o a modo de referencia para consultar arquitecturas interesantes o aprender. El requisito para salir en esta lista es:

  • Tener el código fuente abierto de tal forma que se pueda consultar la implementación de piezas concretas del sistema operativo.
  • Estar disponible preferentemente para una de las plataformas de Intel (x86 o x86-64).
  • Estar programado de una forma simple o que no sea una pesadilla de comprender o navegar (lo siento, ReactOS).

Estos son más académicos:

  • xv6: está pensado como sistema operativo académico que se usa en universidades donde se enseñan cosas relacionadas con el desarrollo de sistemas operativos. Tiene código fuente y también un libro en PDF.
  • ULIX: otro sistema operativo de tipo UNIX. Está escrito en programación literaria así que el código fuente está dentro del PDF del libro. Muy ligado a la arquitectura x86, pero bien documentado.
  • HelenOS: un micronúcleo. Me gusta como referencia porque soporta muchas arquitecturas, y porque al ser otro sistema operativo académico, tiene muchos papers con información para aprender.
  • GNU Mach: GNU Hurd es un microkernel, pero GNU Mach es el núcleo del microkernel. En este repositorio es donde está el código dependiente de arquitectura y el entrypoint Multiboot, así que es un buen punto durante las primeras etapas para tener como referencia.

Otros sistemas operativos que son catalogables como homebrew. No creo que sea de muy buena educación copiar o sacar ideas, porque la gracia está en innovar, pero a modo de referencia práctica para poder resolver problemas que en la teoría no se explican bien (como el

Nota: posiblemente lo mejor si quieres una lista viva es que te suscribas al hilo del foro OSDev donde la gente comparte pantallazos de su progreso. Muchos proyectos desaparecen sin que se vuelva a saber nada de ellos, pero otros proyectos nuevos aparecen frecuentemente. El topic OSDev de GitHub también te mantiene al día.

Menciones honoríficas:

  • ToaruOS: el veterano en cuanto a sistemas operativos homebrew, está a punto de alcanzar la versión 2.0. Se ha vuelto mucho más complejo con la inclusión de algunos componentes, pero seguir su desarrollo es interesante.
  • Manganarm: apuesta muy fuerte por un micronúcleo, busca compatibilidad con POSIX y Linux, se acerca a Wayland, soporte para virtio.
  • Essence: estéticamente muy avanzado, innovación en interfaces de usuario (todo el sistema está orientado a pestañas).
  • SerenityOS: obligatorio mencionarlo; fuertes estéticas vintages noventeras. Si te pillas un año sabático y te dedicas exclusivamente a un proyecto de este tipo (cosa que no tengo muy claro si está bien o mal), consigues algo como esto.

Pero también hay otros que sigo como:

  • MollenOS: modular, soporte para múltiples arquitecturas.
  • MOROS: escrito en Rust, con una estructura limpia basada en un monorepo. 64 bits pero BIOS.
  • SOSO: Monolito escrito en C, UNIX-like, simple de comprender y seguir. Pasa la prueba del DOOM.

Recursos para empezar a programar núcleos

Minientrada

Así que quieres programar tu propio núcleo como base para fabricar un sistema operativo que destrone a GNU/Linux, eh. Te lo advierto: apenas vas a pasar tiempo programando. La mayor parte del tiempo se va a ir en consultar información en internet, escribir el pseudocódigo de funciones y diseñar algoritmos.

Estos son los recursos que suelo consultar más, por si te sirven de algo:

  • La wiki de OSDev.org es territorio sagrado. Hay cosas que están un poco anticuadas. Hay cosas que están mal. Hay cosas que están a medio escribir desde hace 15 años. Pero sin embargo, es mejor que nada. Aunque hoy en día está más modernizado, y cuenta cosas como desarrollo para la plataforma ARM, su punto fuerte es desarrollo para la plataforma Intel (x86 y x86_64).
  • Por relación, el foro de OSDev.org. Si te abres una cuenta, el software del foro te mostrará los nuevos hilos para que puedas ver qué se cuenta. Se hacen preguntas y en general es un buen recurso para buscar ayuda a veces. Su hilo vivo de libros recomendados te puede dar ideas para consultar más, y su hilo vivo de más de 200 páginas y 15 años de historia donde puedes enseñar el aspecto de tu proyecto te puede inspirar o matar de envidia.
  • El topic en GitHub. Hay bastante movimiento y puedes encontrar repositorios con otros sistemas en los que están trabajando otras personas, también para ver qué se está moviendo. Hay también algunos tutoriales y guías en algunos repositorios en este tag.
  • El Discord de Low Level Programming contiene discusiones y conversaciones interesantes y hay canales dedicados a esto, además de hablar de programación de bajo nivel. Te vas a inflar a programación de bajo nivel, así que más vale que te guste y te encuentres confortable.

Según el camino que quieras seguir, hay más recursos en esa categoría. Salvo que te apetezca comenzar creando un cargador de arranque de los de toda la vida de 0x7C00 o te interese programar para EFI, lo más probable hoy día es que empieces jugando con programas que puedan ser cargados por algo ya existente como GRUB o Limine.

  • La especificación Multiboot 1 es necesaria tenerla a mano si quieres entender la estructura de datos que GNU GRUB te proporciona cuando arranca tu programa. Es antigua, pero muy portable. Incluso QEMU es capaz de lanzar programas compatibles usando el flag -kernel, sin necesidad de incorporar un cargador de arranque ni generar una ISO.
  • La especificación Multiboot 2, aunque también tiene muchos años, es todavía experimental. No todos los cargadores de arranque (además de GNU GRUB) lo soportan, así que puede ser un poco más complicado de depurar y arrancar. Sin embargo, soporta aspectos novedosos como más arquitecturas de ordenador además de Intel 32 bit.
  • Limine busca ser un cargador de arranque moderno y alternativo a GNU GRUB. Además de valer como cargador de arranque de propósito general (es capaz de cargar Linux), tiene su propio protocolo de arranque llamado stivale, que puede ser una alternativa a crear una imagen para Multiboot.

Aunque estos protocolos de arranque son técnicamente compatibles con cualquier tipo de archivo binario que contenga código máquina, es probable que empieces creando tus primeros ejecutables de tu núcleo usando un compilador que genera binarios que siguen el formato ELF. La especificación de la ABI SystemV contiene un capítulo dedicado a comprender el formato ELF, algo que puede ser necesario para hacer cargadores dinámicos más adelante.

Eso sería todo por ahora. Próximamente, más.

Notas: estructura del formato de archivos TAR

Minientrada

Ayer en Twitch tocó desarrollo de sistemas operativos y empecé a trabajar en la implementación de un driver de sistema de archivos TAR para mi sistema operativo. El objetivo es poder incrustar en la distribución un archivo TAR que contenga un ramdisk y montarlo al arrancar el sistema para poder acceder a más archivos (por ejemplo, recursos gráficos, archivos de texto…)

Este es un sistema parecido al que usan muchos otros sistemas operativos cuando introduces un CD de instalación al encender la máquina. El cargador de arranque del CD carga el ejecutable con el programa principal, pero los recursos, como el entorno LiveCD de Linux, vive en un archivo compacto que se carga en memoria para dar la apariencia de un entorno real.

Elegí el formato TAR porque es un formato muy sencillo de comprender. No aplica compresión, esencialmente cuando creas un archivo TAR estás concatenando el contenido de todos los archivos que quieras incorporar en el archivo. Cada archivo lleva al principio una cabecera que contiene sus metadatos: nombre, tamaño, tipo de archivo, permisos…

Un archivo TAR está dividido en bloques. Un bloque tiene 512 bytes. O sea, los primeros 512 bytes del archivo (bytes 0 a 511) forman el bloque 0. Los siguientes 512 (bytes 512 a 1023) forman el bloque 1. Los siguientes 512 (bloque 1024 a 1535) forman el bloque 2… todos los componentes de un TAR están alineados respecto a un bloque. Por ejemplo, la cabecera de un archivo siempre empieza al principio de un bloque y ocupa todo el bloque. Si sobra sitio después de cada bloque, se rellena con ceros para que la siguiente sección empiece al principio del siguiente bloque.

Entonces, ¿de qué manera está compuesto un archivo TAR? Para cada uno de los archivos que haya dentro, se vuelca un bloque con los metadatos y luego cero, uno o varios bloques con el contenido del archivo. Esto se repite en bucle para cada archivo que haya en el TAR. Un TAR no tiene metadatos generales: haz un hexdump de un TAR (no comprimido) que tengas a mano y verás que empieza directamente con los metadatos del primer archivo. Al final del archivo TAR hay dos bloques en blanco (o sea, 1 KB lleno de ceros).

La estructura de metadatos es binaria y puede ser accedida a través de registros. El manual de usuario de GNU TAR tiene un capítulo dedicado a la especificación del formato también, y ahí se encuentra un ejemplo de cómo sería la estructura de datos para acceder a los metadatos:


struct posix_header
{                              /* byte offset */
  char name[100];               /*   0 */
  char mode[8];                 /* 100 */
  char uid[8];                  /* 108 */
  char gid[8];                  /* 116 */
  char size[12];                /* 124 */
  char mtime[12];               /* 136 */
  char chksum[8];               /* 148 */
  char typeflag;                /* 156 */
  char linkname[100];           /* 157 */
  char magic[6];                /* 257 */
  char version[2];              /* 263 */
  char uname[32];               /* 265 */
  char gname[32];               /* 297 */
  char devmajor[8];             /* 329 */
  char devminor[8];             /* 337 */
  char prefix[155];             /* 345 */
                                /* 500 */
};

Un pequeño detalle es que, para mejorar la compatibilidad con procesadores que sean big endian, se decidió que el formato TAR guardaría los números directamente en ASCII, en vez de codificados como binario. O sea, que el número 1000 no se guarda convertido a binario, porque entonces habría que saber si el sistema es big endian (0x03 0xE8) o little endian (0xE8 0x03). En su lugar, se guarda tal cual en ASCII, codificado como octal («1750», que es 1000 en base 8). Al final de cada cadena de estas, hay un caracter \0 de fin de string. Es posible convertir entre una de estas «cadenas octales» y número int normal.

Después del bloque de metadatos, el siguiente bloque contiene el contenido del archivo. Puede ocupar uno, dos, los bloques que sea. El último bloque del archivo también tiene ceros al final del bloque si sobra espacio, para que el siguiente archivo empiece al principio del siguiente bloque.

El campo typeflag de la estructura de metadatos indica el tipo de archivo: archivo regular, directorio… esto es importante porque en las carpetas se guarda su nombre y sus permisos, pero como son carpetas, el tamaño es 0 y no tiene contenido. Después de un bloque de metadatos de una carpeta, viene otro bloque de metadatos que describe el siguiente archivo que haya en el archivador TAR.

Además de eso, TAR es capaz de representar enlaces simbólicos e incluso archivos de dispositivo, ya que tiene campos para guardar el devmajor y el devminor.

Ya no tengo WhatsApp

Estado

He olvidado tomar pantallazo del mensaje de confirmación, pero lo importante es que he eliminado mi cuenta esta mañana. Por lo menos el proceso de confirmación es sosprendentemente corto para ser un producto de Facebook Meta.

Nunca acepté los nuevos términos de uso entonces por otra parte hubiese sido cuestión de tiempo igualmente que se me hubiese bloqueado la cuenta.

Pero en el fondo la razón por la que lo he hecho no tiene nada que ver con la privacidad. Es simplemente que he concluido que mirar los chats una vez al mes no es útil para nadie. «Ponerme al día» normalmente se acaba traduciendo por marcar como leídos cientos de mensajes viejos de grupos sin prestarle demasiada atención. Tampoco era útil para mis amigos, ya que ya fui informado varias veces de que al no leer los grupos, estoy bloqueando los checks azules que salen cuando todo el mundo lee un mensaje.

Prepending `bundle exec` to your command may solve this

Minientrada

¿Sabes cuando usas la doctrina «si funciona, no toques», pero aun así las cosas se rompen por arte de magia de un día para otro?

Bundle tiene este problema a veces, que no he podido depurar pero que no es la primera vez que me ocurre: debido a que pueden coexistir múltiples versiones de una gema en simultáneo, a veces utilizar un shim (o sea, invocar rspec o rails en la consola sin más, sin especificar bundle exec ni usar el binstub) falla, porque no está usando la versión concreta de alguna dependencia.

Después de actualizar unas dependencias en nuestro Gemfile, me empezó a salir este mensaje de error:

/Users/danirod/.rbenv/versions/2.6.7/lib/ruby/gems/2.6.0/gems/bundler-2.2.17/lib/bundler/runtime.rb:302:in `check_for_activated_spec!': You have already activated rspec-support 3.10.3, but your Gemfile requires rspec-support 3.10.2. Prepending `bundle exec` to your command may solve this. (Gem::LoadError)

Se soluciona usando bundle exec rspec en vez de rspec. No es que tenga fobia a escribir bundle exec antes de mis comandos, pero mi sentido común me dice que si día tras día, escribir rspec funciona, si de repente deja de funcionar de un día para otro, el problema no lo tengo yo. Por no ir, no me iba ni el binstub. O sea, bin/rspec también me da el mismo mensaje de error.

A veces cuando me pasan estas cosas hago un pristine, o un cleanup. No piques: es una ruleta rusa. A veces se tira 15 minutos limpiando y reinstalando paquetes para luego seguir fallando al ejecutar. En esta ocasión, he optado por tirar una solución más rápida:

gem uninstall rspec-support
bundle install

Si primero elimino todas las versiones de esta gema que hay en mi ordenador, y luego vuelvo a hacer bundle install, me deberá dejar una única versión instalada: la que pida el Gemfile. Me parece una solución aceptable. Esta es mi workstation, y no uso rbenv con software esencial (si tuviese software instalado desde Homebrew que esté escrito en Ruby, probablemente usaría el Ruby de Homebrew, no rbenv). A lo que quiero llegar es a que no pasa nada si elimino todas las versiones de un paquete y luego reinstalo.

Código sin tests

Cita

El otro día respondí lo siguiente por nuestro Discord en un hilo [1] en el que se debatía si era 100% necesario escribir tests a la hora de escribir un código. Traigo el contenido aquí para no perderlo en el historial y tenerlo a mano.

Yo me voy a tirar a la piscina y voy a decir que sí es posible escribir el código de un proyecto sin tests si lo pruebas a mano según lo estás desarrollando.

Lo que no es posible hacer sin tests es mantenerlo. Porque cuando se hagan cambios en el software ya no va a ser el mismo software que el que has probado a mano cuando lo estabas desarrollando. Tendrías que probar a mano de nuevo todo el programa, y eso puede ser una pérdida de tiempo. ¿Cuándo entra un código en mantenimiento? En cuanto dejas de tener visible ese código después de escribirlo.

Creo que la principal finalidad de los tests desde el punto de vista pragmático, más allá del «virtuosismo» de «soy guay, tengo tests, tengo buen coverage (JA)», es poder automatizar el poder comprobar que el software sigue funcionando bien conforme va pasando el tiempo de una forma automática y no intrusiva. Si hago un cambio de mantenimiento a un módulo de software quiero poder tener mi checklist ya implementada que me cubra todos los casos de uso de ese módulo de software para poder hacer la validación automáticamente y comprobar que no se introducen regresiones, o sea, que no he roto nada inesperado al hacer el cambio. (El software a veces se conecta de formas poco esperadas; cohesión siempre acaba habiendo y las interfaces de un módulo nunca son tan buenas como cabría esperar)

[1] Para que este deeplink funcione hay que haberse unido antes al servidor.

iPad SE

Minientrada

Pasa septiembre, llega octubre, Apple actualiza las specs de algunos de sus modelos, pero el nuevo iPad «a secas» se sigue viendo igual que siempre, con su bezel, su pantalla rectangular, su Touch ID y su Apple Pencil de primera generación.

Empiezo a pensar: ¿y si siguen sin actualizar el aspecto del iPad regular, aparte de para no canibalizar al iPad Air, porque busca ser el equivalente en tablets al iPhone SE? El iPhone SE también sigue teniendo pantalla rectangular y Touch ID, pese a tener el cuerpo de un iPhone 11 Pro.

¿Podría ser simplemente que ambas familias empezasen a converger en algún momento; iPad como un iPad SE, iPad Air como el iPad a secas, igual que el iPhone normal con bezel, iPad Pro como iPhone Pro?

¿Por qué FreeBSD y no (x)BSD?

Minientrada

Por eliminación.

Elegí FreeBSD porque NVIDIA publica drivers para la ABI de FreeBSD. Lo que significa que puedo tener aceleración gráfica y tener mis pantallas con la resolución correcta en vez de usar el driver genérico xorg-vesa. No sirve de nada más porque NVENC no está disponible fuera de Windows y porque no uso FreeBSD para jugar.

Eso es todo. No soy un fanboy de FreeBSD y si mis casos de uso hubiesen sido otros tal vez hubiese ganado otro sistema operativo. Pero ganó este. Los tres sistemas son muy similares entre sí de todos modos.


Otros candidatos:

  • NetBSD: me gusta por su extremada simpleza, hecha para darle una perfecta portabilidad. El lema de NetBSD es “por supuesto que corre NetBSD”, haciendo referencia al hecho de que es posible instalarlo en muchísimas arquitecturas (incluso en Macs de principios de los años 90 o en Play Stations).
  • OpenBSD: lo admiro por su extrema seguridad, pero mis casos de uso con mi ordenador me llevan a admirarlo de lejos. OpenBSD es tan seguro que hace unos años eliminaron el soporte para Bluetooth porque es un protocolo inseguro y su driver no estaba bien mantenido.

Primera dosis

Imagen

Un cartel señala la dirección del punto de vacunación del Zendal
La única foto que he tomado, en parte porque me prometí a mi mismo que no haría la cutrez de tomarme un selfie.

Primera dosis puesta. Ha tocado Pfizer. En tres semanas a regresar, y en cinco semanas esto definitivamente se acaba.

Ha sido un año muy largo.

SQL: Suma acumulativa

Minientrada

Tengo una tabla como la siguiente:

datetime               points
2021-07-05 10:00:00    1
2021-07-05 10:05:00    1
2021-07-05 10:08:00    -1
2021-07-05 10:10:00    1
2021-07-05 10:11:00    -1

Y quiero sacar las sumas acumulativas. Es decir, no quiero sacar simplemente un SUM(points) y que me devuelva 1, sino que quiero ir viendo, para cada fila, la suma parcial de todas las filas que hay hasta llegar a esa desde el principio.

Por lo que veo en Stack Overflow, una forma universal de hacerlo aunque poco eficiente con tablas muy grandes, es utilizar INNER JOINs aplicados sobre la misma tabla que parcialmente vayan agrupando los resultados por fecha. Me hice algo como lo siguiente en mi caso usando un WITH para filtrar sólo aquellos resultados que realmente quiero sumar y así tratar de hacerlo más eficiente.

WITH points AS (
  SELECT datetime, points
    FROM data
   WHERE target_id = 12345
)
SELECT a.datetime, a.points, SUM(b.points)
  FROM points a
       INNER JOIN points b
               ON b.datetime <= a.datetime;