Linux es un intérprete: el OS que se llama a sí mismo sin romper el stack

Share

El kernel de Linux tiene un secreto que muy poca gente conoce: técnicamente es un intérprete. No en el sentido metafórico de “interpreta tus necesidades”, sino en el mismo sentido en que Python interpreta scripts o Bash interpreta comandos. Y si lo llevas hasta las últimas consecuencias, puedes construir un sistema operativo que se llama a sí mismo infinitamente, sin romper el stack, con una técnica que los programadores funcionales reconocerán de inmediato: la optimización de llamadas de cola (tail-call optimization).

Esto no es un experimento académico. Es real, funciona, y entenderlo te abre una ventana a cómo funciona realmente la ejecución de programas en Linux — desde el shebang hasta el bootloader.

¿Qué es un initrd y por qué importa?

Cuando Linux arranca, antes de montar el disco real, carga en memoria un sistema de archivos temporal llamado initrd (init RAM disk) o initramfs. Es básicamente un archivo comprimido (generalmente en formato cpio) que contiene los binarios mínimos necesarios para preparar el entorno y luego pasar el control al sistema raíz real.

Aprende IA con nosotros

Únete gratis a mi comunidad en Skool, donde compartimos noticias, tutoriales y recursos para seguir aprendiendo juntos.

👥 Únete gratis 🚀

Pero piénsalo desde otro ángulo: el kernel toma un archivo (el initrd), lo lee, y ejecuta lo que hay dentro. Eso tiene un nombre: interpretar.

El kernel de Linux, en este sentido, es un intérprete cuyo lenguaje de entrada es un initramfs.

Capas de intérpretes: del script al metal

Cuando ejecutas ./myscript.sh, el kernel lee el shebang al inicio del archivo (#!/bin/sh) y lo pasa a /bin/sh. El shell interpreta las instrucciones. Hasta ahí, obvio.

Pero /bin/sh es un binario ELF — formato estándar de ejecutables en Linux. ¿Quién interpreta ese ELF? Hay una pista en el propio archivo:

$ file /bin/sh
ELF 64-bit LSB pie executable, interpreter /lib/ld-linux-x86-64.so.2

Ahí está: ld-linux-x86-64.so.2, el enlazador dinámico (dynamic linker). Cuando el kernel ejecuta un binario ELF con dependencias dinámicas, en realidad ejecuta el enlazador, que carga las bibliotecas compartidas (.so) y luego sí lanza el programa. El ELF también es un lenguaje interpretado.

¿Y quién interpreta al enlazador? Ahí viene el truco: ld.so está enlazado de forma estática (static-pie), así que el kernel puede ejecutarlo directamente sin delegar. Base case. Sin recursión infinita.

La pila de intérpretes queda así:

  1. Kernel Linux → interpreta initrd
  2. Shell → interpreta scripts con shebang
  3. ld.so → interpreta ELF dinámicos
  4. Kernel directamente → ejecuta ELF estáticos

binfmt_misc: añade tus propios intérpretes al kernel

El módulo del kernel binfmt_misc (disponible desde Linux 2.1.43) permite registrar nuevos intérpretes para formatos de archivo arbitrarios. Si instalas Mono, de repente puedes ejecutar archivos .exe de Windows. Si tienes QEMU configurado, puedes ejecutar binarios ARM en tu x86. El kernel simplemente delega al intérprete que hayas registrado.

El registro se hace escribiendo en /proc/sys/fs/binfmt_misc/register una cadena que describe el magic number del formato y qué programa usarlo. Por ejemplo, para que los archivos cpio (que empiezan con el magic 070701) sean ejecutables:

echo ':cpio:M::\x30\x37\x30\x37\x30\x31::/bin/cpio-interpreter:' \
  > /proc/sys/fs/binfmt_misc/register

Y /bin/cpio-interpreter puede ser cualquier cosa — incluyendo un script que lance QEMU con ese cpio como ramdisk, o que use kexec para reemplazar el kernel en caliente.

El OS recursivo: kexec como tail-call

kexec es una herramienta que permite reemplazar el kernel en ejecución por otro, sin pasar por el firmware ni el bootloader. Es como un exec() para el kernel entero: descarta el proceso actual y lo reemplaza.

Un ingeniero publicó esta semana un experimento: un initrd cuyo script /init hace exactamente esto:

  1. Monta /proc
  2. Empaqueta todo el sistema de archivos en memoria como un nuevo cpio (/r)
  3. Usa kexec para cargar el kernel original + el nuevo cpio
  4. Ejecuta kexec → el sistema se “reinicia” en el nuevo OS

El nuevo OS es idéntico al anterior. El proceso se repite indefinidamente.

¿Por qué no explota el stack? Porque kexec no apila kernels. Reemplaza el kernel actual por uno nuevo en una zona diferente de memoria, y abandona el anterior. Es una optimización de llamada de cola (tail-call optimization): el stack frame nuevo reemplaza al anterior, en lugar de acumularse sobre él. Sin límite de recursión. Sin memoria que se agote por el patrón de ejecución.

El resultado es un initrd que es, a la vez, un quine del intérprete Linux: un programa que, cuando el kernel lo ejecuta, produce una copia exacta de sí mismo.

Por qué importa

Más allá del experimento mental, esto tiene implicaciones prácticas. kexec se usa en producción para actualizar kernels sin downtime completo — es exactamente cómo AWS, Google y otras nubes actualizan sus kernels sin apagar instancias. El mecanismo de “reemplazar el intérprete sin reiniciar el hardware” es real y ampliamente usado.

binfmt_misc, por su parte, es la razón por la que los contenedores multi-arquitectura funcionan en tu laptop: registras QEMU como intérprete de binarios ARM64, y docker run --platform linux/arm64 simplemente funciona.

Y la pila de intérpretes — kernel → ld.so → shell → tu programa — explica por qué los problemas de seguridad en cualquiera de esas capas son críticos. Un fallo en el enlazador dinámico compromete todos los programas dinámicamente enlazados del sistema. No por nada Ubuntu 26.04 reemplazó el sudo por una versión en Rust (sudo-rs) — es uno de los programas más privilegiados de esa cadena de ejecución.

Lo que este experimento demuestra, en el fondo, es que la distinción entre “OS” y “programa” es más borrosa de lo que parece. Un initrd es un programa. El kernel es su intérprete. Y si registras el kernel como intérprete de initrds, tienes un sistema de cómputo recursivo donde el lenguaje de más alto nivel es un sistema operativo completo.

Para quien trabaja en sistemas embebidos, contenedores, o infraestructura crítica, entender este modelo no es trivia: es la arquitectura conceptual que explica por qué cualquier componente del stack de init tiene el poder que tiene.


Fuentes

Leer más

Otras noticias