Tüm eğitimler
TEKNİK REHBER GÖMÜLÜ LİNUX WORKQUEUE & KTHREAD 2026

workqueue & kthread
Kernel Ertelenmiş İş ve Thread

IRQ bağlamından process bağlamına geçişin tüm yolları — workqueue, delayed work, kthread ve tasklet API'larını gerçek sürücü örnekleriyle öğren.

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:

Uyuma yasakschedule(), 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 yasakBellek baskısı altında GFP_KERNEL alloc uyuyabilir. Interrupt bağlamında yalnızca GFP_ATOMIC kullanılabilir; bu da başarısız olabilir.
Uzun işlem yasakHer işlenmeyen CPU döngüsü sistem gecikmesine eklenir. IRQ handler süresi ölçülmeli; 10 µs üzerindeyse ertelenmeli.
Bazı kernel API'ları yasakI2C/SPI transfer fonksiyonları, dosya sistemi operasyonları, networking stack çağrıları — tümü uyuyabilir veya mutex kullanır.

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 MekanizmaNeden
IRQ'dan kısa, non-blocking iştaskletDüşük overhead, softirq bağlamı
IRQ'dan blocking I/O (I2C, SPI)workqueue veya threaded IRQProcess bağlamı, uyuyabilir
Belirli bir CPU'da sıralı işordered workqueueTek worker, FIFO garantisi
Gecikmeli (timer-based) işdelayed_workschedule_delayed_work() ile ms hassasiyeti
Sürekli çalışan polling döngüsükthreadBağımsız thread, tam kontrol
Periyodik RT işkthread + hrtimerYü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

system_wqGenel amaçlı, sınırsız (unbound) workqueue. Kısa süreli, non-blocking iş için. schedule_work() bu workqueue'yi kullanır.
system_highpri_wqYüksek öncelikli worker thread'leri. Gecikmeye duyarlı ama CPU-bağlı olmayan işler için. queue_work(system_highpri_wq, w)
system_long_wqUzun süreli (saniyeler) işler için. Normal system_wq'deki starvation riskini azaltır.
system_unbound_wqCPU affinity olmadan çalışır — NUMA topanjını gözetir. Yoğun hesaplama işleri için.
system_freezable_wqSuspend/hibernate sırasında dondurulur. Power-aware driver'lar için.
system_power_efficient_wqWQ_POWER_EFFICIENT bayrağı — mevcut CPU'ya affinity yerine güç tasarrufu öncelikli.

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ı

BayrakAnlamNe Zaman Kullanılır
WQ_UNBOUNDWorker thread'leri CPU'ya bağlı değilUzun hesaplama, NUMA farkındalığı gerekmeyen iş
WQ_MEM_RECLAIMBellek baskısında bile çalışmaya devam ederBlok sürücüler, swap path — ölümcül deadlock'u önler
WQ_HIGHPRIYüksek öncelikli worker thread'leriDüşük gecikme, CPU-bağlı olmayan kritik yol
WQ_CPU_INTENSIVEHesaplama yoğun — scheduler'a ipucuŞifreleme, sıkıştırma işleri
WQ_FREEZABLESistem askıya alındığında dondurulurGüç yönetimi ile entegre çalışan sürücüler
WQ_SYSFSsysfs altında görünür olurDebug / 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ı

Kriterkthread_workerworkqueue
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 affinityset_cpus_allowed_ptrWQ_UNBOUND veya affinity_scope
Sıralı çalışmaGarantili (tek thread)ordered_workqueue gerektirir
OverheadDüşükBiraz daha yüksek (pool yönetimi)
Önerilen kullanımRT, sıralı, özel öncelikGenel 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

Softirq bağlamıTasklet'ler TASKLET_SOFTIRQ veya HI_SOFTIRQ bağlamında çalışır — process bağlamı değil. Uyuyamaz, GFP_ATOMIC gerektirir.
Aynı anda tek CPUAynı tasklet, aynı anda yalnızca bir CPU'da çalışabilir. Farklı tasklet'ler farklı CPU'larda paralel çalışabilir.
Atomik işlemTasklet işlemi atomik — bir kez başlarsa tamamlanır. Preempt edilemez (softirq preemption olmaz).
Çift zamanlama korumasıtasklet_schedule() aynı tasklet için birden fazla çağrıda sadece bir kez kuyruğa alır. IRQ handler'dan güvenle tekrar çağrılabilir.

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

SorunAçıklamaWorkqueue Avantajı
Uyuma yasakTüm blocking işlemler imkânsızWorkqueue'de GFP_KERNEL, mutex, I2C serbest
PREEMPT_RT uyumsuzluğuRT yamalarında tasklet sorunluWorkqueue RT uyumlu
Hata ayıklama zorluğuSoftirq bağlamı stack trace karmaşıkNormal process bağlamı — kolay debug
Deprecation eğilimiKernel geliştiricileri yeni API önermiyorAktif 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

Tek worker threadEski API'da her singlethread_workqueue tek bir worker thread kullanıyordu. Bir work uyuduğunda tüm kuyruk duruyordu.
CPU başına ayrı threadcreate_workqueue() her CPU için ayrı thread oluşturuyordu. 64 CPU'lu sistemde 64 worker — kaynak israfı.
Starvation riskiUzun çalışan work'ler diğerlerini engelliyor, CPU'lar atıl bekliyordu.

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ÖzellikKullanılan Bayrak
per-CPU boundHer CPU için ayrı pool, work o CPU'da çalışırVarsayılan (bayrak yok)
per-CPU highpriYüksek öncelikli per-CPU poolWQ_HIGHPRI
unboundCPU affinity yok, NUMA awareWQ_UNBOUND
unbound highpriUnbound + yüksek öncelikWQ_UNBOUND | WQ_HIGHPRI
orderedmax_active=1, FIFO garantisialloc_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 */

Sorun Giderme Kontrol Listesi

Work hiç çalışmıyorINIT_WORK çağrıldı mı? queue_work dönüş değeri kontrol edildi mi? destroy_workqueue çok erken çağrıldı mı?
Work tamamlanmıyorHandler içinde sonsuz döngü veya deadlock var mı? /proc/PID/wchan ile bekleme noktasını kontrol et.
Sıralama bozukalloc_ordered_workqueue kullanılıyor mu? Yoksa varsayılan workqueue FIFO garantisi vermez.
UAF (Use-After-Free)cancel_work_sync çağrılmadan sürücü struct'ı serbest bırakıldı. remove() içinde her zaman cancel_work_sync çağır.