00 printk temelleri — log seviyeleri
printk, kernel'ın temel loglama fonksiyonudur. userspace'deki printf()'e benzer ama kernel'ın kendi ring buffer'ına yazar ve seviye (log level) bilgisi taşır.
Log seviyeleri
| Makro | Değer | Anlamı | Örnek durum |
|---|---|---|---|
KERN_EMERG | 0 | Sistem kullanılamaz | Kernel panic |
KERN_ALERT | 1 | Derhal müdahale gerekli | Kritik donanım hatası |
KERN_CRIT | 2 | Kritik durum | RAID degrade |
KERN_ERR | 3 | Hata durumları | Driver initialization başarısız |
KERN_WARNING | 4 | Uyarı — dikkat gerektirir | Kaynak azalması |
KERN_NOTICE | 5 | Normal ama önemli | Aygıt bağlandı |
KERN_INFO | 6 | Bilgi mesajı | Driver yüklendi, parametre değerleri |
KERN_DEBUG | 7 | Debug mesajları | Geliştirici izleme bilgisi |
printk kullanım biçimleri
#include <linux/printk.h>
/* Eski biçim — seviye string olarak önek */
printk(KERN_ERR "i2c_init: donanım bulunamadı, hata=%d\n", ret);
printk(KERN_INFO "my_driver: v1.0 yüklendi, base=0x%08x\n", base_addr);
/* Modern biçim — pr_* makroları (önerilen) */
pr_emerg("kritik donanım hatası!\n");
pr_alert("acil müdahale gerekiyor\n");
pr_crit("kritik bölge hatası\n");
pr_err("başlatma başarısız: %d\n", ret);
pr_warn("kaynaklar azalıyor, kalan=%d\n", free_pages);
pr_notice("ağ arayüzü %s bağlandı\n", ifname);
pr_info("sürücü yüklendi: irq=%d, port=%d\n", irq, port);
pr_debug("__func__=%s, satır=%d, değer=%d\n", __func__, __LINE__, val);
pr_fmt — modül öneki
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/printk.h>
/* Artık tüm pr_* çıktıları "my_driver: " önekiyle başlar */
pr_info("başlatıldı\n");
/* dmesg: [ 12.345] my_driver: başlatıldı */
pr_err("donanım yanıt vermedi\n");
/* dmesg: [ 12.567] my_driver: donanım yanıt vermedi */
Konsola (seri port, framebuffer vb.) yalnızca console_loglevel'dan daha düşük seviyedeki mesajlar yazılır. Ring buffer'a ise tüm mesajlar gider. Varsayılan: 7 (INFO). Debug mesajlarını konsolda görmek için: echo 8 > /proc/sys/kernel/printk
01 dmesg ve journalctl — ring buffer okuma
dmesg, kernel log ring buffer'ını okuyan kullanıcı alanı aracıdır. Systemd kullanan sistemlerde journalctl de kernel mesajlarına erişim sağlar.
dmesg temel kullanım
# tüm ring buffer içeriğini yazdır
dmesg
# insan okunabilir zaman damgasıyla
dmesg -T
# [Nis 12 14:32:15] usb 1-1: new high-speed USB device
# gerçek zamanlı izle (tail -f gibi)
dmesg -w
# sadece belirli seviyeleri göster
dmesg --level err,warn
dmesg --level crit,alert,emerg
# belirli kelimeyi ara
dmesg | grep -i 'i2c'
# boot'tan bu yana geçen süreyi sıfırla (yeni boot referansı)
dmesg -c # okur ve temizler (root gerektirir)
journalctl — systemd entegrasyonu
# sadece kernel mesajları
journalctl -k
journalctl --dmesg
# son boot'tan bu yana kernel mesajları
journalctl -k -b
# bir önceki boot (crash sonrası analiz için)
journalctl -k -b -1
# gerçek zamanlı izle
journalctl -k -f
# sadece hata seviyesi ve üzeri
journalctl -k -p err
# zaman aralığı filtresi
journalctl -k --since "2026-04-12 10:00" --until "2026-04-12 11:00"
# JSON formatında çıktı (otomasyon için)
journalctl -k -o json | python3 -m json.tool | grep MESSAGE
Birçok gömülü sistem systemd kullanmaz. Bu durumda dmesg yeterlidir. Minimal sistemlerde /proc/kmsg'den direkt okuma veya netconsole ile uzak log toplama tercih edilebilir.
02 printk ring buffer — yapı ve yapılandırma
printk, kernel mesajlarını döngüsel (ring) bir tampona yazar. Buffer dolduğunda en eski mesajların üzerine yazılır. Boyut ve davranış kernel konfigürasyonuyla ayarlanır.
Buffer boyutunu yapılandır
# Ring buffer boyutu: 2^CONFIG_LOG_BUF_SHIFT bayt
CONFIG_LOG_BUF_SHIFT=17 # 128 KB (varsayılan)
CONFIG_LOG_BUF_SHIFT=20 # 1 MB (yoğun debug için önerilen)
CONFIG_LOG_BUF_SHIFT=21 # 2 MB
# mevcut ring buffer boyutunu öğren
dmesg --buffer-size
# 131072 (128 KB)
# /proc/kmsg — raw kernel log (bir process'in tutması gerekir)
cat /proc/kmsg & # arka planda çalıştır
# [ 12.345678] kernel: my_driver: başlatıldı
# /dev/kmsg — pozisyon takibi yapan versiyon
cat /dev/kmsg
netconsole — uzak log toplama
Flash alanı kısıtlı gömülü sistemlerde kernel mesajlarını ağ üzerinden uzak bir makineye göndermek mümkündür:
# Hedef sistemde netconsole modülünü yükle
# format: src_port@src_ip/src_iface,dst_port@dst_ip/dst_mac
modprobe netconsole netconsole=6666@192.168.1.100/eth0,514@192.168.1.200/ff:ff:ff:ff:ff:ff
# Geliştirme makinesinde dinle
nc -ul 514
# veya
socat UDP-RECVFROM:514,fork STDOUT
printk zaman damgası formatı
# Mesajlara monotonic timestamp ekle (önerilen)
CONFIG_PRINTK_TIME=y
# Zaman damgası çözünürlüğü: nanosaniye (5.10+)
CONFIG_PRINTK_CALLER=y # çağıran CPU ve görevi göster
03 dynamic_debug — kalıcı debug mesajları
dynamic_debug, kaynak koduna yerleştirilen pr_debug() ve dev_dbg() çağrılarını üretim kernelinde devre dışı bırakarak derler; ardından çalışma zamanında belirli dosya, fonksiyon veya satırlar için seçici olarak etkinleştirir.
Neden dynamic_debug?
Geleneksel yaklaşım:
debug için #ifdef DEBUG ekle → derle → test et → #ifdef kaldır → yeniden derle
↕ yavaş, hata yapımına açık, üretim kodu farklılaşır
dynamic_debug:
pr_debug() yaz → her zaman derlensin → üretimde NOP olarak çalışsın
→ sorun çıktığında çalışma zamanında: echo "file spi.c +p" > control
→ debug mesajları akar → sorun çözülünce: echo "file spi.c -p" > control
Gerekli konfigürasyon
CONFIG_DYNAMIC_DEBUG=y
CONFIG_DYNAMIC_DEBUG_CORE=y # 5.1+ kernel'larda ayrı seçenek
Kaynak kodunda kullanım
#include <linux/dynamic_debug.h>
/* pr_debug — dynamic_debug ile kontrol edilir */
pr_debug("register okuması: addr=0x%02x val=0x%02x\n", addr, val);
/* dev_dbg — cihaz bağlamıyla */
dev_dbg(&pdev->dev, "SPI transfer tamamlandı: %d byte\n", len);
/* print_hex_dump_debug — buffer dump */
print_hex_dump_debug("SPI TX: ", DUMP_PREFIX_OFFSET, 16, 1,
tx_buf, tx_len, true);
CONFIG_DYNAMIC_DEBUG=y ile pr_debug() çağrıları derlenip özel bir ELF section'ına (__verbose) yerleştirilir. Çalışma zamanında bu section okunarak hangi çağrıların aktif olduğu takip edilir. CONFIG_DYNAMIC_DEBUG kapalıysa ve DEBUG makrosu da tanımlı değilse, tüm pr_debug() çağrıları derleme sırasında tamamen silinir.
04 dynamic_debug kontrol arayüzü
dynamic_debug'un tüm kontrolü /sys/kernel/debug/dynamic_debug/control dosyası üzerinden yapılır. Belirli bir kelime birleşimine uyan debug noktaları seçici olarak açılıp kapanabilir.
Kontrol dosyası formatı
# mevcut tüm debug noktalarını listele
cat /sys/kernel/debug/dynamic_debug/control | head -20
# drivers/i2c/i2c-core-base.c:847 [i2c_core]i2c_transfer =_ "i2c transfer..."
# drivers/spi/spi.c:1023 [spi]spi_sync =_ "SPI transfer..."
# ^dosya:satır [modül]fonksiyon =durum "mesaj"
# durum: _ = kapalı, p = aktif
# Belirli dosyayı etkinleştir
echo 'file drivers/i2c/i2c-core-base.c +p' > /sys/kernel/debug/dynamic_debug/control
# Belirli modülü etkinleştir
echo 'module i2c_core +p' > /sys/kernel/debug/dynamic_debug/control
# Belirli fonksiyonu etkinleştir
echo 'func i2c_transfer +p' > /sys/kernel/debug/dynamic_debug/control
# Satır numarasıyla
echo 'file spi.c line 1023 +p' > /sys/kernel/debug/dynamic_debug/control
# Tüm kernel'da debug mesajlarını etkinleştir (dikkatli!)
echo 'module * +p' > /sys/kernel/debug/dynamic_debug/control
# Devre dışı bırak
echo 'module i2c_core -p' > /sys/kernel/debug/dynamic_debug/control
Bayraklar
Boot parametresiyle etkinleştirme
# /boot/grub/grub.cfg veya u-boot bootargs
# Boot sırasında i2c modülü debug'u etkinleştir
dyndbg="module i2c_core +p; module spi +p"
# Sadece belirli dosya
dyndbg="file drivers/spi/spi.c +p"
05 dev_dbg() ailesi — cihaza bağlı loglama
dev_* makroları, her mesaja struct device referansını ekler. Bu sayede log çıktısında hangi cihazdan geldiği bellidir ve dynamic_debug ile cihaz bazlı filtreleme yapılabilir.
dev_* makroları
#include <linux/device.h>
struct device *dev = &pdev->dev; /* ya da &client->dev, &spi->dev vb. */
/* Seviye hiyerarşisi */
dev_emerg(dev, "kritik donanım arızası\n");
dev_alert(dev, "acil durum\n");
dev_crit(dev, "ciddi hata\n");
dev_err(dev, "başlatma hatası: %d\n", ret);
dev_warn(dev, "beklenmeyen durum: state=%d\n", state);
dev_notice(dev, "bağlantı kuruldu\n");
dev_info(dev, "başlatıldı: irq=%d, base=0x%lx\n", irq, base);
dev_dbg(dev, "register okundu: 0x%02x = 0x%02x\n", reg, val);
Örnek dmesg çıktısı
[ 12.345678] i2c i2c-1: bme280 0x76: başlatıldı: irq=32, base=0x0
[ 12.345912] spi spi0.0: flash@0: başlatıldı: irq=45, base=0xfe200000
[ 12.346100] platform my-uart: başlatıldı: irq=57, base=0xfe201000
# cihaz adı (bus:address) otomatik ön ek olarak eklenir
dev_dbg ile dynamic_debug
# belirli bir cihazın debug mesajlarını etkinleştir
# (dynamic_debug cihaz adına göre filtreleme yapar)
echo 'func bme280_probe +p' > /sys/kernel/debug/dynamic_debug/control
# veya modül bazında
echo 'module bme280 +p' > /sys/kernel/debug/dynamic_debug/control
# dmesg'de görünür:
# [ 145.678] bme280 1-0076: register 0xD0 = 0x60 (chip_id OK)
Probe fonksiyonunda -EPROBE_DEFER döndürülmesi durumunu sessizce yutmak ve diğer hataları dev_err ile loglamak için kullanılır: return dev_err_probe(dev, ret, "irq isteği başarısız\n");
06 Ratelimited loglama — burst koruması
Hızla tekrar eden bir hata durumunda printk, ring buffer'ı tek bir hata mesajıyla doldurabilir ve diğer kritik mesajların kaybolmasına yol açabilir. Ratelimited varyantlar bunu önler.
Ratelimited makrolar
#include <linux/ratelimit.h>
/* Varsayılan ratelimit: 5 saniyede en fazla 10 mesaj */
pr_err_ratelimited("SPI transfer hatası: %d\n", ret);
pr_warn_ratelimited("düşük bellek, kalan=%d KB\n", free_kb);
dev_err_ratelimited(dev, "I2C NAK alındı, adr=0x%02x\n", addr);
/* Özel limit tanımla */
static DEFINE_RATELIMIT_STATE(my_rs, 10 * HZ, 5);
/* 10 saniyede en fazla 5 mesaj */
if (__ratelimit(&my_rs))
pr_err("özel ratelimit: CRC hatası blok=%llu\n", block_nr);
Ratelimit davranışı
[ 100.001] SPI transfer hatası: -110
[ 100.002] SPI transfer hatası: -110
[ 100.003] SPI transfer hatası: -110
[ 100.004] SPI transfer hatası: -110
[ 100.005] SPI transfer hatası: -110
[ 100.006] SPI transfer hatası: -110
[ 100.007] SPI transfer hatası: -110
[ 100.008] SPI transfer hatası: -110
[ 100.009] SPI transfer hatası: -110
[ 100.010] SPI transfer hatası: -110
[ 100.011] printk: 847 messages suppressed.
[ 105.001] SPI transfer hatası: -110
# 5 saniye sonra tekrar başlar
Ratelimit mekanizması üretim için doğru yaklaşımdır, ancak debug sırasında mesajların bastırılması hatanın gizlenmesine yol açabilir. Debug modunda pr_err() (ratelimitsiz) kullan, sorun çözüldükten sonra pr_err_ratelimited()'a geç.
printk_once ve printk_deferred
# Yalnızca bir kez yazdır (tüm sistem ömrü boyunca)
pr_err_once("bu uyarı yalnızca bir kez görünür\n");
dev_warn_once(dev, "eski API kullanımı tespit edildi\n");
# Ertelenmiş yazdırma — atomic bağlamdan güvenli çağrı
printk_deferred(KERN_ERR "atomic context'ten mesaj\n");
07 Pratik: driver debug workflow
Gerçek dünya senaryosu: bir SPI flash driver, probe sırasında başarısız oluyor ama hata mesajı yeterince ayrıntılı değil. Sıfır yeniden derlemeyle debug workflow'u.
Başlangıç durumu — yetersiz hata mesajı
[ 2.345678] spi-nor spi0.0: unrecognized JEDEC id bytes: ff, ff, ff
[ 2.345900] spi-nor: probe of spi0.0 failed with error -22
Adım 1 — dynamic_debug ile spi-nor modülünü aç
# spi-nor modülündeki tüm pr_debug çağrılarını etkinleştir
echo 'module spi_nor +p' > /sys/kernel/debug/dynamic_debug/control
# modülü yeniden yükle (veya cihazı bağla)
modprobe -r spi_nor && modprobe spi_nor
# ya da sysfs üzerinden aygıtı yeniden probe et
echo spi0.0 > /sys/bus/spi/drivers/spi-nor/unbind 2>/dev/null
echo spi0.0 > /sys/bus/spi/drivers/spi-nor/bind
Adım 2 — Ayrıntılı debug çıktısını incele
[ 5.123456] spi spi0.0: spi_nor_read_id: CS assert
[ 5.123512] spi spi0.0: spi_nor_read_id: RDID cmd=0x9F sent
[ 5.123534] spi spi0.0: spi_nor_read_id: response: ff ff ff ff
[ 5.123556] spi spi0.0: SPI clock divider=4, actual=12.5 MHz
# ff ff ff → MISO hattı sürekli yüksek → CS veya MISO bağlantı sorunu
Adım 3 — Elektriksel sorun teyidi ve çözüm
Tüm yanıtların 0xFF olması, MISO hattının her zaman yüksek (pull-up) olduğunu gösteriyor. Sürücü debugu bizi doğrudan fiziksel katmana yönlendirdi. Bağlantı düzeltildikten sonra:
[ 2.345678] spi spi0.0: spi_nor_read_id: response: ef 40 18
[ 2.345700] spi-nor spi0.0: w25q128jv (16384 Kbytes)
[ 2.346000] spi-nor spi0.0: mtd .name = spi0.0, .size = 0x01000000
Adım 4 — Dynamic debug'u kapat
# Sorun çözüldü, debug mesajlarını kapat
echo 'module spi_nor -p' > /sys/kernel/debug/dynamic_debug/control
# Teyit et
grep 'spi_nor' /sys/kernel/debug/dynamic_debug/control | grep -c '=p'
# 0 (hiçbir mesaj aktif değil)
Hatırlanacaklar
pr_err(),pr_warn(),pr_info(),pr_debug()— modern pr_* makroları,printk(KERN_*)'ten tercih edilir#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt— her mesaja modül öneki eklerdmesg -Tokunabilir zaman,dmesg --level err,warnseviye filtresiCONFIG_DYNAMIC_DEBUG=yile pr_debug() mesajları çalışma zamanında açılabilirecho 'module X +p' > .../dynamic_debug/control— modül bazlı etkinleştirmedev_err(),dev_dbg()— mesajlara cihaz adı ekler, sürücü loglaması için standartpr_err_ratelimited()— tekrarlayan hatalarda ring buffer'ı korur
Bir sonraki adım: KASAN & sanitizer rehberi — memory hataları ve undefined behavior tespiti.