Leer un informe de lockdep
Anatomía del splat possible circular locking dependency: cómo leer el bloque de la clase con su estado de IRQ, seguir la cadena en orden inverso, y usar el escenario CPU0/CPU1 que lockdep sintetiza para llegar al bug en segundos.
El informe de lockdep parece un muro de direcciones hexadecimales, pero tiene una estructura rígida y siempre la misma. Una vez que sabes qué mirar, un splat de deadlock circular se lee en treinta segundos y te entrega los dos sitios de código exactos que hay que reordenar.
- Reconocer la anatomía del splat “possible circular locking dependency”.
- Descifrar la notación de clase
{+.+.}-{2:2}. - Seguir la cadena de dependencias
#1y#0. - Usar el escenario CPU0/CPU1 para localizar el bug.
El esqueleto del splat
Un deadlock ABBA detectado produce un informe con estas partes fijas:
======================================================
WARNING: possible circular locking dependency detected
6.13.0-dios+ #1 Not tainted
------------------------------------------------------
kworker/2:1/91 is trying to acquire lock:
ffff8881036b4120 (&dev->lock_b){+.+.}-{2:2}, at: rutina_b+0x2c/0x90
but task is already holding lock:
ffff8881036b40a0 (&dev->lock_a){+.+.}-{2:2}, at: rutina_a+0x1e/0x80
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (&dev->lock_a){+.+.}-{2:2}:
_raw_spin_lock+0x34/0x50
rutina_a+0x1e/0x80
hilo_uno+0x40/0xc0
-> #0 (&dev->lock_b){+.+.}-{2:2}:
_raw_spin_lock+0x34/0x50
rutina_b+0x2c/0x90
hilo_dos+0x51/0xd0
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&dev->lock_a);
lock(&dev->lock_b);
lock(&dev->lock_a);
lock(&dev->lock_b);
*** DEADLOCK ***
Las dos líneas de cabecera lo dicen casi todo: la tarea actual está intentando adquirir lock_b mientras ya retiene lock_a. Es decir, aquí y ahora se ha recorrido la arista lock_a → lock_b. lockdep se queja porque ya conocía la arista contraria.
La notación de la clase
Cada lock aparece con su nombre de clase y dos descriptores entre llaves. Merece descifrarlos:
(&dev->lock_b)es el nombre de la clase, derivado del nombre de la variable en su inicialización.{+.+.}es el estado de IRQ, cuatro posiciones: hardirq-write, hardirq-read, softirq-write, softirq-read. Los caracteres significan:.nunca usado en ese contexto,+tomado con IRQs habilitadas,-tomado en contexto de IRQ, y?tomado en contexto de IRQ con IRQs habilitadas, el caso peligroso. Un{+.+.}es un lock corriente de contexto de proceso; en cuanto veas un-o un?, hay IRQs en juego.-{2:2}es el contexto de espera externo:interno. Distingue un lock que solo hace spin de uno que puede dormir, y permite a lockdep cazar el error de dormir dentro de una sección atómica. Un2corresponde a un spinlock.
La cadena y el escenario
El bloque “existing dependency chain (in reverse order)” es la prueba. Se lee de abajo arriba: #0 es el lock nuevo que cierra el círculo y #1 el que ya se retenía en la dependencia aprendida antes. Cada entrada trae su pila de llamadas, y ahí está el oro: hilo_uno tomó lock_a y luego lock_b; hilo_dos tomó lock_b y luego lock_a. Los dos sitios de código que hay que conciliar están literalmente en esas pilas.
El bloque final, “Possible unsafe locking scenario”, es lo que lockdep sintetiza por ti: un intercalado concreto de CPU0 y CPU1 que produciría el deadlock. No es una conjetura vaga; es el timing exacto que colgaría la máquina. CPU0 toma A y va a por B; CPU1 toma B y va a por A; abrazo mortal.
trying to acquire
El lock nuevo. La arista que se acaba de recorrer termina aquí.
already holding
El lock ya retenido. La arista nueva empieza aquí.
dependency chain
Las aristas ya aprendidas, con pilas de llamadas: los sitios a reordenar.
unsafe scenario
El intercalado CPU0/CPU1 exacto que causaría el deadlock.
Otros splats: recursión e inversión de IRQ
No todos los informes son ciclos ABBA. Dos parientes aparecen a menudo. El de recursión encabeza con “possible recursive locking detected”: una tarea toma dos veces la misma clase. A veces es un bug real; a veces son dos instancias distintas con jerarquía legítima que lockdep no conoce, el caso que se anota en el nivel 19.4.
El de inversión de IRQ encabeza con “inconsistent lock state” o “possible irq lock inversion dependency”, y ahí el estado entre llaves canta: verás marcas como HARDIRQ-safe frente a HARDIRQ-unsafe, o un ? en el descriptor que delata un lock tomado en contexto de IRQ con IRQs habilitadas.
WARNING: inconsistent lock state
--------------------------------
inconsistent {HARDIRQ-ON-W} -> {IN-HARDIRQ-W} usage.
La disciplina de lectura es la misma en los tres: identifica los locks por su clase, mira el estado entre llaves para saber si hay IRQs en juego, y sigue las pilas hasta el código. Todo splat termina con el mismo andamiaje “other info” y el escenario sintetizado.
Las pilas del splat traen símbolos con desplazamiento, como rutina_b+0x2c/0x90. Para convertirlos en archivo y línea exactos usa scripts/faddr2line vmlinux rutina_b+0x2c/0x90, o addr2line sobre el .ko con símbolos de depuración. Es el paso que traduce el informe en un cursor parpadeando sobre la línea culpable.
Del splat al arreglo
El procedimiento es mecánico. Uno: de las dos pilas, saca las dos rutas que toman los locks en orden opuesto. Dos: decide un orden global para esa pareja de clases, por rol semántico si hay jerarquía, por dirección si son gemelos. Tres: reordena el sitio que viola ese orden. Cuatro: recompila y vuelve a ejecutar; el silencio de lockdep es tu prueba.
En el splat de arriba, hilo_uno marca el orden canónico lock_a → lock_b; el culpable es hilo_dos, que lo invierte. El arreglo es reordenar dos líneas:
/* hilo_dos, ANTES: orden invertido */
spin_lock(&dev->lock_b);
spin_lock(&dev->lock_a);
/* hilo_dos, DESPUES: orden canonico, lock_a antes que lock_b */
spin_lock(&dev->lock_a);
spin_lock(&dev->lock_b);
Con ambos hilos respetando lock_a antes que lock_b, la arista b → a nunca se aprende, el ciclo no existe y lockdep calla.
Interioriza esta diferencia respecto a otros depuradores. Un sanitizer típico te dice “ha pasado algo raro”; lockdep te dice “existe un ciclo en el grafo de dependencias, y aquí está el intercalado exacto que lo convierte en deadlock”. El escenario CPU0/CPU1 que imprime no es una hipótesis: es la construcción explícita de la secuencia fatal a partir del ciclo hallado, y por el teorema que sustenta al validador, un ciclo cerrado es condición necesaria y suficiente para el deadlock. Por eso no debes descartar un splat de lockdep como “ruido” ni como “seguramente un falso positivo”: si la jerarquía que ve es real, el deadlock es real, aunque tu carga de prueba no lo haya disparado nunca y no lo dispare en un año. La única salida legítima es o bien arreglar el orden, o bien —si el anidamiento es correcto y lockdep no lo sabe— enseñárselo con una anotación, que es justo el tema del siguiente nivel. Aprender a leer estos treinta renglones es una de las habilidades de depuración de kernel de mayor retorno que existen: convierte un cuelgue irreproducible en una tarea de diez minutos.
- Reproduce el ABBA del nivel 19.2 y captura el splat completo desde
dmesg. - Señala en él las cuatro partes: acquire, holding, chain, scenario.
- Traduce
{+.+.}-{2:2}a palabras y confirma que son spinlocks de proceso. - Sigue las pilas de
#0y#1hasta las dos funciones culpables y propón el reordenamiento.