00 lockdep nedir — lock dependency graph
lockdep (Lock Dependency Validator), kernel kilit alımlarını izleyerek ileride deadlock'a yol açabilecek kilit sıralama ihlallerini tespit eden bir runtime doğrulama aracıdır. Deadlock'un kendisi oluşmadan, tehlikeli kilit alım sırasını önceden saptar.
Temel fikir: lock dependency graph
Thread A: lock(L1) → lock(L2) → L1 → L2 bağımlılığı oluşur
Thread B: lock(L2) → lock(L1) → L2 → L1 bağımlılığı oluşur
lockdep bu grafikte çevrim (cycle) tespit eder:
L1 → L2 → L1 ← döngüsel bağımlılık = olası deadlock!
Gerçek deadlock Thread A ve B aynı anda çalıştığında oluşur.
lockdep bunu Thread B'nin ilk kez L2 → L1 yaptığında bildirir.
lockdep özellikleri
Gerekli kernel konfigürasyonu
CONFIG_LOCKDEP=y
CONFIG_PROVE_LOCKING=y # lockdep'i etkinleştirir
CONFIG_DEBUG_LOCKDEP=y # ek lockdep doğrulamaları
CONFIG_LOCK_STAT=y # kilit istatistikleri
CONFIG_DEBUG_SPINLOCK=y # spinlock doğrulama
CONFIG_DEBUG_MUTEXES=y # mutex doğrulama
CONFIG_DEBUG_RT_MUTEXES=y
CONFIG_DEBUG_ATOMIC_SLEEP=y # uyku yasak bağlamda uyku kontrolü
lockdep, her kilit alımında ve bırakımında bir bağımlılık grafiği güncellemesi yapar. Bu, yaklaşık 1.5-2x CPU overhead ve belirgin miktarda ek RAM (lock class hash tablosu) anlamına gelir. Yalnızca debug kernel'larında etkinleştir.
01 lockdep uyarısını okumak
lockdep bir sorun tespit ettiğinde dmesg'e kapsamlı bir rapor yazar. Bu raporu sistematik olarak okumayı öğrenmek, sorunun kaynağını hızla bulmak için gereklidir.
======================================================
WARNING: possible circular locking dependency detected
6.6.0-rc4 #1 Not tainted
------------------------------------------------------
kworker/0:1/42 is trying to acquire lock:
ffff000012345678 (&lock_B){+.+.}-{3:3}, at: func_b+0x30/0x60
^ ^ ^
kilit adresi sınıf kilit türü ve derinlik bilgisi
but task is already holding lock:
ffff000087654321 (&lock_A){+.+.}-{3:3}, at: func_a+0x20/0x80
^
bu kilit zaten tutulmuş
which lock already depends on the new lock.
the existing dependency chain (in reverse order) is:
-> #1 (&lock_B){+.+.}-{3:3}:
lock_acquire
mutex_lock_nested
func_b+0x30/0x60 ← lock_B alındı
func_entry+0x50/0xa0
-> #0 (&lock_A){+.+.}-{3:3}:
lock_acquire
mutex_lock_nested
func_a+0x20/0x80 ← lock_A alındı
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0 CPU1
---- ----
lock(&lock_A); lock(&lock_B);
lock(&lock_A); ← CPU1 bekler
lock(&lock_B); ← CPU0 bekler
↑↑↑ DEADLOCK ↑↑↑
Call Trace:
lockdep_circular_chain_check
validate_chain
__lock_acquire
lock_acquire
mutex_lock_nested
func_a+0x20/0x80
Stack backtrace:
func_a+0x20/0x80
my_driver_ioctl+0x60/0x100 [my_driver]
vfs_ioctl+0x4a/0xa0
======================================================
Raporu okuma rehberi
02 Spinlock vs mutex hataları
Kernel'da kilit türü seçimi bağlama bağlıdır. Yanlış bağlamda yanlış kilit kullanmak lockdep tarafından yakalanır.
Kilit türü seçim kuralları
| Bağlam | Spinlock | Mutex | Sebep |
|---|---|---|---|
| Process context (görev) | Kullanılabilir | Tercih edilir | Mutex uyku destekler, CPU kullanmaz |
| Interrupt handler (IRQ) | Zorunlu (spin_lock_irqsave) | Yasak | IRQ bağlamında uyku yasak |
| Softirq / tasklet | Zorunlu (spin_lock_bh) | Yasak | Softirq'da uyku yasak |
| NMI handler | Yasak (deadlock riski) | Yasak | NMI her şeyi keser |
IRQ bağlamında mutex kullanımı
#include <linux/mutex.h>
#include <linux/interrupt.h>
static DEFINE_MUTEX(my_mutex);
/* IRQ handler — bu bağlamda uyku yasak */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
mutex_lock(&my_mutex); /* HATA: IRQ context'te mutex kullanılamaz */
/* ... */
mutex_unlock(&my_mutex);
return IRQ_HANDLED;
}
BUG: sleeping function called from invalid context
in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 0, name: swapper
preempt_count: 1, expected: 0
RCU nest depth: 0, expected: 0
Call Trace:
__might_resched
__might_sleep
mutex_lock
my_irq_handler+0x30/0x60 ← IRQ handler'da mutex_lock
Düzeltilmiş kod — spinlock kullan
#include <linux/spinlock.h>
static DEFINE_SPINLOCK(my_lock);
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
unsigned long flags;
spin_lock_irqsave(&my_lock, flags); /* IRQ'yu durdur + kilit al */
/* kritik bölge */
spin_unlock_irqrestore(&my_lock, flags);
return IRQ_HANDLED;
}
/* Process context'ten aynı kilide erişirken de irqsave kullan */
void my_process_func(void)
{
unsigned long flags;
spin_lock_irqsave(&my_lock, flags); /* IRQ handler ile aynı kilit */
/* kritik bölge */
spin_unlock_irqrestore(&my_lock, flags);
}
03 Hung task detector
Hung task detector, TASK_UNINTERRUPTIBLE durumunda uzun süre bekleyen görevleri tespit eder. Bu durum genellikle deadlock, sonsuz bekleyen I/O işlemi veya kernel bug'ının belirtisidir.
Konfigürasyon
CONFIG_DETECT_HUNG_TASK=y
CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=120 # varsayılan: 120 saniye
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=n # sadece uyar, panic atma
# veya panic için:
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
Hung task uyarısı
[ 1234.567890] INFO: task kworker/0:1:42 blocked for more than 120 seconds.
[ 1234.567900] Not tainted 6.6.0 #1
[ 1234.567910] "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
[ 1234.567920] task:kworker/0:1 state:D stack:13696 pid:42
[ 1234.567930] Call Trace:
[ 1234.567940] __schedule+0x3a4/0x9c0
[ 1234.567950] schedule+0x5c/0xa0
[ 1234.567960] schedule_preempt_disabled+0x14/0x20
[ 1234.567970] __mutex_lock.isra.0+0x2dc/0x4b0
[ 1234.567980] mutex_lock_nested+0x34/0x40
[ 1234.567990] my_driver_work_handler+0x44/0xa0 [my_driver] ← burada bekleniyor
[ 1234.568000] process_one_work+0x17c/0x3c0
Hung task yorumlama
state:D — TASK_UNINTERRUPTIBLE (D: Deep sleep, sinyal de uyandırmaz). Bu durumun başlıca nedenleri:
iowait yüksekse bu sebeple.nfsd veya ağ sorunu.04 hung_task_timeout_secs — özelleştirme
Hung task detector'ün zaman aşımı sysctl ile çalışma zamanında ayarlanabilir. Farklı sistemlerin farklı bekleme toleransları vardır.
# Mevcut timeout değerini gör
cat /proc/sys/kernel/hung_task_timeout_secs
# 120
# Zaman aşımını azalt (hızlı tespit — test için)
echo 30 > /proc/sys/kernel/hung_task_timeout_secs
# Zaman aşımını artır (uzun I/O işlemleri olan sistemler için)
echo 300 > /proc/sys/kernel/hung_task_timeout_secs
# Hung task tespitini devre dışı bırak (tavsiye edilmez)
echo 0 > /proc/sys/kernel/hung_task_timeout_secs
# Kalıcı ayar
echo 'kernel.hung_task_timeout_secs = 60' >> /etc/sysctl.conf
sysctl -p
# Hung task panic — tespit edilince sistem dump alsın
echo 1 > /proc/sys/kernel/hung_task_panic
Ek parametreler
I/O yavaş veya gerçek zamanlı kısıtlamaları olan gömülü sistemlerde hung task timeout'u dikkatli ayarla. Yüksek CPU yükünde bile D durumundaki görevler normal olabilir (flash yazma, ağ I/O). Çok kısa timeout yanlış alarmlar üretir.
05 RCU stall — uzun read-side critical section
RCU (Read-Copy-Update), kilitsiz okuma için tasarlanmış bir senkronizasyon mekanizmasıdır. Ancak read-side critical section'lar çok uzun sürerse, yazarlar beklemek zorunda kalır — RCU stall bu durumu rapor eder.
RCU nedir
Okuyucu: rcu_read_lock() ... (kısıtlama: uyku yasak, preempt dengeli) ... rcu_read_unlock()
Güncelleyici: rcu_assign_pointer() ... synchronize_rcu() ... kfree(eski_nesne)
^
Tüm okuyucular bitene kadar bekle (grace period)
Bu bekleme çok uzun sürerse: RCU stall
Konfigürasyon
CONFIG_RCU_STALL_COMMON=y
CONFIG_RCU_CPU_STALL_TIMEOUT=21 # 21 saniye (varsayılan)
CONFIG_RCU_CPU_STALL_SUPPRESS=0 # 0 = uyarıları göster
# RCU stall timeout
cat /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
# 21
# Timeout'u değiştir
echo 60 > /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
RCU stall uyarısı
[ 1234.567] rcu: INFO: rcu_sched self-detected stall on CPU
[ 1234.568] rcu: 0-....: (5252 ticks this GP) idle=0002/1/0 softirq=1234/1234 fqs=5
[ 1234.568] rcu: (t=5252 jiffies g=12345 q=12345 ncpus=4)
[ 1234.569] rcu: rcu_sched kthread starved for 5253 jiffies!
[ 1234.570] Task dump for CPU 0:
[ 1234.571] task:my_driver state:R running task
[ 1234.572] Call Trace:
[ 1234.573] my_driver_rcu_cb+0x30/0x60 [my_driver] ← uzun RCU callback
Yaygın RCU stall nedenleri
msleep(), schedule() veya kilit alma. Preemptible RCU olmadıkça yasak.06 lockstat — kilit çekişme istatistikleri
lockstat, her kilit için bekleme süresini, edinim sayısını ve çekişme (contention) yüzdesini istatistiksel olarak takip eder. Performans darboğazı olan kilitleri bulmak için kullanılır.
Konfigürasyon ve etkinleştirme
# lockstat etkinleştir (CONFIG_LOCK_STAT=y gerektirir)
echo 1 > /proc/sys/kernel/lock_stat
# ... yük oluştur / test çalıştır ...
# istatistikleri oku
cat /proc/lock_stat
lock_stat çıktısı
lock_stat version 0.4
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
class name con-bounces contentions waittime-min waittime-max waittime-total waittime-avg acq-bounces acquisitions holdtime-min
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
&my_driver_lock: 0 0 0 0 0 0 145 12458 0.14
acquisitions: 12458 holdtime-total: 123456.78 ns holdtime-avg: 9.91 ns
contentions: 0 waittime-total: 0.00 ns waittime-avg: 0.00 ns
Sütun açıklamaları
# İstatistikleri sıfırla
echo 0 > /proc/lock_stat
echo 1 > /proc/lock_stat # yeniden etkinleştir
# En yüksek bekleme süreli kilitler
cat /proc/lock_stat | sort -k8 -rn | head -20
# lockstat devre dışı bırak
echo 0 > /proc/lock_stat
07 Pratik: deadlock senaryo analizi
Gerçek dünya senaryosu: iki kaynak kilidi (bus_lock ve device_lock) iki farklı kod yolunda ters sırada alınıyor. lockdep bu durumu tespit edecek ve rapor üretecek.
Deadlock modülü
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static DEFINE_MUTEX(bus_lock);
static DEFINE_MUTEX(device_lock);
/* Thread A: bus_lock → device_lock sırasıyla alır */
static int thread_a_func(void *data)
{
pr_info("Thread A: bus_lock alınıyor...\n");
mutex_lock(&bus_lock);
pr_info("Thread A: bus_lock alındı. 100ms bekleniyor...\n");
msleep(100); /* Thread B'nin device_lock almasına fırsat ver */
pr_info("Thread A: device_lock alınıyor...\n");
mutex_lock(&device_lock); /* lockdep BURAYI yakalar */
pr_info("Thread A: her iki kilit alındı (bu satıra ulaşılamaz)\n");
mutex_unlock(&device_lock);
mutex_unlock(&bus_lock);
return 0;
}
/* Thread B: device_lock → bus_lock sırasıyla alır (ters sıra!) */
static int thread_b_func(void *data)
{
msleep(50); /* Thread A'nın bus_lock almasına fırsat ver */
pr_info("Thread B: device_lock alınıyor...\n");
mutex_lock(&device_lock);
pr_info("Thread B: device_lock alındı. bus_lock alınıyor...\n");
mutex_lock(&bus_lock); /* lockdep BURAYI yakalar: AB-BA sırası */
pr_info("Thread B: her iki kilit alındı (bu satıra ulaşılamaz)\n");
mutex_unlock(&bus_lock);
mutex_unlock(&device_lock);
return 0;
}
static struct task_struct *ta, *tb;
static int __init deadlock_init(void)
{
pr_info("deadlock_demo başlatıldı\n");
ta = kthread_run(thread_a_func, NULL, "deadlock-a");
tb = kthread_run(thread_b_func, NULL, "deadlock-b");
return 0;
}
static void __exit deadlock_exit(void)
{
/* Gerçekte deadlock oluşacağından bu kısma ulaşılamaz */
pr_info("deadlock_demo kaldırıldı\n");
}
module_init(deadlock_init);
module_exit(deadlock_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("lockdep deadlock tespiti demo");
Modülü yükle ve lockdep raporunu gözlemle
insmod deadlock_demo.ko
dmesg | grep -A 60 'circular locking'
======================================================
WARNING: possible circular locking dependency detected
------------------------------------------------------
deadlock-b/1235 is trying to acquire lock:
ffffffff82345678 (bus_lock){+.+.}-{3:3}, at: thread_b_func+0x5c/0x80
but task is already holding lock:
ffffffff82345690 (device_lock){+.+.}-{3:3}, at: thread_b_func+0x30/0x80
which lock already depends on the new lock.
Possible unsafe locking scenario:
CPU0 (deadlock-a) CPU1 (deadlock-b)
----------------- -----------------
lock(bus_lock); lock(device_lock);
lock(bus_lock); ← burada bekler
lock(device_lock); ← burada bekler
↑↑↑ DEADLOCK ↑↑↑
Call Trace:
thread_b_func+0x5c/0x80 [deadlock_demo]
======================================================
Çözüm — sabit kilit sırası
/* KURAL: Her zaman bus_lock → device_lock sırasıyla al */
static int thread_b_func_fixed(void *data)
{
msleep(50);
/* bus_lock her zaman ÖNCE alınır — tüm kod yollarında tutarlı sıra */
mutex_lock(&bus_lock);
mutex_lock(&device_lock);
/* kritik bölge */
mutex_unlock(&device_lock);
mutex_unlock(&bus_lock);
return 0;
}
lockdep ile test doğrulama
# Düzeltilmiş modülü yükle
insmod deadlock_fixed.ko
sleep 1
dmesg | grep -c 'circular locking'
# 0 ← lockdep uyarısı yok → kilit sırası tutarlı
Hatırlanacaklar
- lockdep, deadlock olmadan önce kilit sırası döngüsünü tespit eder —
CONFIG_PROVE_LOCKING=y - Rapordaki "Possible unsafe locking scenario" bölümü, hangi CPU'nun hangi sırayla kilit aldığını gösterir
- IRQ bağlamında mutex kullanmak
BUG: sleeping function called from invalid contextüretir - Hung task =
state:D+ uzun süre — deadlock, I/O timeout veya kernel bug echo 60 > /proc/sys/kernel/hung_task_timeout_secs— gömülü sistemler için timeout ayarı- RCU stall = grace period bitmeden okuyucu çok uzun süre meşgul — rcu_read_lock() içinde uyku yasak
- lockstat:
/proc/lock_stat— kilit başına bekleme süresi ve çekişme istatistikleri
Kernel debug rehberlerinin tümüne dön: Kernel Debug Araçları katalogu.