Tüm eğitimler
REHBer GÖMÜLÜ LİNUX KERNEL

Linux Kernel Modülleri —
LKM Yazımı.

kernel'ı recompile etmeden genişlet — module_init, sysfs, character device ve ioctl.

00 Kernel module nedir

LKM (Loadable Kernel Module), sistemin yeniden başlatılmasına gerek kalmadan çalışan kernel'a runtime'da eklenip çıkarılabilen kernel space koddur.

Linux kernel'ı iki temel mimariden birini seçmek zorundadır: monolitik ya da microkernel. Monolitik kernellerde tüm servisler (sürücüler, dosya sistemi, ağ yığını) tek bir adres alanında çalışır; microkernel'larda ise servisler izole süreçlere dağıtılır. Linux, her iki dünyanın avantajını birleştiren modüler monolitik yaklaşımı benimser: çekirdek monolitiktir ancak bileşenler runtime'da yüklenip boşaltılabilir.

Statik derleme vs loadable module

Bir kernel bileşeni iki farklı şekilde derlenebilir. Bu seçim Kconfig dosyasındaki tek bir karakterle belirlenir:

Kconfig değeriAnlamıSonuç
CONFIG_FOO=y built-in, statik vmlinuz içine derlenir; ayrı .ko dosyası yok
CONFIG_FOO=m loadable module foo.ko dosyası oluşturulur; runtime yüklenir
CONFIG_FOO is not set devre dışı hiç derlenmez

Built-in modüller boot zamanında yüklenir ve bellekten çıkarılamaz. Loadable module (.ko) ise kernel header'larına karşı derlenir, bellekte yüklü tutulur ve rmmod ile kaldırılabilir.

Temel araçlar

lsmodYüklü modülleri, boyutlarını ve bağımlılıklarını listeler — /proc/modules okunur
modinfoBir .ko dosyasının metadata'sını gösterir: lisans, yazar, parametreler, versiyon
insmodBelirtilen .ko dosyasını kernel'a yükler; bağımlılık çözümü yapmaz
rmmodYüklü modülü kernel'dan kaldırır; kullanımda veya bağımlılığı varsa hata verir
modprobeinsmod'un akıllı versiyonu; /lib/modules/ altında bağımlılıkları otomatik çözer ve yükler
dmesg -wKernel ring buffer'ı canlı takip eder — pr_info/pr_err çıktılarını gerçek zamanlı görmek için

Neden kernel module yazılır

Kernel module geliştirmek gereken yaygın durumlar şunlardır:

Hardware driverDonanım aygıtı için character/block/network device sürücüsü — /dev/mydev veya network interface
Filesystemext4, btrfs, overlayfs gibi VFS üzerine dosya sistemi implementasyonu
Network protocolÖzel protokol veya Netfilter hook'u — paket filtreleme, NAT, güvenlik duvarı
Debug/tracingkprobe, ftrace hook'u, özel /proc veya /sys arayüzü ile sistem davranışını izleme
  Kullanıcı alanı  (userspace)
  ┌─────────────────────────────────────────┐
  │  ls, cat, custom_app, python_script...  │
  └────────────────────┬────────────────────┘
                       │  syscall (read/write/ioctl/...)
  ┌────────────────────▼────────────────────┐
  │  Kernel space                           │
  │  ┌──────────┐  ┌──────────────────────┐ │
  │  │ built-in │  │ hello.ko (LKM)       │ │
  │  │ drivers  │  │ → insmod / modprobe  │ │
  │  └──────────┘  └──────────────────────┘ │
  └─────────────────────────────────────────┘
    
NOT

Kernel module kodu, userspace koduyla aynı C'yi kullanır ancak standart C kütüphanesi (glibc) kullanılamaz. Tüm fonksiyonlar kernel'ın kendi API'sinden gelir: printk(), kmalloc(), copy_to_user() vb.

Bu bölümde

  • LKM: kernel'ı reboot gerektirmeden runtime'da genişleten .ko dosyaları
  • Linux modüler monolitik mimarisi: monolitik hız + dinamik yükleme esnekliği
  • Kconfig: =y built-in, =m loadable module, is not set devre dışı
  • insmod/rmmod/modprobe/lsmod/modinfo araç seti
  • Kullanım alanları: driver, filesystem, network protocol, debug

01 İlk modül: Hello World

Kernel module geliştirmenin minimum gereksinimlerini karşılayan, derlenip yüklenebilir ve kaldırılabilir ilk modül.

Bir kernel modülünün olmazsa olmaz iki bileşeni vardır: yükleme fonksiyonu ve kaldırma fonksiyonu. Bu fonksiyonlar sırasıyla module_init() ve module_exit() makrolarıyla kernel'a tanıtılır.

hello_module.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("Basit Hello World kernel modülü");
MODULE_VERSION("1.0");

static int __init hello_init(void)
{
    pr_info("hello_module: yüklendi — merhaba kernel!\n");
    pr_info("hello_module: kernel versiyonu %s\n", UTS_RELEASE);
    return 0; /* 0 döndür = başarılı yükleme */
}

static void __exit hello_exit(void)
{
    pr_info("hello_module: kaldırıldı — güle güle kernel!\n");
}

module_init(hello_init);
module_exit(hello_exit);

Kernel log seviyeleri

Eski printk(KERN_INFO "mesaj") yerine modern kernel kodu bu wrapper makroları kullanır:

pr_emerg()Seviye 0 — sistem kullanılamaz, panic öncesi
pr_alert()Seviye 1 — acil müdahale gerekli
pr_crit()Seviye 2 — kritik koşul
pr_err()Seviye 3 — hata durumu; başarısız işlem bildirimi
pr_warn()Seviye 4 — uyarı; çalışma devam eder ama dikkat
pr_notice()Seviye 5 — normal ama önemli durum
pr_info()Seviye 6 — bilgi mesajı; en sık kullanılan
pr_debug()Seviye 7 — debug; CONFIG_DYNAMIC_DEBUG olmadan derlenmez

Makefile

Makefile
# obj-m: bu hedefin loadable module olarak derleneceğini belirtir
obj-m := hello_module.o

# KDIR: kernel build tree — uname -r ile çalışan kernel versiyonu
KDIR := /lib/modules/$(shell uname -r)/build

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

Derleme ve test adımları

terminal
# 1. Derle
$ make
make -C /lib/modules/6.1.0/build M=/home/emirhan/hello_module modules
  CC [M]  /home/emirhan/hello_module/hello_module.o
  MODPOST /home/emirhan/hello_module/Module.symvers
  CC [M]  /home/emirhan/hello_module/hello_module.mod.o
  LD [M]  /home/emirhan/hello_module/hello_module.ko

# 2. Modül bilgisini incele
$ modinfo hello_module.ko
filename:       /home/emirhan/hello_module/hello_module.ko
version:        1.0
description:    Basit Hello World kernel modülü
author:         Emirhan Pehlevan
license:        GPL
srcversion:     A3B2C1D4E5F6...
depends:
retpoline:      Y
name:           hello_module
vermagic:       6.1.0 SMP mod_unload

# 3. Yükle — ayrı terminalde dmesg izle
$ sudo dmesg -w &
$ sudo insmod hello_module.ko
[  142.331022] hello_module: yüklendi — merhaba kernel!
[  142.331025] hello_module: kernel versiyonu 6.1.0

# 4. Yüklü modülleri listele
$ lsmod | grep hello
hello_module           16384  0

# 5. Kaldır
$ sudo rmmod hello_module
[  198.004411] hello_module: kaldırıldı — güle güle kernel!
DİKKAT

module_init() fonksiyonu 0 dönmek zorundadır; negatif errno değeri dönerse kernel modülü yüklemez ve insmod hata verir. Hata durumunda mutlaka tahsis edilen kaynakları temizle ve negatif hata kodu dön.

Bu bölümde

  • module_init() / module_exit() — yükleme ve kaldırma callback'leri
  • pr_info / pr_warn / pr_err — printk wrapper'ları; log seviyesi kontrolü
  • MODULE_LICENSE("GPL") zorunlu — kapalı kaynak modüller kernel API'sine erişemez
  • Makefile: obj-m ile out-of-tree modül derleme, KDIR kernel build tree
  • insmod → lsmod → dmesg → rmmod test döngüsü

02 Module parametreleri

Modülü yeniden derlemeden davranışını değiştirmek için module_param() ile runtime konfigürasyon parametreleri tanımlanır.

Kernel modülleri module_param() makrosu ile yükleme anında veya runtime'da değiştirilebilen parametreler tanımlayabilir. Bu parametre değerleri /sys/module/<modül_adı>/parameters/ dizini altında dosya sistemi arayüzüyle erişilebilir hale gelir.

param_module.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("Module parametre örneği");

/* Parametreler — global değişken + module_param() */
static bool  debug  = false;
static int   count  = 1;
static char *label  = "default";
static short speed  = 100;
static int   ports[4];
static int   nports = 0;

/*
 * module_param(isim, tip, izinler)
 *   isim  : değişken adı (aynı zamanda parametre adı)
 *   tip   : bool | int | charp | short | long | uint | ulong
 *   izinler: 0644 = owner rw, group r, other r
 *            0 = sysfs'te görünmez (yalnızca yükleme anında)
 */
module_param(debug,  bool,  0644);
module_param(count,  int,   0644);
module_param(label,  charp, 0444); /* string — sadece okunabilir */
module_param(speed,  short, 0644);

/* Dizi parametresi: max 4 eleman, gerçek eleman sayısı nports'a yazılır */
module_param_array(ports, int, &nports, 0644);

MODULE_PARM_DESC(debug,  "Debug logları aç/kapat (default: false)");
MODULE_PARM_DESC(count,  "İşlem tekrar sayısı (default: 1)");
MODULE_PARM_DESC(label,  "Modül etiketi string (default: 'default')");
MODULE_PARM_DESC(speed,  "Hız kHz cinsinden (default: 100)");
MODULE_PARM_DESC(ports,  "Port numaraları dizisi (max 4)");

static int __init param_init(void)
{
    int i;

    pr_info("param_module: debug=%d count=%d label=%s speed=%d\n",
            debug, count, label, speed);

    for (i = 0; i < nports; i++)
        pr_info("param_module: port[%d] = %d\n", i, ports[i]);

    if (debug)
        pr_debug("param_module: debug modu aktif\n");

    return 0;
}

static void __exit param_exit(void)
{
    pr_info("param_module: kaldırıldı\n");
}

module_init(param_init);
module_exit(param_exit);

Yükleme sırasında parametre geçmek

terminal
# insmod ile doğrudan parametre geç
$ sudo insmod param_module.ko debug=1 count=5 label="test" ports=8080,8443,9090

# dmesg çıktısı
[  55.123] param_module: debug=1 count=5 label=test speed=100
[  55.124] param_module: port[0] = 8080
[  55.125] param_module: port[1] = 8443
[  55.126] param_module: port[2] = 9090

# sysfs üzerinden runtime oku
$ cat /sys/module/param_module/parameters/debug
N
$ cat /sys/module/param_module/parameters/count
5

# sysfs üzerinden runtime yaz (izin 0644 olanlar)
$ echo 10 | sudo tee /sys/module/param_module/parameters/count
$ cat /sys/module/param_module/parameters/count
10

modprobe ile kalıcı parametre

modprobe kullanıldığında parametreler /etc/modprobe.d/ dizini altındaki konfigürasyon dosyalarından okunur; bu sayede sistem her açılışında aynı parametrelerle yükleme yapılır:

/etc/modprobe.d/param_module.conf
# /etc/modprobe.d/param_module.conf
# modprobe param_module çalıştırıldığında bu parametreler otomatik uygulanır
options param_module debug=1 count=10 speed=200
NOT

Parametre izni (perm) 0 verilirse parametre sadece yükleme anında geçilebilir; /sys/module/ altında hiç görünmez. Güvenlik açısından hassas parametreler (örneğin şifre, API key) için 0 kullanılmalıdır — ancak kernel modülüne şifre geçmek genellikle tasarım hatasıdır.

Bu bölümde

  • module_param(isim, tip, perm) — bool/int/charp/short/long/uint destekli
  • module_param_array() — dizi parametresi; eleman sayısı ayrı değişkende
  • MODULE_PARM_DESC() — modinfo çıktısında ve belgede görünen açıklama
  • /sys/module/<mod>/parameters/ — runtime okuma/yazma arayüzü
  • /etc/modprobe.d/ — kalıcı parametre konfigürasyonu

03 Kernel bellek yönetimi

Kernel space'te bellek yönetimi userspace malloc/free'den farklıdır; allocation context'i, fiziksel süreklilik garantileri ve SLAB allocator bilinmeden doğru bellek kullanımı yapılamaz.

Kernel'da bellek tahsisi için en temel fonksiyon kmalloc()'tur. İkinci argüman olan GFP flag (Get Free Page), allocation'ın nasıl yapılacağını belirler. Bu flag seçimi yanlış yapılırsa kernel panic veya deadlock oluşabilir.

GFP flag'leri

GFP_KERNELNormal allocation; bellek bulunamazsa process uyutulabilir (sleep). Sadece process context'te — interrupt'ta kullanılamaz
GFP_ATOMICInterrupt context veya spinlock tutarken kullanılır; sleep edilemez. Başarısız olursa NULL döner
GFP_DMADMA-uyumlu bellek; düşük adres bölgesinden (ZONE_DMA) tahsis
GFP_NOWAITSleep yapmaz ama GFP_ATOMIC kadar agresif değil; kqueue callback'leri için
__GFP_ZEROTahsis edilen belleği sıfırla — kzalloc() bunu otomatik yapar
memory_demo.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>

MODULE_LICENSE("GPL");

struct mydata {
    int   id;
    char  name[64];
    void *payload;
};

static int __init mem_init(void)
{
    struct mydata *p;
    int           *arr;
    void          *big;

    /* kmalloc: küçük allocation, fiziksel olarak sürekli */
    p = kmalloc(sizeof(*p), GFP_KERNEL);
    if (!p) {
        pr_err("mem_demo: kmalloc başarısız\n");
        return -ENOMEM;
    }
    p->id = 42;
    pr_info("mem_demo: kmalloc %zu bayt @ %p\n", sizeof(*p), p);
    kfree(p);

    /* kzalloc: kmalloc + memset(0) — struct field'larını sıfırlar */
    p = kzalloc(sizeof(*p), GFP_KERNEL);
    if (!p)
        return -ENOMEM;
    /* p->id == 0, p->name == "", p->payload == NULL garantili */
    kfree(p);

    /* kcalloc: n eleman için type-safe array alloc + sıfırlama */
    arr = kcalloc(16, sizeof(int), GFP_KERNEL);
    if (!arr)
        return -ENOMEM;
    arr[0] = 100;
    arr[15] = 200;
    kfree(arr);

    /* vmalloc: büyük allocation (MB düzeyi), sanal sürekli
     * fiziksel süreklilik gerekmez — DMA için UYGUN DEĞİL */
    big = vmalloc(4 * 1024 * 1024); /* 4 MB */
    if (!big)
        return -ENOMEM;
    pr_info("mem_demo: vmalloc 4MB @ %p\n", big);
    vfree(big);

    return 0;
}

static void __exit mem_exit(void) { }

module_init(mem_init);
module_exit(mem_exit);

kmalloc vs vmalloc karşılaştırması

Özellikkmallocvmalloc
Fiziksel süreklilikGarantiliGarantisiz
Sanal süreklilikGarantiliGarantili
DMA kullanımıUygunUygun değil
Maksimum boyut~4 MB (arch'a göre)Sanal adres alanıyla sınırlı
HızHızlıDaha yavaş (TLB)
Serbest bırakmakfree()vfree()

SLAB/SLUB allocator

kmalloc'un altında SLAB allocator (modern kernellerde SLUB) çalışır. SLUB, aynı boyuttaki nesneler için önceden ayrılmış havuzlar (slab cache) tutar. Bu havuzlar sayesinde:

HızSık kullanılan boyutlar için pre-allocated cache — page allocator'a her seferinde gidilmez
Fragmentation azaltmaAynı boyuttaki nesneler aynı slab'da paketlenir — internal fragmentation minimuma iner
Per-CPU cacheHer CPU'nun kendi slab cache'i — lock contention azaltır
terminal — slab istatistikleri
# Mevcut slab cache'lerini listele
$ cat /proc/slabinfo | head -20
slabinfo - version: 2.1
# name            <active_objs> <num_objs> <objsize> <objperslab> ...
ext4_inode_cache    5120   5120    1152   28    8 ...
sock_inode_cache     512    512     768   21    4 ...
...

# SLUB debugger ile nesne takibi (CONFIG_SLUB_DEBUG=y gerekli)
$ cat /sys/kernel/slab/kmalloc-128/alloc_calls
UYARI

Her kmalloc() çağrısına karşılık bir kfree() olmalıdır. Kernel'da bellek sızıntısı tespit edildiğinde sistem yeniden başlatılana kadar bellekten yer tüketmeye devam eder — userspace'teki gibi süreç öldüğünde otomatik temizlik yoktur.

Bu bölümde

  • kmalloc(size, GFP_KERNEL) — fiziksel sürekli bellek; process context'te
  • GFP_ATOMIC — interrupt context / spinlock altında; sleep etmez
  • kzalloc() sıfırlanmış, kcalloc() type-safe dizi allocation
  • vmalloc() büyük bellek için sanal sürekli; DMA için uygun değil
  • SLUB allocator: per-CPU slab cache, düşük latency, fragmentation kontrolü

04 Character device oluşturma

Character device, userspace uygulamalarının kernel modülüyle /dev/mydev üzerinden read/write/ioctl ile iletişim kurmasını sağlar.

Linux'ta her aygıt bir major ve minor numara çiftiyle tanımlanır. Major numara hangi driver'ın kullanılacağını, minor numara ise aygıt örneğini belirler. Modern kernel'da major numarası statik atamak yerine dinamik tahsis tercih edilir.

  alloc_chrdev_region()          → major:minor al
  cdev_init(&cdev, &fops)        → file_operations bağla
  cdev_add(&cdev, devno, 1)      → kernel'a kaydet
  class_create("mydev")          → /sys/class/mydev/ oluştur
  device_create(cls, devno, ...) → udev tetikle → /dev/mydev
    
chardev.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("Character device — buffer okuma/yazma");

#define DEVICE_NAME  "mychardev"
#define CLASS_NAME   "myclass"
#define BUF_SIZE     1024

static dev_t            devno;
static struct cdev      my_cdev;
static struct class    *my_class;
static struct device   *my_device;
static char            *kbuf;
static size_t           buf_len;

/* open: /dev/mychardev açıldığında çağrılır */
static int mydev_open(struct inode *inode, struct file *filp)
{
    pr_info("mychardev: açıldı\n");
    return 0;
}

/* release: son file descriptor kapatıldığında çağrılır */
static int mydev_release(struct inode *inode, struct file *filp)
{
    pr_info("mychardev: kapatıldı\n");
    return 0;
}

/* read: kullanıcı cat /dev/mychardev yaptığında çağrılır */
static ssize_t mydev_read(struct file *filp, char __user *ubuf,
                           size_t len, loff_t *off)
{
    size_t to_copy;

    if (*off >= buf_len)
        return 0; /* EOF */

    to_copy = min(len, buf_len - (size_t)*off);

    /* copy_to_user: kernel → userspace güvenli kopya
     * Doğrudan memcpy YASAK — userspace pointer geçersiz olabilir */
    if (copy_to_user(ubuf, kbuf + *off, to_copy))
        return -EFAULT;

    *off += to_copy;
    return to_copy;
}

/* write: echo "merhaba" > /dev/mychardev yapıldığında çağrılır */
static ssize_t mydev_write(struct file *filp, const char __user *ubuf,
                            size_t len, loff_t *off)
{
    size_t to_write = min(len, (size_t)(BUF_SIZE - 1));

    /* copy_from_user: userspace → kernel güvenli kopya */
    if (copy_from_user(kbuf, ubuf, to_write))
        return -EFAULT;

    kbuf[to_write] = '\0';
    buf_len = to_write;
    pr_info("mychardev: %zu bayt yazıldı: %s\n", to_write, kbuf);
    return to_write;
}

/* file_operations tablosu — her callback opsiyonel, NULL olabilir */
static const struct file_operations mydev_fops = {
    .owner   = THIS_MODULE,
    .open    = mydev_open,
    .release = mydev_release,
    .read    = mydev_read,
    .write   = mydev_write,
};

static int __init chardev_init(void)
{
    int ret;

    /* 1. Kernel buffer tahsis et */
    kbuf = kzalloc(BUF_SIZE, GFP_KERNEL);
    if (!kbuf)
        return -ENOMEM;

    /* 2. Major/minor numarası dinamik tahsis et */
    ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        pr_err("chardev: major tahsis başarısız: %d\n", ret);
        goto err_kfree;
    }
    pr_info("chardev: major=%d minor=%d\n", MAJOR(devno), MINOR(devno));

    /* 3. cdev başlat ve kernel'a kaydet */
    cdev_init(&my_cdev, &mydev_fops);
    my_cdev.owner = THIS_MODULE;
    ret = cdev_add(&my_cdev, devno, 1);
    if (ret < 0) {
        pr_err("chardev: cdev_add başarısız: %d\n", ret);
        goto err_unreg;
    }

    /* 4. sysfs class oluştur → udev /dev/mychardev yaratır */
    my_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(my_class)) {
        ret = PTR_ERR(my_class);
        goto err_cdev;
    }

    my_device = device_create(my_class, NULL, devno, NULL, DEVICE_NAME);
    if (IS_ERR(my_device)) {
        ret = PTR_ERR(my_device);
        goto err_class;
    }

    pr_info("chardev: /dev/%s hazır\n", DEVICE_NAME);
    return 0;

/* Hata durumunda ters sırayla temizle */
err_class:  class_destroy(my_class);
err_cdev:   cdev_del(&my_cdev);
err_unreg:  unregister_chrdev_region(devno, 1);
err_kfree:  kfree(kbuf);
            return ret;
}

static void __exit chardev_exit(void)
{
    device_destroy(my_class, devno);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(devno, 1);
    kfree(kbuf);
    pr_info("chardev: kaldırıldı\n");
}

module_init(chardev_init);
module_exit(chardev_exit);

Test

terminal
$ sudo insmod chardev.ko
$ ls -la /dev/mychardev
crw------- 1 root root 240, 0 Apr 12 10:00 /dev/mychardev

$ echo "merhaba kernel" | sudo tee /dev/mychardev
$ sudo cat /dev/mychardev
merhaba kernel
DİKKAT

copy_to_user() ve copy_from_user() kullanmadan doğrudan userspace pointer'a erişmek kernel panik veya güvenlik açığına yol açar. Bu fonksiyonlar pointer geçerliliğini kontrol eder ve page fault durumunu yönetir.

Bu bölümde

  • alloc_chrdev_region() → dinamik major/minor tahsisi
  • cdev_init() + cdev_add() → kernel'a file_operations kaydı
  • class_create() + device_create() → udev tetikleme, /dev/mydev oluşturma
  • copy_to_user() / copy_from_user() — kernel↔userspace güvenli veri transferi
  • goto ile hata yönetimi ve ters sıra temizleme

05 sysfs arayüzü

sysfs, kernel nesnelerini /sys/ altında dosya sistemi olarak sunan sanal filesystem'dir; driver'dan userspace'e parametre ve durum bilgisi okumak/yazmak için standart yoldur.

sysfs'in temelinde kobject altyapısı yatar. Her /sys/ girişi bir kobject'e bağlıdır. Device driver yazarken doğrudan kobject API'siyle uğraşmak yerine device_attribute üzerinden çalışılır.

DEVICE_ATTR makroları

DEVICE_ATTR_RW(isim)Hem okunabilir hem yazılabilir attribute — show ve store callback'lerini tanımla
DEVICE_ATTR_RO(isim)Sadece okunabilir (0444) — sadece show callback'i tanımla
DEVICE_ATTR_WO(isim)Sadece yazılabilir (0200) — sadece store callback'i tanımla
sysfs_module.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/string.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");

static int  myval  = 0;
static char mystr[64] = "baslangic";

/*
 * show: cat /sys/kernel/mymodule/myval  →  bu fonksiyon çağrılır
 * Dönüş: yazdırılan byte sayısı; PAGE_SIZE'dan büyük olamaz
 */
static ssize_t myval_show(struct kobject *kobj,
                           struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%d\n", myval);
}

/*
 * store: echo "42" > /sys/kernel/mymodule/myval  →  bu fonksiyon çağrılır
 * buf: kullanıcının yazdığı string; count: uzunluğu
 */
static ssize_t myval_store(struct kobject *kobj,
                            struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
    int ret = kstrtoint(buf, 10, &myval);
    if (ret)
        return ret;
    pr_info("sysfs_module: myval = %d\n", myval);
    return count;
}

static ssize_t mystr_show(struct kobject *kobj,
                           struct kobj_attribute *attr, char *buf)
{
    return sysfs_emit(buf, "%s\n", mystr);
}

static ssize_t mystr_store(struct kobject *kobj,
                            struct kobj_attribute *attr,
                            const char *buf, size_t count)
{
    size_t len = min(count, sizeof(mystr) - 1);
    strncpy(mystr, buf, len);
    mystr[len] = '\0';
    /* sondaki newline temizle */
    if (len > 0 && mystr[len - 1] == '\n')
        mystr[len - 1] = '\0';
    return count;
}

/* kobj_attribute tanımla: isim, izin, show, store */
static struct kobj_attribute myval_attr =
    __ATTR(myval, 0664, myval_show, myval_store);
static struct kobj_attribute mystr_attr =
    __ATTR(mystr, 0664, mystr_show, mystr_store);

/* Attribute grubu — tek sysfs_create_group() çağrısıyla hepsini ekle */
static struct attribute *mymod_attrs[] = {
    &myval_attr.attr,
    &mystr_attr.attr,
    NULL
};

static const struct attribute_group mymod_group = {
    .attrs = mymod_attrs,
};

static struct kobject *mymod_kobj;

static int __init sysfs_init(void)
{
    int ret;

    /* /sys/kernel/mymodule/ dizini oluştur */
    mymod_kobj = kobject_create_and_add("mymodule", kernel_kobj);
    if (!mymod_kobj)
        return -ENOMEM;

    ret = sysfs_create_group(mymod_kobj, &mymod_group);
    if (ret) {
        kobject_put(mymod_kobj);
        return ret;
    }

    pr_info("sysfs_module: /sys/kernel/mymodule/ oluşturuldu\n");
    return 0;
}

static void __exit sysfs_exit(void)
{
    sysfs_remove_group(mymod_kobj, &mymod_group);
    kobject_put(mymod_kobj);
    pr_info("sysfs_module: kaldırıldı\n");
}

module_init(sysfs_init);
module_exit(sysfs_exit);

Kullanım

terminal
$ sudo insmod sysfs_module.ko

# Attribute dosyalarını listele
$ ls /sys/kernel/mymodule/
mystr  myval

# Değer oku
$ cat /sys/kernel/mymodule/myval
0

# Değer yaz
$ echo 42 | sudo tee /sys/kernel/mymodule/myval
$ cat /sys/kernel/mymodule/myval
42

$ echo "yeni_deger" | sudo tee /sys/kernel/mymodule/mystr
$ cat /sys/kernel/mymodule/mystr
yeni_deger
NOT

sysfs_emit() — eski sprintf(buf, ...) yerine kullanılmalıdır. sysfs_emit, PAGE_SIZE sınırını otomatik kontrol eder ve buffer overflow'u önler. Linux 5.10'dan itibaren tercih edilen API budur.

Bu bölümde

  • sysfs: /sys/ altında kobject hiyerarşisi — kernel nesnelerini dosya olarak sun
  • kobject_create_and_add() → /sys/kernel/mymodule/ dizini
  • kobj_attribute + __ATTR() → show/store callback çifti
  • sysfs_create_group() / sysfs_remove_group() — toplu attribute yönetimi
  • sysfs_emit() — page-safe show callback çıktısı

06 Interrupt handling

Donanım interrupt'larını kernel'da ele almak; top half / bottom half ayrımı ve deferred work mekanizmaları.

Bir donanım interrupt oluştuğunda CPU mevcut işi bırakır ve interrupt handler'a atlar. Bu handler iki kısma ayrılır: top half (interrupt context, hızlı) ve bottom half (deferred, process context).

  Donanım IRQ tetikler
         │
         ▼
  ┌─────────────────────────────┐
  │  TOP HALF (interrupt ctx)   │  ← sleep yasak, hızlı ol
  │  irqreturn_t handler(...)   │
  │  • ACK interrupt            │
  │  • Veri oku (register)      │
  │  • tasklet/workqueue planla │
  └────────────┬────────────────┘
               │  schedule_work() / tasklet_schedule()
               ▼
  ┌─────────────────────────────┐
  │  BOTTOM HALF                │
  │  Tasklet: soft-IRQ ctx      │  ← sleep hala yasak
  │  Workqueue: process ctx     │  ← sleep yapılabilir
  └─────────────────────────────┘
    
irq_module.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/tasklet.h>

MODULE_LICENSE("GPL");

#define MY_IRQ  17  /* /proc/interrupts'dan doğru IRQ'yu bul */

static int              irq_count = 0;
static struct work_struct my_work;
static struct tasklet_struct my_tasklet;

/* Workqueue bottom half — process context, sleep yapılabilir */
static void my_work_handler(struct work_struct *work)
{
    pr_info("irq_module: workqueue — IRQ işleniyor, sayaç=%d\n", irq_count);
    /* Burada msleep(), mutex_lock(), kmalloc(GFP_KERNEL) yapılabilir */
}

/* Tasklet bottom half — soft-IRQ context, sleep YASAK */
static void my_tasklet_fn(unsigned long data)
{
    pr_info("irq_module: tasklet çalıştı, data=%lu\n", data);
}

/*
 * Top half — interrupt handler
 * IRQF_SHARED: IRQ başka sürücülerle paylaşılıyor
 * dev_id: shared IRQ'da hangi device olduğunu ayırt etmek için
 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
    /* Önce bu interrupt'ın bize ait olduğunu doğrula (shared IRQ'da kritik) */
    if (dev_id != &irq_count)
        return IRQ_NONE; /* bizim değil */

    irq_count++;

    /* Workqueue planla: process context'e ertele */
    schedule_work(&my_work);

    /* Ya da tasklet planla: soft-IRQ context'e ertele */
    tasklet_schedule(&my_tasklet);

    return IRQ_HANDLED;
}

static int __init irq_init(void)
{
    int ret;

    INIT_WORK(&my_work, my_work_handler);
    tasklet_init(&my_tasklet, my_tasklet_fn, 42UL);

    /*
     * request_irq(irq, handler, flags, name, dev_id)
     *   IRQF_SHARED : IRQ paylaşımına izin ver
     *   IRQF_TRIGGER_RISING : yükselen kenarda tetikle
     */
    ret = request_irq(MY_IRQ, my_irq_handler,
                       IRQF_SHARED | IRQF_TRIGGER_RISING,
                       "my_irq_module", &irq_count);
    if (ret) {
        pr_err("irq_module: IRQ %d istenemedi: %d\n", MY_IRQ, ret);
        return ret;
    }

    pr_info("irq_module: IRQ %d kaydedildi\n", MY_IRQ);
    return 0;
}

static void __exit irq_exit(void)
{
    /* Önce IRQ'yu serbest bırak — ardından workqueue'yu temizle */
    free_irq(MY_IRQ, &irq_count);
    tasklet_kill(&my_tasklet);
    cancel_work_sync(&my_work);
    pr_info("irq_module: kaldırıldı\n");
}

module_init(irq_init);
module_exit(irq_exit);

devm_request_irq ile otomatik cleanup

Platform driver'larda devm_request_irq() kullanılırsa driver kaldırılırken IRQ otomatik serbest bırakılır; free_irq() çağrısına gerek kalmaz:

platform_irq_snippet.c
/* probe() içinde — device kaldırılınca otomatik free_irq() */
ret = devm_request_irq(&pdev->dev, irq, my_irq_handler,
                        IRQF_TRIGGER_RISING, "mydev", priv);
if (ret)
    return dev_err_probe(&pdev->dev, ret, "IRQ istenemedi\n");
UYARI

Interrupt handler içinde (top half) asla sleep yapılamaz: msleep(), kmalloc(GFP_KERNEL), mutex_lock() gibi çağrılar kernel panic'e neden olur. Bu işlemleri workqueue'ya taşı.

Bu bölümde

  • request_irq() / free_irq() — interrupt kayıt ve serbest bırakma
  • IRQF_SHARED, IRQF_TRIGGER_RISING flag'leri
  • Top half: hızlı, interrupt context, sleep yasak
  • Tasklet: soft-IRQ bottom half; schedule_work() vs tasklet_schedule()
  • Workqueue: process context bottom half — sleep yapılabilir
  • devm_request_irq() — platform driver'larda otomatik cleanup

07 Mutex, spinlock ve senkronizasyon

Kernel'da paylaşılan verilere eşzamanlı erişimi önlemek için doğru primitive'i seçmek; yanlış seçim deadlock ya da veri bozulmasına yol açar.

Senkronizasyon primitifleri tablosu

PrimitiveContextSleep?Kullanım senaryosu
mutexprocess onlyEvetUzun süren kritik bölge, dosya işlemi
spinlockinterrupt dahil her yerHayırKısa kritik bölge, interrupt handler
rwlockinterrupt dahilHayırÇok okuyucu / az yazıcı, spinlock varyantı
semaphoreprocess onlyEvetKaynak sayımı (N eşzamanlı erişim)
atomic_ther yerHayırSayaç artırma/azaltma — lock gerektirmez
RCUher yer (okuma)Okuma hayırÇok okuyucu linked list, minimal overhead
sync_module.c
// SPDX-License-Identifier: GPL-2.0
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>

MODULE_LICENSE("GPL");

/* ─── MUTEX ─────────────────────────────────────────────── */
static DEFINE_MUTEX(my_mutex);
static int shared_data = 0;

static void mutex_demo(void)
{
    mutex_lock(&my_mutex);
    /* Kritik bölge — sadece bir thread buraya girebilir */
    shared_data++;
    pr_info("sync: shared_data = %d\n", shared_data);
    mutex_unlock(&my_mutex);
}

/* mutex_lock_interruptible: sinyal gelirse -EINTR döner — UI işlemleri için */
static int mutex_interruptible_demo(void)
{
    if (mutex_lock_interruptible(&my_mutex))
        return -ERESTARTSYS;
    shared_data += 10;
    mutex_unlock(&my_mutex);
    return 0;
}

/* ─── SPINLOCK ───────────────────────────────────────────── */
static DEFINE_SPINLOCK(my_spin);
static int spin_counter = 0;

static void spinlock_demo_irqsave(void)
{
    unsigned long flags;

    /* spin_lock_irqsave: yerel interrupt'ları devre dışı bırak + lock al
     * flags: interrupt durumunu sakla — restore için gerekli */
    spin_lock_irqsave(&my_spin, flags);
    spin_counter++;
    spin_unlock_irqrestore(&my_spin, flags);
}

/* Interrupt handler'da kullanım (process context'te değil) */
static irqreturn_t irq_with_spinlock(int irq, void *data)
{
    unsigned long flags;
    spin_lock_irqsave(&my_spin, flags);
    spin_counter++;
    spin_unlock_irqrestore(&my_spin, flags);
    return IRQ_HANDLED;
}

/* ─── ATOMIC ─────────────────────────────────────────────── */
static atomic_t my_counter = ATOMIC_INIT(0);

static void atomic_demo(void)
{
    atomic_inc(&my_counter);             /* ++counter */
    atomic_dec(&my_counter);             /* --counter */
    atomic_add(5, &my_counter);           /* counter += 5 */
    pr_info("sync: counter = %d\n", atomic_read(&my_counter));

    /* atomic_dec_and_test: 0'a düştüğünde true döner — referans sayımı için */
    if (atomic_dec_and_test(&my_counter))
        pr_info("sync: counter sıfıra ulaştı\n");
}

static int __init sync_init(void)
{
    mutex_demo();
    spinlock_demo_irqsave();
    atomic_demo();
    return 0;
}

static void __exit sync_exit(void) { }

module_init(sync_init);
module_exit(sync_exit);

RCU — Read-Copy-Update

RCU, okuma tarafının hiç lock almadan veri erişebildiği özel bir senkronizasyon mekanizmasıdır. Linked list gibi paylaşılan veri yapılarına yoğun okuma, nadiren yazma senaryolarında kullanılır:

rcu_snippet.c
/* RCU okuma tarafı — lock yok, son derece hızlı */
struct mynode *node;
rcu_read_lock();
node = rcu_dereference(global_node); /* güvenli pointer okuma */
if (node)
    pr_info("değer: %d\n", node->val);
rcu_read_unlock();

/* RCU yazma tarafı — eski pointer'ı güvenle yok et */
struct mynode *old_node, *new_node;
new_node = kmalloc(sizeof(*new_node), GFP_KERNEL);
new_node->val = 99;
old_node = rcu_replace_pointer(global_node, new_node, 1);
synchronize_rcu(); /* tüm okuyucuların bitirmesini bekle */
kfree(old_node);
DİKKAT

Mutex tutan bir thread spinlock almaya çalışırsa deadlock oluşabilir. Her zaman kilit sırası belirle ve tutarlı şekilde uygula. lockdep (CONFIG_LOCKDEP=y) bu sorunları runtime'da tespit eder ve kernel log'a raporlar.

Bu bölümde

  • mutex: process context, sleep yapabilir — uzun kritik bölgeler için
  • spinlock + irqsave: interrupt context dahil, sleep yok — kısa kritik bölgeler
  • atomic_t: lock-free sayaç; atomic_inc/dec/add/read/dec_and_test
  • RCU: okuma lock-free, yüksek performanslı linked list erişimi
  • lockdep: CONFIG_LOCKDEP ile runtime deadlock tespiti

08 Kernel debug araçları

Kernel bug'larını bulmak userspace'e göre çok daha zordur; doğru araçları bilmek saatleri dakikalara indirir.

pr_debug ve dynamic debug

pr_debug() varsayılan olarak derlenmez. CONFIG_DYNAMIC_DEBUG=y ile kernel'a dahil edilir ve runtime'da dosya/fonksiyon bazında açılıp kapatılabilir:

terminal — dynamic debug
# Belirli dosyada tüm pr_debug'ları aç
$ echo "file mymodule.c +p" | sudo tee /sys/kernel/debug/dynamic_debug/control

# Belirli modüldeki tüm debug'ları aç
$ echo "module mymodule +p" | sudo tee /sys/kernel/debug/dynamic_debug/control

# Belirli satırda debug aç
$ echo "file mymodule.c line 45 +p" | sudo tee /sys/kernel/debug/dynamic_debug/control

# Debug'u kapat
$ echo "module mymodule -p" | sudo tee /sys/kernel/debug/dynamic_debug/control

WARN_ON, BUG_ON ve dump_stack

debug_macros.c
/* WARN_ON: koşul doğruysa stack trace yaz, devam et */
WARN_ON(ptr == NULL);
WARN_ON_ONCE(size > MAX_SIZE); /* yalnızca bir kez uyar */

/* BUG_ON: koşul doğruysa kernel panic — kurtarılamaz hata */
BUG_ON(irqs_disabled());

/* dump_stack: mevcut call stack'i dmesg'e yaz */
dump_stack();

/* print_hex_dump: buffer içeriğini hex+ASCII formatında yaz */
print_hex_dump(KERN_INFO, "buf: ", DUMP_PREFIX_OFFSET,
               16, 1, kbuf, 64, true);
/* Çıktı örneği:
 * [  0.000] buf: 00000000: 48 65 6c 6c 6f 20 4b 65 72 6e 65 6c  Hello Kernel
 */

Ftrace — fonksiyon izleme

terminal — ftrace
# tracefs mount et (genellikle zaten bağlı)
$ mount -t tracefs tracefs /sys/kernel/debug/tracing

# Mevcut tracer'ları listele
$ cat /sys/kernel/debug/tracing/available_tracers
blk function_graph function nop

# Function tracer aktif et
$ echo function | sudo tee /sys/kernel/debug/tracing/current_tracer

# Belirli fonksiyonu filtrele
$ echo mydev_read | sudo tee /sys/kernel/debug/tracing/set_ftrace_filter

# Tracing başlat
$ echo 1 | sudo tee /sys/kernel/debug/tracing/tracing_on

# Sonuçları oku
$ cat /sys/kernel/debug/tracing/trace
# tracer: function
           <...>-1234  [001] ..... 142.001: mydev_read <-- vfs_read

# Tracing durdur
$ echo 0 | sudo tee /sys/kernel/debug/tracing/tracing_on

KASAN — Kernel Address Sanitizer

CONFIG_KASAN=y ile derlenen kernel, out-of-bounds erişim ve use-after-free hatalarını çalışma zamanında tespit eder. Gömülü sistemlerde test kernel'ı için idealdir:

terminal — KASAN raporu
# KASAN use-after-free tespit örneği (dmesg çıktısı)
[  45.123456] BUG: KASAN: use-after-free in mydev_read+0x4c/0x80
[  45.123460] Read of size 4 at addr ffff888012345678 by task cat/1234
[  45.123462]
[  45.123465] CPU: 0 PID: 1234 Comm: cat
[  45.123466] Call Trace:
[  45.123468]  mydev_read+0x4c/0x80
[  45.123469]  vfs_read+0xa0/0x1a0
[  45.123470]  ksys_read+0x6f/0xf0
[  45.123471]
[  45.123473] Freed by task 1233:
[  45.123474]  kfree+0x8e/0xc0
[  45.123475]  mydev_release+0x2c/0x40
CONFIG_KASANKernel Address Sanitizer — out-of-bounds ve use-after-free tespiti
CONFIG_KASAN_INLINEDaha hızlı ama daha büyük kernel — gömülü test için
CONFIG_UBSANUndefined Behavior Sanitizer — integer overflow, null deref
CONFIG_LOCKDEPDeadlock ve lock sırası ihlali tespiti
CONFIG_KCSANKernel Concurrency Sanitizer — data race tespiti

Bu bölümde

  • pr_debug() + CONFIG_DYNAMIC_DEBUG — runtime açılıp kapanan debug log
  • WARN_ON() stack trace + devam; BUG_ON() kernel panic
  • dump_stack() mevcut call stack; print_hex_dump() buffer görüntüleme
  • ftrace: function/function_graph tracer, set_ftrace_filter ile hedef seçimi
  • KASAN: out-of-bounds ve use-after-free — test kernel'ında olmazsa olmaz

09 devm_ resource management ve module temizliği

devm_ fonksiyon ailesi, kaynak yönetimini device yaşam döngüsüne bağlar ve hata yollarındaki karmaşık temizleme kodunu ortadan kaldırır.

devm_ (device-managed) fonksiyonları, tahsis edilen kaynağı bir struct device'a bağlar. Bu device kaldırıldığında (driver unbind veya remove çağrısında) kernel tahsis edilen tüm kaynakları otomatik serbest bırakır.

devm_ fonksiyon ailesi

devm_kmalloc()device kaldırılınca otomatik kfree() — elle serbest bırakmak zorunda değilsin
devm_kzalloc()devm_kmalloc() + sıfırlama — en sık kullanılan devm_ çifti
devm_request_irq()device kaldırılınca otomatik free_irq()
devm_ioremap_resource()resource al + ioremap — device kaldırılınca iounmap + release
devm_clk_get()clock handle — device kaldırılınca clk_put()
devm_regulator_get()regulator handle — device kaldırılınca regulator_put()
devm_gpiod_get()GPIO descriptor — device kaldırılınca gpiod_put()

devm_ olmadan: goto cleanup pattern

manual_cleanup.c — goto pattern
static int __init mymod_init(void)
{
    int ret;

    kbuf = kmalloc(BUF_SIZE, GFP_KERNEL);
    if (!kbuf) { ret = -ENOMEM; goto err_out; }

    ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
    if (ret) goto err_kfree;

    cdev_init(&my_cdev, &mydev_fops);
    ret = cdev_add(&my_cdev, devno, 1);
    if (ret) goto err_unreg;

    my_class = class_create(THIS_MODULE, "myclass");
    if (IS_ERR(my_class)) { ret = PTR_ERR(my_class); goto err_cdev; }

    return 0;

/* Ters sıra: son başarılı adımdan geriye doğru temizle */
err_cdev:   cdev_del(&my_cdev);
err_unreg:  unregister_chrdev_region(devno, 1);
err_kfree:  kfree(kbuf);
err_out:    return ret;
}

devm_ ile: temiz probe

devm_probe.c — platform driver probe
struct mypriv {
    void __iomem  *base;
    int            irq;
    struct clk    *clk;
};

static int mydev_probe(struct platform_device *pdev)
{
    struct mypriv  *priv;
    struct resource *res;
    int             irq, ret;

    /* Private struct — device kaldırılınca otomatik kfree */
    priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /* IO bellek — device kaldırılınca otomatik iounmap + release */
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    priv->base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(priv->base))
        return PTR_ERR(priv->base);

    /* Clock — device kaldırılınca otomatik clk_put */
    priv->clk = devm_clk_get(&pdev->dev, "apb_pclk");
    if (IS_ERR(priv->clk))
        return PTR_ERR(priv->clk);

    ret = clk_prepare_enable(priv->clk);
    if (ret)
        return ret;

    /* IRQ — device kaldırılınca otomatik free_irq */
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;
    priv->irq = irq;

    ret = devm_request_irq(&pdev->dev, irq, mydev_irq_handler,
                            IRQF_TRIGGER_RISING, "mydev", priv);
    if (ret)
        return ret;

    platform_set_drvdata(pdev, priv);
    dev_info(&pdev->dev, "mydev probe başarılı, IRQ=%d\n", irq);
    return 0;
    /* goto yok, cleanup kodu yok — devm_ halletti */
}

/* remove neredeyse boş: devm_ her şeyi otomatik temizler */
static int mydev_remove(struct platform_device *pdev)
{
    struct mypriv *priv = platform_get_drvdata(pdev);
    clk_disable_unprepare(priv->clk);
    dev_info(&pdev->dev, "mydev kaldırıldı\n");
    return 0;
}

Manuel cleanup'ta doğru sıra

devm_ kullanılmayan durumlarda module_exit() içinde kaynaklar ters sırada serbest bırakılmalıdır:

  Yükleme sırası         │  Kaldırma sırası (ters)
  ───────────────────────┼───────────────────────────
  1. kmalloc             │  6. kfree
  2. alloc_chrdev_region │  5. unregister_chrdev_region
  3. cdev_add            │  4. cdev_del
  4. class_create        │  3. class_destroy
  5. device_create       │  2. device_destroy
  6. request_irq         │  1. free_irq
    
NOT

devm_ fonksiyonlarını kullanmak, probe/remove kodunu dramatik şekilde basitleştirir. Özellikle hata yollarındaki goto zincirini ortadan kaldırır ve kaynak sızıntısı riskini minimuma indirir. Yeni yazılan her platform driver'ında devm_ tercih edilmelidir.

Bu bölümde

  • devm_ ailesi: device yaşam döngüsüne bağlı otomatik cleanup
  • devm_kzalloc, devm_ioremap_resource, devm_request_irq, devm_clk_get
  • goto cleanup pattern: devm_ olmadan hata yönetiminin doğru yapısı
  • module_exit() cleanup sırası: yüklemenin tersi — son yüklenen ilk serbest bırakılır
  • devm_ kullanan driver'da remove() neredeyse boş kalır