wandres.dev
EL C DEL KERNEL · Freestanding, sin libc

container_of y las macros del kernel

El kernel tiene su propio arsenal de macros que verás por todas partes: container_of (la más ingeniosa), likely/unlikely, ARRAY_SIZE y compañía. Dominarlas es leer el kernel.

⏱ 13 min

El código del kernel está salpicado de macros que, si no conoces, lo vuelven ilegible. No son adorno: resuelven problemas reales de un lenguaje sin genéricos ni clases. La estrella es container_of, uno de los trucos de C más elegantes jamás escritos.

🎯 Al terminar esta lección sabrás
  • container_of: obtener el struct desde un campo.
  • Cómo habilita las estructuras de datos genéricas.
  • likely / unlikely y la predicción de saltos.
  • El resto del arsenal: ARRAY_SIZE, min/max, BUILD_BUG_ON.

container_of: el truco maestro

Problema: tienes un puntero a un campo dentro de un struct, y quieres el puntero al struct completo. En C no hay forma directa… salvo container_of:

struct tarea {
    int id;
    struct list_head lista;    // un campo embebido
    char nombre[32];
};

// tengo un 'struct list_head *ptr' que apunta al campo .lista de una tarea.
// ¿cómo obtengo la 'struct tarea' que lo contiene?
struct tarea *t = container_of(ptr, struct tarea, lista);

Por dentro, container_of calcula el desplazamiento del campo dentro del struct (con offsetof, nivel 11 de C23) y lo resta de la dirección del campo, obteniendo la dirección del struct contenedor.

container_of hace posible las estructuras genéricas del kernel

Aquí está la genialidad que sostiene medio kernel. C no tiene genéricos ni herencia, así que ¿cómo hace el kernel una lista enlazada que sirva para cualquier tipo? La respuesta: en vez de que la lista contenga tus datos, tú embebes un struct list_head dentro de tu struct. La lista enlaza esos campos list_head entre sí, sin saber nada de tu tipo. Y cuando recorres la lista y tienes un list_head, usas container_of para recuperar tu struct completo. Es orientación a objetos sin objetos: composición por embebido + container_of para volver al contenedor. Este patrón —que verás en el nivel 10 con las listas— es cómo el kernel logra colecciones genéricas y type-safe en C puro. Entender container_of es entender la mitad del código del kernel.

likely y unlikely

El kernel sabe qué ramas son raras (errores) y cuáles comunes, y se lo dice al compilador para que optimice la predicción de saltos:

if (unlikely(error))          // "esto casi nunca pasa"
    return -EINVAL;
if (likely(buffer_valido))    // "esto casi siempre pasa"
    procesar(buffer);

En el camino crítico (hot path) del kernel, esta pista mejora el rendimiento colocando el código común de forma que la CPU lo prediga bien. Verás unlikely en casi todas las comprobaciones de error.

El resto del arsenal

ARRAY_SIZE(arr)          // número de elementos (seguro, del kernel)
min(a, b)  max(a, b)     // con chequeo de tipos, no como las macros ingenuas
min_t(int, a, b)         // forzando un tipo
BUILD_BUG_ON(cond)       // falla la COMPILACIÓN si cond es cierta (chequeo estático)
READ_ONCE(x) / WRITE_ONCE(x)  // accesos que el compilador no puede reordenar (nivel 18)
💡
BUILD_BUG_ON: errores atrapados al compilar

BUILD_BUG_ON(sizeof(struct x) != 64) hace que el kernel no compile si esa condición se cumple. El kernel lo usa para garantizar invariantes en tiempo de compilación —tamaños de struct, alineaciones, supuestos de layout— de modo que un cambio que los rompa se detecte al instante, no en ejecución. Es la filosofía de “que el error sea imposible de compilar”, llevada al kernel. Cuando escribas código que dependa de un tamaño o layout, protégelo con BUILD_BUG_ON.

⚔️ Aprende el vocabulario
  1. Busca la definición de container_of en include/linux/container_of.h y entiende cómo usa offsetof.
  2. Encuentra tres usos de container_of en el código del kernel (Elixir).
  3. Localiza unlikely en una comprobación de error de un driver.
  4. Escribe (mentalmente) un BUILD_BUG_ON que garantice que un struct tuyo mide 32 bytes.