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.
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.
container_of: obtener el struct desde un campo.- Cómo habilita las estructuras de datos genéricas.
likely/unlikelyy 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.
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(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.
- Busca la definición de
container_ofeninclude/linux/container_of.hy entiende cómo usaoffsetof. - Encuentra tres usos de
container_ofen el código del kernel (Elixir). - Localiza
unlikelyen una comprobación de error de un driver. - Escribe (mentalmente) un
BUILD_BUG_ONque garantice que un struct tuyo mide 32 bytes.