00 Ertelenmiş İş Neden Gerekli
Linux çekirdeği, interrupt işlemeyi iki yarıya böler: üst yarı (hardirq) donanımı hızlıca susturup kritik verileri alır; alt yarı gerçek işi daha sonra, daha az kısıtlı bir bağlamda yapar.
Interrupt Context Kısıtları
Bir IRQ handler yürütülürken CPU şu kısıtlar altındadır:
schedule(), mutex_lock(), down(), wait_event() — tümü çekirdek içinde uyumaya yol açar ve interrupt bağlamında çağrılması BUG üretir.GFP_KERNEL alloc uyuyabilir. Interrupt bağlamında yalnızca GFP_ATOMIC kullanılabilir; bu da başarısız olabilir.Neden Üst/Alt Yarı Ayrımı
Donanım Kesme
│
▼
ÜST YARI (hardirq) ALT YARI (deferred work)
┌─────────────────────┐ ┌──────────────────────────┐
│ • Donanımı acknowledge│ │ • Gerçek veri işleme │
│ • Veriyi ring buffer │ │ • Protokol stack işleme │
│ veya FIFO'ya koy │ │ • I2C/SPI okuma │
│ • Zamanlayıcıyı │ │ • kmalloc(GFP_KERNEL) │
│ başlat/signal ver │ │ • mutex/semaphore alma │
│ • <10 µs hedef │ │ • user-space uyanıklık │
└─────────────────────┘ └──────────────────────────┘
▲
┌──────────────┼──────────────┐
tasklet workqueue kthread
(softirq) (process ctx) (sürekli döngü)
Mekanizma Seçim Rehberi
| Senaryo | Önerilen Mekanizma | Neden |
|---|---|---|
| IRQ'dan kısa, non-blocking iş | tasklet | Düşük overhead, softirq bağlamı |
| IRQ'dan blocking I/O (I2C, SPI) | workqueue veya threaded IRQ | Process bağlamı, uyuyabilir |
| Belirli bir CPU'da sıralı iş | ordered workqueue | Tek worker, FIFO garantisi |
| Gecikmeli (timer-based) iş | delayed_work | schedule_delayed_work() ile ms hassasiyeti |
| Sürekli çalışan polling döngüsü | kthread | Bağımsız thread, tam kontrol |
| Periyodik RT iş | kthread + hrtimer | Yüksek çözünürlüklü uyanış |
01 Workqueue Türleri
Linux 2.6.36 ile gelen cmwq (concurrency-managed workqueue) önceki API'yı yeniden tasarladı. Günümüzde sürücüler genellikle sistem geneli workqueue'leri kullanır; özel gereksinimler için alloc_workqueue() ile yeni workqueue oluşturulur.
Sistem Geneli Workqueue'ler
schedule_work() bu workqueue'yi kullanır.queue_work(system_highpri_wq, w)alloc_workqueue ile Özel Workqueue
#include <linux/workqueue.h>
/**
* alloc_workqueue - yeni workqueue oluştur
*
* @fmt: İsim formatı (printf stili) — worker thread ismi için kullanılır
* @flags: WQ_* bayrakları (aşağıda açıklanır)
* @max_active: Aynı anda çalışabilecek maksimum work sayısı (0 = varsayılan)
*
* Başarıda workqueue pointer, başarısızda NULL döner.
*/
struct workqueue_struct *alloc_workqueue(const char *fmt,
unsigned int flags,
int max_active, ...);
/* Örnek: sürücü başına özel workqueue */
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv = /* ... */;
/* Tek worker, sıralı çalışma garantisi */
priv->wq = alloc_ordered_workqueue("mydrv/%s",
WQ_MEM_RECLAIM,
dev_name(&pdev->dev));
if (!priv->wq)
return -ENOMEM;
/* ... */
return 0;
}
static void mydrv_remove(struct platform_device *pdev)
{
struct mydrv_priv *priv = platform_get_drvdata(pdev);
destroy_workqueue(priv->wq); /* bekler, sonra serbest bırakır */
}
WQ_* Bayrakları
| Bayrak | Anlam | Ne Zaman Kullanılır |
|---|---|---|
| WQ_UNBOUND | Worker thread'leri CPU'ya bağlı değil | Uzun hesaplama, NUMA farkındalığı gerekmeyen iş |
| WQ_MEM_RECLAIM | Bellek baskısında bile çalışmaya devam eder | Blok sürücüler, swap path — ölümcül deadlock'u önler |
| WQ_HIGHPRI | Yüksek öncelikli worker thread'leri | Düşük gecikme, CPU-bağlı olmayan kritik yol |
| WQ_CPU_INTENSIVE | Hesaplama yoğun — scheduler'a ipucu | Şifreleme, sıkıştırma işleri |
| WQ_FREEZABLE | Sistem askıya alındığında dondurulur | Güç yönetimi ile entegre çalışan sürücüler |
| WQ_SYSFS | sysfs altında görünür olur | Debug / tuning için workqueue'yi expose etmek |
alloc_ordered_workqueue
/*
* alloc_ordered_workqueue — max_active=1 olan WQ_UNBOUND workqueue kısayolu.
* İşler sıraya girdiği sırayla çalışır (FIFO).
* Aynı anda yalnızca bir work yürütülür.
*
* Ne zaman kullanılır:
* - Sıralama önemli (örn. DMA transfer zinciri)
* - Mutex yerine sıralı workqueue ile eşzamanlılığı önlemek
* - Tek bir kaynak üzerinde sıralı güvenli erişim
*/
priv->ordered_wq = alloc_ordered_workqueue("mydrv-ordered", WQ_MEM_RECLAIM);
02 INIT_WORK, queue_work, cancel_work_sync
Workqueue kullanımının temel döngüsü: struct work_struct'ı başlat, sırayı tetikle, sürücü kaldırılırken güvenle iptal et.
struct work_struct ve Başlatma
#include <linux/workqueue.h>
/* Statik tanımlama (modül seviyesi veya global) */
static DECLARE_WORK(my_work, my_work_handler);
/* Dinamik tanımlama (struct içinde) */
struct mydrv_priv {
struct workqueue_struct *wq;
struct work_struct work;
/* work handler'ın ihtiyaç duyduğu veriler */
spinlock_t lock;
u32 pending_data;
};
/* probe'da başlat */
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
INIT_WORK(&priv->work, mydrv_work_fn);
priv->wq = alloc_ordered_workqueue("mydrv", WQ_MEM_RECLAIM);
if (!priv->wq)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
return 0;
}
Work Handler Yazımı
/*
* Work handler: process bağlamında çalışır.
* container_of ile çevreleyen struct'a erişilir.
*/
static void mydrv_work_fn(struct work_struct *work)
{
struct mydrv_priv *priv =
container_of(work, struct mydrv_priv, work);
unsigned long flags;
u32 data;
/* Spinlock ile IRQ-safe veri al */
spin_lock_irqsave(&priv->lock, flags);
data = priv->pending_data;
priv->pending_data = 0;
spin_unlock_irqrestore(&priv->lock, flags);
/* Artık process bağlamındayız — her şey mümkün */
if (data & DATA_FLAG_I2C) {
/* I2C okuma — uyuyabilir */
mydrv_i2c_transfer(priv, data);
}
if (data & DATA_FLAG_ALLOC) {
/* Güvenli bellek tahsisi */
void *buf = kmalloc(4096, GFP_KERNEL);
if (buf) {
process_buffer(priv, buf);
kfree(buf);
}
}
/* Yeni work queue edebilir: kendini yeniden zamanla */
if (priv->needs_retry)
queue_work(priv->wq, &priv->work);
}
queue_work — IRQ Handler'dan Tetikleme
static irqreturn_t mydrv_irq(int irq, void *dev_id)
{
struct mydrv_priv *priv = dev_id;
u32 status;
status = readl(priv->base + STATUS_REG);
if (!status)
return IRQ_NONE;
writel(status, priv->base + CLEAR_REG);
/* Veriyi IRQ-safe biçimde aktar */
spin_lock(&priv->lock); /* IRQ bağlamında, irqsave gerekmiyor */
priv->pending_data |= status;
spin_unlock(&priv->lock);
/*
* queue_work: eğer work zaten sıradaysa veya çalışıyorsa
* yeniden kuyruğa alınmaz — güvenli tekrar çağrılabilir.
* Dönüş değeri: true = yeni kuyruğa alındı, false = zaten vardı
*/
queue_work(priv->wq, &priv->work);
return IRQ_HANDLED;
}
/* system_wq kısayolu: özel workqueue oluşturmak gerekmiyorsa */
schedule_work(&priv->work); /* queue_work(system_wq, ...) eşdeğeri */
Güvenli Temizlik — cancel_work_sync
static void mydrv_remove(struct platform_device *pdev)
{
struct mydrv_priv *priv = platform_get_drvdata(pdev);
/*
* cancel_work_sync:
* 1. Work sıradaysa sıradan çıkar
* 2. Work çalışıyorsa bitmesini bekler
* 3. Sonra döner — handler artık çalışmıyor, priv güvenle serbest
*
* DEV DRVDATA SIFIRLANMADAN ÖNCE ÇAĞIRILMALI!
* Aksi hâlde work handler serbest bırakılmış priv'e erişebilir.
*/
cancel_work_sync(&priv->work);
destroy_workqueue(priv->wq);
/* devm ile tahsis edilen kaynaklar otomatik serbest bırakılır */
}
flush_work ve flush_workqueue
/*
* flush_work: belirli bir work'ün bitmesini bekler (iptal etmez)
* Örnek: konfigürasyon değişikliği sonrası pending work'ün tamamlanmasını bekle
*/
flush_work(&priv->work);
/*
* flush_workqueue: tüm workqueue'deki mevcut work'lerin bitmesini bekler
* Yeni eklenenler beklenmez.
*/
flush_workqueue(priv->wq);
03 INIT_DELAYED_WORK ve schedule_delayed_work
Delayed work, normal work'e bir timer gecikme ekler. Donanım stabilizasyon süreleri, yeniden deneme mantığı ve periyodik polling için yaygın kullanılır.
struct delayed_work ve Başlatma
#include <linux/workqueue.h>
struct mydrv_priv {
struct workqueue_struct *wq;
struct delayed_work dwork;
unsigned int retry_count;
};
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv = /* ... */;
INIT_DELAYED_WORK(&priv->dwork, mydrv_dwork_fn);
priv->wq = alloc_workqueue("mydrv", WQ_MEM_RECLAIM, 0);
if (!priv->wq)
return -ENOMEM;
return 0;
}
schedule_delayed_work API
/* msecs_to_jiffies ile ms cinsinden gecikme */
#define STABILIZE_MS 50 /* donanım stabilizasyon süresi */
/* İlk zamanlama */
queue_delayed_work(priv->wq, &priv->dwork,
msecs_to_jiffies(STABILIZE_MS));
/* system_wq kısayolu */
schedule_delayed_work(&priv->dwork, msecs_to_jiffies(STABILIZE_MS));
/* Belirli bir CPU'da çalıştır */
queue_delayed_work_on(cpu, priv->wq, &priv->dwork,
msecs_to_jiffies(STABILIZE_MS));
Gerçek Kullanım: Yeniden Deneme Mantığı
#define RETRY_DELAY_MS 100
#define MAX_RETRIES 5
static void mydrv_dwork_fn(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct mydrv_priv *priv =
container_of(dwork, struct mydrv_priv, dwork);
int ret;
ret = mydrv_hw_init(priv);
if (ret == 0) {
dev_info(&priv->dev, "Donanım başarıyla başlatıldı\n");
priv->retry_count = 0;
return;
}
priv->retry_count++;
if (priv->retry_count >= MAX_RETRIES) {
dev_err(&priv->dev, "Donanım başlatılamadı (%d deneme)\n",
priv->retry_count);
return;
}
dev_warn(&priv->dev, "Yeniden deneniyor (%d/%d)...\n",
priv->retry_count, MAX_RETRIES);
/* Üstel geri çekilme */
queue_delayed_work(priv->wq, &priv->dwork,
msecs_to_jiffies(RETRY_DELAY_MS * (1 << priv->retry_count)));
}
Gerçek Kullanım: GPIO Debounce
/* GPIO IRQ'dan sonra donanımı tekrar oku (debounce) */
#define DEBOUNCE_MS 20
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
struct gpio_priv *priv = dev_id;
/*
* GPIO hattı kararlı değil — hemen okuma güvenilmez.
* 20 ms sonra okumayı ertele.
*/
mod_delayed_work(priv->wq, &priv->dwork,
msecs_to_jiffies(DEBOUNCE_MS));
return IRQ_HANDLED;
}
static void gpio_dwork_fn(struct work_struct *work)
{
struct delayed_work *dwork = to_delayed_work(work);
struct gpio_priv *priv = container_of(dwork, struct gpio_priv, dwork);
int val;
val = gpiod_get_value_cansleep(priv->gpio);
/* Kararlı değeri işle */
input_report_key(priv->input, KEY_ENTER, val);
input_sync(priv->input);
}
Temizlik
static void mydrv_remove(struct platform_device *pdev)
{
struct mydrv_priv *priv = platform_get_drvdata(pdev);
/*
* cancel_delayed_work_sync:
* - Zamanlayıcı beklemedeyse iptal et
* - Zaten kuyruktaysa veya çalışıyorsa bitene kadar bekle
*/
cancel_delayed_work_sync(&priv->dwork);
destroy_workqueue(priv->wq);
}
04 kthread_run, kthread_stop, kthread_should_stop
Kernel thread'leri, workqueue'den farklı olarak bağımsız çalışma döngüsü gerektiren senaryolar için kullanılır: sürekli polling, producer/consumer kuyrukları ve RT görevleri.
Temel kthread API
#include <linux/kthread.h>
/**
* kthread_run - yeni kernel thread oluştur ve başlat
*
* @threadfn: Thread fonksiyonu — int döndürür
* @data: threadfn'e aktarılan opak pointer
* @namefmt: Thread ismi (ps çıktısında görünür, printf formatı)
*
* Başarıda task_struct pointer, hata durumunda ERR_PTR döner.
*/
struct task_struct *kthread_run(int (*threadfn)(void *data),
void *data,
const char namefmt[], ...);
/**
* kthread_stop - thread'i durdur ve tamamlanmasını bekle
*
* kthread_should_stop()'u true yapar, thread uyanıksa uyandırır,
* sonra thread'in sonlanmasını bekler.
* Dönüş: threadfn'in dönüş değeri.
*/
int kthread_stop(struct task_struct *k);
/**
* kthread_should_stop - thread durma sinyali var mı?
*
* Thread döngüsünde düzenli kontrol edilmeli.
* kthread_stop() çağrıldığında true döner.
*/
bool kthread_should_stop(void);
/**
* kthread_freezable_should_stop - suspend/freeze kontrolü de yapar
* Güç yönetimi ile entegre thread'ler için tercih edilmeli.
*/
bool kthread_freezable_should_stop(bool *was_frozen);
Tipik kthread Döngüsü
struct mydrv_priv {
struct task_struct *kthread;
wait_queue_head_t wq; /* thread'i uyandırmak için */
atomic_t work_pending; /* yeni iş var mı? */
};
static int mydrv_thread_fn(void *data)
{
struct mydrv_priv *priv = data;
/* Thread önceliğini ayarla — RT kritik ise */
/* sched_set_fifo(current); -- SCHED_FIFO, öncelik 50 */
while (!kthread_should_stop()) {
/*
* Yeni iş bekle — CPU boşa harcama.
* wait_event_interruptible ile sinyal alabilir.
*/
wait_event_interruptible(priv->wq,
atomic_read(&priv->work_pending) ||
kthread_should_stop());
if (kthread_should_stop())
break;
atomic_set(&priv->work_pending, 0);
/* Asıl işi yap — uyuyabilir, blocking I/O yapabilir */
mydrv_process_data(priv);
}
return 0;
}
/* IRQ handler'dan thread'i uyandır */
static irqreturn_t mydrv_irq(int irq, void *dev_id)
{
struct mydrv_priv *priv = dev_id;
/* Hızlı acknowledge */
writel(IRQ_CLEAR, priv->base + CLEAR_REG);
/* Thread'i uyandır */
atomic_set(&priv->work_pending, 1);
wake_up_interruptible(&priv->wq);
return IRQ_HANDLED;
}
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv = /* ... */;
init_waitqueue_head(&priv->wq);
atomic_set(&priv->work_pending, 0);
priv->kthread = kthread_run(mydrv_thread_fn, priv, "mydrv/%s",
dev_name(&pdev->dev));
if (IS_ERR(priv->kthread)) {
dev_err(&pdev->dev, "kthread oluşturulamadı\n");
return PTR_ERR(priv->kthread);
}
return 0;
}
static void mydrv_remove(struct platform_device *pdev)
{
struct mydrv_priv *priv = platform_get_drvdata(pdev);
/*
* kthread_stop: should_stop bayrağını set et,
* eğer wait_event'te bekliyorsa uyandır,
* thread tamamlanana kadar bekle.
*/
kthread_stop(priv->kthread);
}
kthread Öncelik Ayarı
#include <linux/sched.h>
#include <uapi/linux/sched/types.h>
static int rt_thread_fn(void *data)
{
/* Kernel içinden SCHED_FIFO öncelik 80 ver */
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_FIFO,
.sched_priority = 80,
};
sched_setattr_nocheck(current, &attr);
/* Belirli bir CPU'ya bağla */
set_cpus_allowed_ptr(current, cpumask_of(2));
while (!kthread_should_stop()) {
/* RT görev döngüsü */
do_rt_work(data);
/* Bir sonraki periyoda kadar uyu */
schedule_timeout_interruptible(usecs_to_jiffies(1000)); /* 1ms */
}
return 0;
}
05 kthread_worker API
kthread_worker, tek bir kernel thread etrafında iş kuyruğu soyutlaması sunar. Workqueue ile kthread arasında bir köprüdür: sıralı çalışma garantisi, düşük overhead ve tam thread kontrolü bir arada.
kthread_worker Yapısı
kthread_worker
├─ worker_list: kthread_work listesi
├─ task: worker kernel thread
└─ lock: liste koruma spinlock
kthread_work (her iş birimi)
├─ node: worker_list'teki bağlı liste düğümü
├─ func: yürütülecek fonksiyon
└─ worker: bağlı olduğu kthread_worker
API Akışı:
kthread_init_worker() → worker oluştur
kthread_init_work() → iş birimi tanımla
kthread_queue_work() → kuyruğa ekle
worker thread → sırayla çalıştırır
kthread_flush_work() → bitmesini bekle
kthread_cancel_work_sync() → iptal et
Tam Kullanım Örneği
#include <linux/kthread.h>
struct mydrv_priv {
struct kthread_worker worker;
struct task_struct *worker_task;
struct kthread_work work;
struct kthread_work cleanup_work;
};
static void mydrv_kwork_fn(struct kthread_work *work)
{
struct mydrv_priv *priv =
container_of(work, struct mydrv_priv, work);
/* Process bağlamı — uyuyabilir */
mydrv_process(priv);
}
static void mydrv_cleanup_fn(struct kthread_work *work)
{
struct mydrv_priv *priv =
container_of(work, struct mydrv_priv, cleanup_work);
mydrv_hw_cleanup(priv);
}
static int mydrv_probe(struct platform_device *pdev)
{
struct mydrv_priv *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
/* Worker'ı başlat — henüz thread yok */
kthread_init_worker(&priv->worker);
/* İş birimlerini tanımla */
kthread_init_work(&priv->work, mydrv_kwork_fn);
kthread_init_work(&priv->cleanup_work, mydrv_cleanup_fn);
/* Worker thread'i oluştur ve başlat */
priv->worker_task = kthread_run(kthread_worker_fn,
&priv->worker,
"mydrv-worker/%s",
dev_name(&pdev->dev));
if (IS_ERR(priv->worker_task))
return PTR_ERR(priv->worker_task);
platform_set_drvdata(pdev, priv);
return 0;
}
/* İş kuyruğa alma — IRQ handler'dan veya başka yerden */
static irqreturn_t mydrv_irq(int irq, void *dev_id)
{
struct mydrv_priv *priv = dev_id;
kthread_queue_work(&priv->worker, &priv->work);
return IRQ_HANDLED;
}
static void mydrv_remove(struct platform_device *pdev)
{
struct mydrv_priv *priv = platform_get_drvdata(pdev);
/* Cleanup work kuyruğa al ve bekle */
kthread_queue_work(&priv->worker, &priv->cleanup_work);
kthread_flush_worker(&priv->worker);
/* Worker thread'i durdur */
kthread_stop(priv->worker_task);
}
kthread_worker vs workqueue Karşılaştırması
| Kriter | kthread_worker | workqueue |
|---|---|---|
| Thread sayısı | Tam kontrol (1 thread) | cmwq yönetir (değişken) |
| Öncelik ayarı | Doğrudan (sched_setattr) | WQ_HIGHPRI bayrağı ile sınırlı |
| CPU affinity | set_cpus_allowed_ptr | WQ_UNBOUND veya affinity_scope |
| Sıralı çalışma | Garantili (tek thread) | ordered_workqueue gerektirir |
| Overhead | Düşük | Biraz daha yüksek (pool yönetimi) |
| Önerilen kullanım | RT, sıralı, özel öncelik | Genel sürücü ertelenmiş iş |
06 tasklet — Eski API
Tasklet'ler softirq üzerine inşa edilmiş, eski Linux sürücülerinde yaygın kullanılan bir erteleme mekanizmasıdır. Yeni kodda workqueue tercih edilmelidir; ancak eski sürücüleri okumak ve bakımını yapmak için tam olarak anlaşılmalıdır.
tasklet Çalışma Modeli
tasklet API
#include <linux/interrupt.h>
/* ---- Başlatma ---- */
/* Statik: modül yükleme anında hazır */
DECLARE_TASKLET(my_tasklet, my_tasklet_handler);
DECLARE_TASKLET_DISABLED(my_tasklet, my_tasklet_handler); /* devre dışı başlar */
/* Dinamik (struct içinde): */
struct mydrv_priv {
struct tasklet_struct tasklet;
unsigned long tasklet_data;
};
/* probe'da: */
tasklet_init(&priv->tasklet, mydrv_tasklet_fn, (unsigned long)priv);
/* ---- Handler ---- */
static void mydrv_tasklet_fn(unsigned long data)
{
struct mydrv_priv *priv = (struct mydrv_priv *)data;
/* Softirq bağlamı: uyuma yok, GFP_ATOMIC gerekli */
process_data(priv);
}
/* ---- Zamanlama ---- */
/* IRQ handler'dan: */
tasklet_schedule(&priv->tasklet); /* TASKLET_SOFTIRQ */
tasklet_hi_schedule(&priv->tasklet); /* HI_SOFTIRQ — yüksek öncelik */
/* ---- Kontrol ---- */
tasklet_disable(&priv->tasklet); /* çalışıyorsa bitmesini bekle, sonra devre dışı */
tasklet_enable(&priv->tasklet); /* yeniden etkinleştir */
tasklet_kill(&priv->tasklet); /* çalışıyorsa bekle, pending iptal et */
Yeni Kodda Kaçınma Nedenleri
| Sorun | Açıklama | Workqueue Avantajı |
|---|---|---|
| Uyuma yasak | Tüm blocking işlemler imkânsız | Workqueue'de GFP_KERNEL, mutex, I2C serbest |
| PREEMPT_RT uyumsuzluğu | RT yamalarında tasklet sorunlu | Workqueue RT uyumlu |
| Hata ayıklama zorluğu | Softirq bağlamı stack trace karmaşık | Normal process bağlamı — kolay debug |
| Deprecation eğilimi | Kernel geliştiricileri yeni API önermiyor | Aktif geliştirme ve bakım var |
tasklet → workqueue Geçişi
/* ESKİ KOD (tasklet) */
static void old_tasklet_fn(unsigned long data)
{
struct mydrv_priv *priv = (struct mydrv_priv *)data;
do_work_no_sleep(priv);
}
tasklet_schedule(&priv->tasklet);
/* YENİ KOD (workqueue) — sadece birkaç satır fark */
static void new_work_fn(struct work_struct *work)
{
struct mydrv_priv *priv =
container_of(work, struct mydrv_priv, work);
do_work_with_sleep(priv); /* artık uyuyabilir */
}
queue_work(priv->wq, &priv->work);
07 cmwq İç Mimarisi
Concurrency-Managed Workqueue (cmwq), Linux 2.6.36'da geleneksel workqueue'nin yerini aldı. Temel yeniliği, CPU başına sabit worker thread yerine dinamik thread havuzu yönetimidir.
cmwq Öncesi Sorunlar
cmwq Çalışma Prensibi
workqueue (soyut kuyruk)
│
│ queue_work()
▼
worker_pool (CPU başına veya unbound)
├─ worker thread havuzu (dinamik boyut)
├─ pending work listesi
├─ running count (şu an çalışan work sayısı)
└─ idle worker listesi
Kural: running_count < 1 iken idle worker uyandırılır.
running_count >= 1 iken yeni worker oluşturulmaz
(mevcut yeterli concurrent work sağlıyor).
Eğer tek work çalışıyor ve uyursa:
running_count 0'a düşer
→ idle worker uyandırılır veya yeni thread oluşturulur
→ diğer work'ler devam eder
Bu sayede:
• Bloke olan work diğerlerini engellemez
• CPU sayısı kadar minimum thread
• Yoğun dönemde geçici ek worker
Worker Pool Tipleri
| Pool Tipi | Özellik | Kullanılan Bayrak |
|---|---|---|
| per-CPU bound | Her CPU için ayrı pool, work o CPU'da çalışır | Varsayılan (bayrak yok) |
| per-CPU highpri | Yüksek öncelikli per-CPU pool | WQ_HIGHPRI |
| unbound | CPU affinity yok, NUMA aware | WQ_UNBOUND |
| unbound highpri | Unbound + yüksek öncelik | WQ_UNBOUND | WQ_HIGHPRI |
| ordered | max_active=1, FIFO garantisi | alloc_ordered_workqueue() |
Parametre Ayarlama
/* Workqueue'nun maksimum eşzamanlı work sayısını ayarla */
/* alloc_workqueue'nin 3. parametresi: max_active */
priv->wq = alloc_workqueue("mydrv", WQ_UNBOUND | WQ_MEM_RECLAIM,
4); /* aynı anda max 4 work */
/* Sysfs üzerinden çalışma zamanında izleme (WQ_SYSFS ile) */
priv->wq = alloc_workqueue("mydrv", WQ_UNBOUND | WQ_SYSFS, 0);
/* /sys/bus/workqueue/devices/mydrv/ altında görünür */
/* Pool istatistikleri */
/* cat /sys/kernel/debug/workqueue -- kernel debug build'de */
08 Hata Ayıklama
Workqueue hataları genellikle sessizdir: ölü kilit, hung worker, yanlış sıralama. Bu araçlar sorunları erken tespit eder.
Hung Task Dedektörü
/*
* CONFIG_DETECT_HUNG_TASK=y
*
* Kernel, TASK_UNINTERRUPTIBLE durumunda kalan thread'leri izler.
* Varsayılan eşik: 120 saniye (kernel.hung_task_timeout_secs)
*
* Tetiklendiğinde dmesg'de:
* kernel: INFO: task mydrv-worker:1234 blocked for more than 120 seconds.
* kernel: "echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
* kernel: task:mydrv-worker state:D stack:0 pid:1234 ...
* kernel: Call Trace:
* kernel: __schedule+0x...
* kernel: schedule+0x...
* kernel: mutex_lock+0x...
* kernel: mydrv_work_fn+0x...
*/
/* Eşiği düşür (debug için) */
echo 30 > /proc/sys/kernel/hung_task_timeout_secs
/* Hung task panic: ürün sistem için tehlikeli, sadece test'te */
echo 1 > /proc/sys/kernel/hung_task_panic
ftrace workqueue Events
# workqueue event'lerini etkinleştir
echo 1 > /sys/kernel/debug/tracing/events/workqueue/enable
# Trace başlat
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ... test senaryosunu çalıştır ...
echo 0 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace
# Örnek çıktı:
# kworker/2:1-42 [002] 1234.567890: workqueue_queue_work: work struct=0xffff... function=mydrv_work_fn workqueue=0xffff... req_cpu=2 cpu=2
# kworker/2:1-42 [002] 1234.567891: workqueue_execute_start: work struct=0xffff... function=mydrv_work_fn
# kworker/2:1-42 [002] 1234.567950: workqueue_execute_end: work struct=0xffff... function=mydrv_work_fn
# Sadece belirli workqueue filtrele
echo 'function == mydrv_work_fn' > /sys/kernel/debug/tracing/events/workqueue/workqueue_execute_start/filter
lockdep ile Deadlock Tespiti
/*
* Yaygın hata: work handler'dan flush_workqueue() veya cancel_work_sync()
* çağırmak — deadlock!
*
* Örnek:
* work_fn() çalışıyor
* → flush_workqueue(wq) çağrılıyor
* → tüm work'lerin bitmesini bekliyor
* → work_fn() bitmesi için kendisinin bitmesini bekliyor
* → Deadlock!
*
* CONFIG_LOCKDEP=y ile kernel bu durumu tespit eder:
*
* WARNING: possible circular locking dependency detected
* kworker/u8:2/1234 is trying to acquire lock:
* ((&wq->completion)){+.+.}, ...
* but task is already holding lock:
* ((&work->work_func))
*
* Çözüm: queue_work kullan (senkron bekleme yapma)
* veya farklı workqueue'ye flush et
*/
/proc/PID/wchan — Nerede Bekliyor
# Tüm kworker thread'lerinin bekleme noktasını göster
for pid in $(ps -eo pid,comm | grep kworker | awk '{print $1}'); do
comm=$(cat /proc/$pid/comm 2>/dev/null)
wchan=$(cat /proc/$pid/wchan 2>/dev/null)
echo "$comm ($pid): $wchan"
done
# Örnek çıktı:
# kworker/0:1 (45): worker_thread
# kworker/u8:2 (89): worker_thread
# mydrv-worker (1234): mutex_lock ← burada bekliyor!
Workqueue Dump — Kernel Panic Sonrası
/* CONFIG_WQ_WATCHDOG=y — workqueue watchdog
*
* Bir worker thread belirli süre çalışmaya devam ederse:
* BUG: workqueue lockup - pool cpus=2 node=0 flags=0x0 nice=0 stuck for 30s!
* Showing busy workqueues and worker pools:
* workqueue mydrv: flags=0xa0
* pwq 4: cpus=2 node=0 flags=0x0 nice=0 active=1/256 refcnt=2
* in-flight: 1234:mydrv_work_fn
* pending: (diğer work listesi)
*/
/* Watchdog zaman aşımı ayarı (saniye, 0=devre dışı) */
/* kernel cmdline: workqueue.watchdog_thresh=30 */