Ir al contenido

Módulos del Kernel Linux que explican cómo Podman funciona

13 de julio de 2026 por
Módulos del Kernel Linux que explican cómo Podman funciona
Sergio Méndez

Hola, lectores, esta vez les quiero mostrar cómo construir y ejecutar tres módulos de kernel de Linux que ilustran conceptos fundamentales de Sistemas Operativos: mensajes de depuración del kernel, dispositivos de caracteres, y la relación entre la información a nivel de kernel y los procesos/contenedores en espacio de usuario. Tal como en mi publicación de gRPC Hello World, vamos a usar un playground de Ubuntu desechable en Killercoda como entorno, y el repositorio kernel-modules como base de código fuente. Vamos a usar Podman como nuestro motor de contenedores para ejecutar los contenedores que sostienen las cargas de trabajo de module3.

Esta publicación se va a enfocar en:

  • Preparar un entorno para compilar módulos de kernel de Linux.
  • Instalar Podman en Ubuntu.
  • Construir y cargar 3 módulos de kernel diferentes (module1, module2, module3).
  • Entender qué hace cada módulo y cómo se relaciona con los procesos y contenedores que corren en la misma máquina.

Qué vas a aprender

  • Cómo instalar los headers del kernel y las dependencias de compilación para módulos de kernel.
  • Cómo instalar Podman en Ubuntu.
  • Cómo compilar un módulo de kernel con make y cargarlo/descargarlo con insmod/rmmod, y leer mensajes de depuración del kernel con dmesg.
  • Cómo interactuar con un dispositivo de caracteres creado por un módulo de kernel.
  • Cómo se relaciona la información de procesos a nivel de kernel con podman ps y los comandos de procesos del sistema operativo como ps.

Entorno

Vamos a trabajar dentro del playground de Ubuntu en Killercoda. Esto nos da una VM de Ubuntu desechable con acceso root, que es exactamente lo que necesitamos para construir y cargar módulos de kernel (algo que normalmente no se puede hacer dentro de un contenedor regular, ya que los módulos de kernel corren en el espacio de kernel del host).

El código fuente de los tres módulos vive en el repositorio de GitHub kernel-modules. Cada módulo tiene su propia carpeta (module1, module2, module3), y el repositorio incluye un script ins_dep.sh que instala las dependencias de compilación necesarias (cosas como build-essential, linux-headers-$(uname -r), y make). Dentro de cada carpeta de módulo:

  • make all compila el módulo y produce un archivo .ko (kernel object).
  • make clean elimina los artefactos de compilación.

Enunciado

Construir y ejecutar los 3 módulos de kernel, mostrando o explicando lo que se solicita para cada uno.

Cómo instalar los headers del kernel y las dependencias de compilación para módulos de kernel

1. Clonar el repositorio dentro del entorno de Ubuntu en Killercoda:

git clone https://github.com/sergioarmgpl/kernel-modules.git
cd kernel-modules

2. Instalar las dependencias necesarias para compilar módulos de kernel (headers del kernel, compilador, make, etc.):

chmod +x ins_dep.sh
./ins_dep.sh

Este script se encarga de instalar los headers del kernel de Linux que coinciden con el kernel en ejecución (linux-headers-$(uname -r)) junto con las herramientas básicas de compilación, lo cual es necesario porque los módulos de kernel deben compilarse contra la versión exacta del kernel que estás usando.

Cómo instalar Podman en Ubuntu

Dado que el playground de Ubuntu en Killercoda no viene con Podman preinstalado, lo primero que debemos hacer es instalarlo desde los repositorios oficiales de Ubuntu:

sudo apt-get update
sudo apt-get install -y podman

Verificar la instalación:

podman --version

Deberías ver una salida similar a:

podman version 4.9.3

Podman no requiere un daemon: no necesita un servicio en segundo plano de larga duración corriendo como root para administrar contenedores. En cambio, lanza cada contenedor como un proceso hijo regular de la propia CLI, lo cual encaja muy bien con lo que vamos a observar más adelante con module3, cuando inspeccionemos la tabla de procesos del kernel.

Cómo compilar un módulo de kernel con make y cargarlo/descargarlo con insmod/rmmod, y leer mensajes de depuración del kernel con dmesg (Módulo 1)

module1 es el clásico "Hello World" de la programación de módulos de kernel en Linux. Su único trabajo es registrar funciones init y exit, e imprimir un mensaje en el buffer circular del kernel (accesible con dmesg) cuando el módulo se carga y se descarga, usando macros como printk(KERN_INFO ...).

1. Compilar el módulo:

cd module1
make all

2. Cargar el módulo en el kernel en ejecución:

sudo insmod module1.ko

3. Mostrar el mensaje de depuración impreso por el módulo usando el buffer circular del kernel:

dmesg | tail -n 10

Deberías ver una entrada similar a:

[ 1234.567890] Module 1: Hello, Kernel! Module loaded successfully.

4. Una vez que hayas terminado, descarga el módulo y limpia la compilación:

sudo rmmod module1
make clean

dmesg te muestra tanto el mensaje de carga (impreso dentro de la función init del módulo) como, después de rmmod, el mensaje de descarga (impreso dentro de la función exit). Esta es la prueba más básica de que un pedazo de tu propio código ahora está corriendo como parte del kernel de Linux.

Cómo interactuar con un dispositivo de caracteres creado por un módulo de kernel (Módulo 2)

¿Qué hace el módulo? module2 va un paso más allá que module1: en lugar de solo imprimir un mensaje, registra un dispositivo de caracteres en el kernel. Un dispositivo de caracteres es una de las formas en que el kernel expone funcionalidad al espacio de usuario como si fuera un archivo regular, implementando funciones de callback como open, read, write y release. Cuando el módulo se carga, crea un nodo de dispositivo (por ejemplo /dev/module2 o similar, dependiendo del número mayor/menor que registre), y cada vez que un proceso en espacio de usuario interactúa con ese archivo, se ejecuta el callback correspondiente dentro del módulo.

1. Compilar y cargar el módulo:

cd ../module2
make all
sudo insmod module2.ko

2. Verificar que el dispositivo fue creado (ya sea automáticamente bajo /dev o creando el nodo manualmente con mknod, dependiendo de cómo se registre el módulo), y revisar el número mayor asignado:

dmesg | tail -n 10
cat /proc/devices | grep module2
ls -l /dev/module2

3. Escribir en el dispositivo creado por el módulo:

echo "Hello from user space" | sudo tee /dev/module2

También puedes leer de vuelta desde el dispositivo para ver cómo responde el módulo a los datos que recibió:

sudo cat /dev/module2

4. Explicando los mensajes de kernel del módulo 2:

Cada vez que cargas el módulo, escribes en el dispositivo, o lees de él, module2 imprime mensajes de rastreo a través de printk, los cuales puedes inspeccionar con:

dmesg | tail -n 20

Estos mensajes típicamente muestran:

  • Un mensaje cuando el dispositivo se abre, confirmando que un proceso obtuvo un descriptor de archivo hacia /dev/module2.
  • Un mensaje cuando se escriben datos, usualmente incluyendo cuántos bytes se recibieron y, en algunas implementaciones, repitiendo el contenido que se envió.
  • Un mensaje cuando el dispositivo se lee, mostrando cuántos bytes se copiaron de vuelta al espacio de usuario.
  • Un mensaje cuando el dispositivo se libera (se cierra).

Esto ilustra una de las ideas fundamentales de un Sistema Operativo: los procesos en espacio de usuario nunca tocan el hardware o la memoria del kernel directamente, sino que pasan por llamadas al sistema (open, read, write, close), y el módulo de kernel es la pieza de código que decide qué sucede del otro lado de esa llamada al sistema.

5. Limpieza:

sudo rmmod module2
make clean

Cómo se relaciona la información de procesos a nivel de kernel con podman ps y los comandos de procesos del sistema operativo como ps (Módulo 3)

1. Antes de cargar el módulo, vamos a crear algunos contenedores para tener cargas de trabajo reales corriendo en el sistema. Crear un contenedor de nginx, uno de redis, y uno de mongo usando Podman:

podman run -d --name web nginx
podman run -d --name cache redis
podman run -d --name db mongo

2. Listar los contenedores usando Podman:

podman ps

Deberías ver los tres contenedores (web, cache, db) con sus IDs de contenedor, nombres de imagen, y estado.

3. Listar las mismas cargas de trabajo desde el punto de vista del Sistema Operativo, usando comandos de procesos estándar. Dado que cada contenedor es, en última instancia, uno (o más) procesos de Linux corriendo en namespaces y cgroups aislados, puedes encontrarlos con:

ps -ef | grep -E "nginx|redis|mongo"
pstree -p | grep -E "nginx|redis|mongod"
top

Esto demuestra que los contenedores no son "magia": nginx, redis-server y mongod aparecen como PIDs regulares en el host, de la misma forma que cualquier otro proceso de Linux. Esto es especialmente visible con Podman, ya que no requiere un daemon y ejecuta cada contenedor como un proceso hijo del propio árbol de procesos de conmon/podman, en lugar de ocultarlo detrás de un servicio en segundo plano independiente.

4. Ahora compilar y cargar module3:

cd ../module3
make all
sudo insmod module3.ko

5. Inspeccionar la información que el módulo carga en el log del kernel:

dmesg | tail -n 30

Explicando qué carga el módulo 3 y cómo se relaciona con los comandos anteriores:

module3 recorre la lista de tareas interna del kernel (la misma estructura de datos interna del kernel que respalda comandos como ps y top) e imprime información sobre los procesos actualmente en ejecución: PID, nombre del proceso (comm), y a menudo el PID del padre o el estado. En otras palabras, hace a nivel de kernel exactamente lo que ps -ef hace a nivel de espacio de usuario, excepto que lee los datos directamente de las estructuras del kernel (como task_struct, recorridas con funciones auxiliares como for_each_process()) en lugar de pasar por /proc.

Por eso, la salida de dmesg después de cargar module3 incluirá entradas para nginx, redis-server, mongod, sus padres conmon, y otros procesos del sistema: todos son entradas regulares en la tabla de procesos del kernel, la misma lista de la que podman ps depende indirectamente (Podman solo añade un mapeo entre los IDs/nombres de contenedor y los PIDs y namespaces que el kernel ya está rastreando, sin necesitar un daemon central para hacerlo).

Juntando todo:

  • podman ps te muestra la abstracción de contenedores: nombres, imágenes, puertos.
  • ps / pstree / top te muestran la abstracción de procesos del sistema operativo: PIDs, padres, uso de CPU/memoria.
  • module3 te muestra las estructuras de datos del kernel que hacen posibles ambas vistas anteriores, en primer lugar.

6. Limpiar todo:

sudo rmmod module3
make clean
podman rm -f web cache db

Conclusión sobre módulos de kernel, virtualización, y procesos

Estos tres módulos se construyen conceptualmente uno sobre otro. module1 demuestra que tu propio código puede correr dentro del kernel y registrar mensajes. module2 muestra cómo el kernel expone una interfaz al espacio de usuario a través de un archivo de dispositivo, usando el mismo modelo open/read/write que sigue todo driver en Linux. module3 cierra el ciclo mostrando que los motores de contenedores como Podman están construidos sobre las mismas primitivas que el kernel ya expone para procesos, namespaces y cgroups: no existe un "mundo de contenedores" separado, todo son procesos de Linux, hasta el final — y el diseño sin daemon de Podman hace eso especialmente visible, ya que cada contenedor que ejecutas se mantiene rastreable como un árbol de procesos ordinario.

¡Gracias por leer! Por último, nos vemos en mi próxima publicación.

Referencias

Ejemplo de Hello world con gRPC y Go