El C del kernel: otro lenguaje
El kernel se escribe en C, pero no el C que conoces: GNU C freestanding, sin libc, sin coma flotante libre, con su propio universo de APIs. Las reglas que lo cambian todo.
El kernel está escrito en C, así que sabes el lenguaje. Pero programar el kernel es como mudarte a un país donde hablan tu idioma con reglas distintas: no hay librería estándar, ciertas cosas están prohibidas, y hay un universo entero de funciones propias. Este nivel te aclimata.
- Por qué el C del kernel es freestanding.
- No hay libc: printk en vez de printf.
- Las restricciones (coma flotante, stack pequeño).
- El universo de APIs propias del kernel.
Freestanding: sin suelo debajo
¿Recuerdas el nivel 23 de C23, freestanding? El kernel es ese entorno. No hay sistema operativo debajo del kernel — el kernel es el sistema operativo. Por tanto:
- No hay libc. Nada de
stdio.h,stdlib.h,string.hde userspace. Si quieres imprimir, reservar memoria o copiar cadenas, usas las funciones del kernel. - El kernel se compila
-ffreestandingcon su propia colección de headers eninclude/linux/.
// userspace → kernel
printf("hola\n"); → printk(KERN_INFO "hola\n");
malloc(size); → kmalloc(size, GFP_KERNEL);
free(p); → kfree(p);
strcpy / memcpy → strscpy / memcpy (versiones del kernel)
Esto sorprende a todo el que llega: el kernel tiene su propia versión de casi todo lo que la libc te daba — sus funciones de cadenas, su asignador de memoria, sus estructuras de datos, sus macros de utilidad. ¿Por qué? Porque la libc de userspace hace syscalls al kernel para funcionar, y el kernel no puede hacerse syscalls a sí mismo. Así que se lo implementa todo internamente, adaptado a sus necesidades (contextos sin dormir, sin coma flotante, escalable a miles de núcleos). Aprender el kernel es, en buena parte, aprender ese universo paralelo de APIs: printk, kmalloc, list_head, spinlock_t. La sintaxis de C la sabes; el vocabulario es nuevo. Los headers de include/linux/ son tu nuevo diccionario.
Las restricciones
El kernel impone límites que en userspace no existen:
Sin coma flotante (casi)
El kernel normalmente no salva los registros FPU en cada cambio de contexto, así que usar float/double está prohibido salvo en secciones especiales. Trabajas con enteros.
Stack diminuto
El stack del kernel es pequeño (unos pocos KB), no los megabytes de userspace. Nada de arrays enormes en el stack ni recursión profunda: reserva en el heap con kmalloc.
Un fallo lo cuelga todo
No hay red de seguridad. Un puntero nulo dereferenciado es un oops o un panic que puede colgar la máquina, no un segfault de un proceso.
Concurrencia por defecto
Tu código puede ejecutarse en varios núcleos a la vez y ser interrumpido en cualquier momento. La sincronización no es opcional (niveles 15-18).
GNU C, no C estándar puro
El kernel usa GNU C: C con extensiones de GCC (y compatibles con Clang). Verás construcciones que no son C estándar:
// expresiones de sentencia, typeof, atributos... extensiones GNU muy usadas
#define min(a, b) ({ typeof(a) _a = (a); typeof(b) _b = (b); _a < _b ? _a : _b; })
// atributos de sección, alineación, likely/unlikely para el predictor de saltos
if (unlikely(error)) { /* … */ }
El kernel no usa C23 (necesita compilar con toolchains muy variados y estables), pero con GNU C y su tooling logra lo mismo que verías en C moderno: genéricos con typeof y macros, chequeos estáticos con sparse (su analizador propio), sanitizers adaptados como KASAN (nivel 27), y una disciplina de estilo férrea. No echarás de menos constexpr o nullptr: el kernel tiene equivalentes propios (NULL, constantes, macros) forjados en décadas de experiencia. Es C llevado a su extremo profesional.
- Explora
include/linux/en el árbol de fuentes: abrestring.h,slab.h,printk.hy ve las APIs del kernel. - Anota las equivalencias: printf→printk, malloc→kmalloc, free→kfree.
- Investiga por qué no puedes usar
doubleen el kernel (pista: registros FPU y context switch). - Busca
likely()/unlikely()en el código y entiende qué le dicen al compilador.