Errores: ERR_PTR y el patrón goto
Cómo el kernel maneja los errores sin excepciones: códigos negativos, la técnica ERR_PTR para devolver errores en punteros, y el patrón goto para limpieza ordenada.
El kernel no tiene excepciones ni destructores. El manejo de errores es explícito, disciplinado y omnipresente, porque un error mal gestionado aquí deja recursos colgados en un sistema vivo. Dominar sus patrones —ERR_PTR y el goto de limpieza— es escribir código de kernel correcto.
- Devolver errores con enteros negativos.
- ERR_PTR, PTR_ERR e IS_ERR.
- El patrón goto para limpieza.
- Por qué la simetría importa.
Errores como enteros negativos
La convención universal del kernel: una función devuelve 0 en éxito y un -errno (negativo) en fallo:
int hacer_algo(struct dispositivo *dev)
{
if (!dev)
return -EINVAL; /* argumento inválido */
if (dev->ocupado)
return -EBUSY; /* recurso ocupado */
return 0; /* éxito */
}
El que llama comprueba: if (ret) return ret; propaga el error hacia arriba.
ERR_PTR: errores en punteros
¿Y si la función devuelve un puntero? No puede devolver “-EINVAL” en un puntero… salvo con un truco: ERR_PTR codifica un código de error como un puntero inválido (en el rango alto reservado), y IS_ERR/PTR_ERR lo decodifican:
struct objeto *crear_objeto(int tam)
{
if (tam <= 0)
return ERR_PTR(-EINVAL); /* devuelve un "puntero de error" */
struct objeto *o = kmalloc(tam, GFP_KERNEL);
if (!o)
return ERR_PTR(-ENOMEM);
return o;
}
/* al usarla: */
struct objeto *o = crear_objeto(64);
if (IS_ERR(o))
return PTR_ERR(o); /* extrae el -errno del puntero */
El patrón goto para limpieza
Sin destructores, ¿cómo liberas varios recursos cuando algo falla a mitad? Con el goto de limpieza (nivel 6 de C23, elevado a arte en el kernel):
int inicializar(void)
{
int ret;
buf = kmalloc(1024, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = registrar_dispositivo();
if (ret)
goto liberar_buf; /* limpia lo ya hecho */
ret = crear_sysfs();
if (ret)
goto desregistrar;
return 0; /* éxito: no se libera nada */
desregistrar:
desregistrar_dispositivo();
liberar_buf:
kfree(buf);
return ret; /* devuelve el error original */
}
Este patrón es de los más importantes y bonitos del kernel. Cada recurso que adquieres tiene una etiqueta de limpieza, y las etiquetas se ordenan de forma que un goto cae en cascada, deshaciendo en orden inverso exactamente lo adquirido hasta el punto del fallo — ni más ni menos. Fíjate en la elegancia: el código de liberación aparece una sola vez (no duplicado en cada rama de error), y saltar a la etiqueta correcta libera justo lo que hace falta. Sin este patrón, tendrías if anidados diez niveles o liberaciones repetidas y propensas a error. En un lenguaje con excepciones o RAII esto lo haría el compilador; en C, el kernel lo hace explícito con goto, y es tan sistemático que se lee como un idioma. Domínalo: es cómo el kernel evita fugas de recursos en un entorno donde una fuga no se limpia hasta reiniciar la máquina. La disciplina de “cada adquisición, su etiqueta de limpieza” es el ownership del kernel.
- Escribe una función que devuelva 0 o -errno y otra que la llame propagando el error.
- Escribe una función que devuelva un puntero con
ERR_PTRy manéjala conIS_ERR/PTR_ERR. - Escribe una
initcon tres recursos usando el patrón goto en cascada. - Verifica que en el camino de éxito no se libera nada.