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ğeri | Anlamı | 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
Neden kernel module yazılır
Kernel module geliştirmek gereken yaygın durumlar şunlardır:
Kullanıcı alanı (userspace)
┌─────────────────────────────────────────┐
│ ls, cat, custom_app, python_script... │
└────────────────────┬────────────────────┘
│ syscall (read/write/ioctl/...)
┌────────────────────▼────────────────────┐
│ Kernel space │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ built-in │ │ hello.ko (LKM) │ │
│ │ drivers │ │ → insmod / modprobe │ │
│ └──────────┘ └──────────────────────┘ │
└─────────────────────────────────────────┘
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.
// 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:
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ı
# 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!
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.
// 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
# 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
# modprobe param_module çalıştırıldığında bu parametreler otomatik uygulanır
options param_module debug=1 count=10 speed=200
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
// 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ı
| Özellik | kmalloc | vmalloc |
|---|---|---|
| Fiziksel süreklilik | Garantili | Garantisiz |
| Sanal süreklilik | Garantili | Garantili |
| DMA kullanımı | Uygun | Uygun değil |
| Maksimum boyut | ~4 MB (arch'a göre) | Sanal adres alanıyla sınırlı |
| Hız | Hızlı | Daha yavaş (TLB) |
| Serbest bırakma | kfree() | 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:
# 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
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
// 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
$ 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
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ı
// 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
$ 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
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
└─────────────────────────────┘
// 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:
/* 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");
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
| Primitive | Context | Sleep? | Kullanım senaryosu |
|---|---|---|---|
| mutex | process only | Evet | Uzun süren kritik bölge, dosya işlemi |
| spinlock | interrupt dahil her yer | Hayır | Kısa kritik bölge, interrupt handler |
| rwlock | interrupt dahil | Hayır | Çok okuyucu / az yazıcı, spinlock varyantı |
| semaphore | process only | Evet | Kaynak sayımı (N eşzamanlı erişim) |
| atomic_t | her yer | Hayır | Sayaç artırma/azaltma — lock gerektirmez |
| RCU | her yer (okuma) | Okuma hayır | Çok okuyucu linked list, minimal overhead |
// 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 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);
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:
# 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
/* 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
# 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:
# 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
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_ olmadan: goto cleanup 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
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
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