Tüm eğitimler
Rehber Gömülü Linux Real-time

PREEMPT-RT —
Gerçek Zamanlı Linux.

standart kernel'ı RT'ye dönüştür — cyclictest, latency analizi ve CPU isolation.

00 Real-time nedir ve neden Linux

Real-time hesaplama, sonucun hem doğru hem de zamanında üretilmesini şart koşar — deadline miss'in bedeli sisteme göre değişir, ama RT'nin özü budur.

Soft RT ve Hard RT ayrımı

Real-time sistemler iki ana kategoriye ayrılır. Soft RT'de deadline zaman zaman kaçırılabilir; sonuç hâlâ işe yarar ama kalite düşer. Video akışında bir kare geç gelirse ekran donup açılır — sinir bozucudur, kritik değil. Hard RT'de ise tek bir deadline miss felakete yol açabilir: otomotiv ABS sistemi, uçak fly-by-wire kontrol yüzeyi, implante kalp pili. Bu sistemlerde "zamanında değil" ile "yanlış" arasında fark yoktur.

KategoriDeadline Miss SonucuÖrnek
Soft RTPerformans düşüşü (degraded)Video codec, UI refresh, ses işleme
Hard RTSistem hatası (catastrophic)Motor kontrol, ABS, robotik eklem
Firm RTSonuç geçersiz, görmezden gelinirFinans tick verisi, sensör sampling

Standart Linux preemption modelleri

Linux kernel'i farklı preemption seviyeleriyle derlenebilir. Bu seçim, bir interrupt geldiğinde veya yüksek öncelikli task uyandığında, mevcut task'ın ne kadar çabuk yerini bıraktığını belirler.

CONFIG seçeneğiAdPreemption noktasıTipik latency
PREEMPT_NONENo Forced PreemptionYalnızca explicit yield / syscall dönüşüms – onlarca ms
PREEMPT_VOLUNTARYVoluntary Kernel PreemptionEkstra might_sleep() noktalarıyüzlerce µs
PREEMPTLow-Latency DesktopTüm kernel kodu (spinlock dışı)~100 µs
PREEMPT_RTFull PreemptionHer nokta — spinlock dahil< 50 µs

PREEMPT_RT ne yapar?

PREEMPT_RT patch'inin temel mekanizması üç dönüşümde özetlenir. Spinlock → RT-mutex: spinlock'lar sleeping mutex'e dönüştürülür; böylece kilit bekleyen bir thread CPU'yu meşgul etmez, uyur. IRQ handler → RT thread: interrupt handler'lar ayrı kernel thread'lerine taşınır, dolayısıyla preempt edilebilir ve priority atanabilir hâle gelir. Soft IRQ → thread: softirq'lar da thread olarak çalışır. Sonuç: kernel içinde neredeyse hiçbir non-preemptible bölge kalmaz.

  Interrupt gelir
       │
       ▼
  IRQ kernel thread (RT, preemptible)
       │
       ├─ PREEMPT_RT:  yüksek prio task varsa → hemen preempt et
       │
       └─ Standart:    top-half biter, sonra softirq, sonra schedule
    

Kullanım alanları

PREEMPT_RT en sık şu alanlarda kullanılır: CNC ve robot manipülatör kontrolü (EtherCAT servo döngüsü), endüstriyel PLC yazılımı (IEC 61131 runtime), audio processing (JACK, low-latency audio), telecom baz istasyonları, medikal görüntüleme ekipmanı ve otonom araç algı pipeline'ları.

PREEMPT_RT vs RTOS karşılaştırması

KriterPREEMPT_RT LinuxFreeRTOS / Zephyr
Worst-case latency10–50 µs (iyi konfigürasyonla)1–10 µs
POSIX uyumuTamKısmi / yok
Sürücü ekosistemiGeniş (Linux driver tree)Sınırlı BSP
Bellek korumaMMU ile tam izolasyonMPU / opsiyonel
Geliştirme araçlarıGDB, perf, ftrace, valgrindSınırlı
DeterminizmYüksek (bounded)Çok yüksek (hard)
İşletim maliyetiYüksek (full OS)Düşük (< 10 KB ROM)
NOT

2024 yılında PREEMPT_RT resmi olarak Linux mainline'a girdi: kernel 6.12 ile birlikte CONFIG_PREEMPT_RT artık ayrı patch gerektirmiyor. Daha eski kernel sürümleri için kernel.org RT patch arşivini kullanın.

Bu bölümde

  • Soft RT ve Hard RT arasındaki fark: deadline miss sonucu
  • Dört preemption modeli ve latency karakteristikleri
  • PREEMPT_RT'nin üç temel dönüşümü: spinlock, IRQ, softirq
  • RTOS ile karşılaştırmalı analiz — ne zaman hangisi?

01 RT kernel derleme veya kurulum

PREEMPT_RT kernel'i edinmenin iki yolu var: hazır paket (hızlı) veya kaynak koddan derleme (tam kontrol).

Hazır paket ile kurulum (Debian / Ubuntu)

Debian tabanlı sistemlerde RT kernel paketi doğrudan apt deposunda bulunur. x86_64 için:

terminal
# Mevcut RT kernel paketlerini listele
apt-cache search linux-image-rt

# Kur
sudo apt install linux-image-rt-amd64 linux-headers-rt-amd64

# Yeniden başlat ve RT kernel'i seç (GRUB menüsünde)
sudo reboot

# Doğrula
uname -a
# → Linux hostname 6.1.0-28-rt-amd64 #1 SMP PREEMPT_RT ...
DİKKAT

Ubuntu 22.04 LTS'de RT kernel paketi linux-image-lowlatency-hwe-22.04 adıyla gelir, ancak bu PREEMPT_RT değil yalnızca PREEMPT'tir. Gerçek PREEMPT_RT için Ubuntu Pro aboneliği veya manuel derleme gerekir.

Kaynak koddan manuel derleme

Tam kontrol veya özel donanım desteği için kaynak koddan derleme şart. Kernel 6.12 öncesi sürümler için RT patch indirmek gerekir:

rt-kernel-build.sh
# 1) Kernel kaynağını indir (örnek: 6.6.x)
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.30.tar.xz
tar xf linux-6.6.30.tar.xz
cd linux-6.6.30

# 2) Karşılık gelen RT patch'i indir
wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.6/\
patch-6.6.30-rt30.patch.xz
xz -d patch-6.6.30-rt30.patch.xz

# 3) Patch'i uygula
patch -p1 < patch-6.6.30-rt30.patch

# 4) Mevcut konfigürasyondan başla
cp /boot/config-$(uname -r) .config
make olddefconfig

# 5) menuconfig ile PREEMPT_RT'yi aktif et
make menuconfig
# General Setup → Preemption Model → Fully Preemptible Kernel (Real-Time)

Kritik Kconfig seçenekleri

menuconfig'de veya .config dosyasında şu seçeneklerin doğru ayarlandığından emin olun:

.config
CONFIG_PREEMPT_RT=y          # Tam RT preemption
CONFIG_HZ_1000=y             # 1000 Hz tick — daha iyi resolüsyon
CONFIG_NO_HZ_FULL=y          # Tickless RT CPU'lar için
CONFIG_HIGH_RES_TIMERS=y     # Yüksek çözünürlüklü timer
CONFIG_CPU_ISOLATION=y       # isolcpus desteği
CONFIG_IKCONFIG_PROC=y       # /proc/config.gz — runtime doğrulama

# Kapatılması önerilen özellikler
# CONFIG_NUMA_BALANCING is not set
# CONFIG_TRANSPARENT_HUGEPAGE is not set  (jitter kaynağı)
derleme ve kurulum
# Çok çekirdekli derleme (-j ile)
make -j$(nproc) LOCALVERSION="-rt-custom"

# Modülleri kur
sudo make modules_install

# Kernel'i kur (GRUB otomatik güncellenir)
sudo make install

sudo reboot
uname -v
# → #1 SMP PREEMPT_RT Sat Apr 12 12:00:00 UTC 2025

Raspberry Pi için RT kernel

RPi'da iki seçenek var: Raspberry Pi Foundation'ın kendi rpi-rt fork'u veya Raspberry Pi OS'un linux-raspi-rt paketi.

raspberry-pi-rt.sh
# RPi OS (Bookworm) üzerinde
sudo apt install linux-image-rt-arm64   # RPi 4/5 için

# Veya rpi-rt kaynak fork'u ile çapraz derleme:
git clone --depth=1 -b rpi-6.6.y-rt \
  https://github.com/raspberrypi/linux rpi-rt-kernel

# Çapraz derleme toolchain
sudo apt install gcc-aarch64-linux-gnu

cd rpi-rt-kernel
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make bcm2711_defconfig
ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- make -j$(nproc)

Yocto'da RT kernel

Yocto tabanlı projelerde RT kernel seçimi local.conf veya distro katmanında yapılır:

local.conf
# linux-yocto-rt recipe'sini kullan
PREFERRED_PROVIDER_virtual/kernel = "linux-yocto-rt"

# Kernel özelliğini etkinleştir
KERNEL_FEATURES:append = " features/preempt-rt/preempt-rt.scc"

# RT için önerilen ek konfigürasyon
KERNEL_EXTRA_FEATURES = "features/preempt-rt/preempt-rt.scc"

Bu bölümde

  • Hazır paket kurulumu: linux-image-rt-amd64
  • Manuel derleme: RT patch indirme, uygulama, menuconfig
  • Kritik Kconfig seçenekleri: CONFIG_PREEMPT_RT, CONFIG_HZ_1000, CONFIG_HIGH_RES_TIMERS
  • Raspberry Pi ve Yocto için RT kernel seçenekleri

02 cyclictest ile latency ölçümü

cyclictest, bir RT thread'i periyodik olarak uyandırarak beklenen ve gerçekleşen wake-up zamanı arasındaki farkı — latency'yi — ölçer.

cyclictest nasıl çalışır?

Araç şu döngüyü çalıştırır: bir timestamp al, belirtilen aralık kadar uyu (clock_nanosleep), uyandıktan sonra başka bir timestamp al, farkı hesapla. Bu fark latency'dir. RT thread'inde SCHED_FIFO policy ile çalışarak kernel scheduler'ının gecikmesini, spinlock bekleme sürelerini ve interrupt handler gecikmelerini ölçer.

terminal
# rt-tests paketini kur
sudo apt install rt-tests

# Temel ölçüm: 4 thread, 100.000 iterasyon, 1ms periyot, RT prio 99
sudo cyclictest -p 99 -t 4 -n -i 1000 -l 100000

# CPU 2'ye affinity, quiet mod (yalnızca özet)
sudo cyclictest -p 99 -t 1 -n -i 1000 -l 1000000 -a 2 -q

Parametre referansı

-p <priority>SCHED_FIFO RT önceliği (1–99). Test thread'i için 99 kullanın.
-t <threads>Kaç RT thread açılacak. CPU sayısı kadar setin.
-nnanosleep kullan (clock_nanosleep, daha hassas)
-i <interval>Periyot, mikrosaniye cinsinden. 1000 = 1 ms.
-l <loops>Her thread'in döngü sayısı. 0 = sonsuz.
-a <cpus>CPU affinity maskesi. -a 2 → yalnızca CPU2.
-qQuiet mod: yalnızca son özeti göster.
--histogram=<N>N mikrosaniyeye kadar histogram verisi üret.

Örnek çıktı ve yorumlama

cyclictest çıktısı
# RT kernel üzerinde tipik çıktı:
T: 0 ( 1234) P:99 I:1000 C: 100000 Min:      3 Act:    7 Avg:    8 Max:     42
T: 1 ( 1235) P:99 I:1000 C: 100000 Min:      3 Act:    6 Avg:    8 Max:     38
T: 2 ( 1236) P:99 I:1000 C: 100000 Min:      4 Act:    9 Avg:    9 Max:     51

# Standart kernel (PREEMPT) üzerinde tipik çıktı:
T: 0 ( 5678) P:99 I:1000 C: 100000 Min:     12 Act:   85 Avg:   91 Max:   4821
T: 1 ( 5679) P:99 I:1000 C: 100000 Min:     11 Act:  120 Avg:  102 Max:   6340

Çıktı sütunları: Min en düşük gözlenen latency (µs), Avg ortalama, Max worst-case. RT sistemlerde Max değeri kritiktir ve tutarlı kalmalıdır.

Histogram analizi

histogram.sh
# Histogram verisi üret (200 µs'ye kadar)
sudo cyclictest -p 99 -t 4 -n -i 1000 -l 1000000 \
  --histogram=200 --histfile=histogram.dat

# gnuplot ile görselleştir
cat > plot.gp <<'EOF'
set terminal png size 900,400
set output "latency.png"
set title "Latency Histogram — PREEMPT_RT"
set xlabel "Latency (µs)"
set ylabel "Occurrences"
set logscale y
plot "histogram.dat" using 1:2 with impulses lc rgb "#9ab6ff" title "CPU0", \
     "histogram.dat" using 1:3 with impulses lc rgb "#b07dcc" title "CPU1"
EOF
gnuplot plot.gp
NOT

Ölçüm sırasında sisteme yük bindirin: stress-ng --cpu 4 --io 2 & komutuyla. Boşta düşük latency elde edip gerçek yük altında kötü performans görmek, yanlış bir güven yaratır. Worst-case her zaman yük altında ölçülmelidir.

Bu bölümde

  • cyclictest çalışma prensibi: RT thread + clock_nanosleep delta ölçümü
  • Temel kullanım: cyclictest -p 99 -t 4 -n -i 1000 -l 100000
  • Parametre tablosu: -p, -t, -n, -i, -l, -a, -q, --histogram
  • RT kernel ile standart kernel latency karşılaştırması

03 Priority inversion ve priority inheritance

Priority inversion, düşük öncelikli bir task'ın mutex tutması nedeniyle yüksek öncelikli task'ı bloke ettiği klasik RT tuzağıdır — Mars Pathfinder bunu 1997'de uzayda yaşadı.

Senaryo: priority inversion nasıl oluşur?

  LOW   task (prio=10)  → mutex al → çalışıyor
  MED   task (prio=50)  → LOW'u preempt et → CPU'yu meşgul tut
  HIGH  task (prio=90)  → mutex bekle → LOW çalışamıyor çünkü MED bloke etti

  Sonuç: HIGH prio=90 task, prio=50 MED task tamamlanana kadar bekler.
         Bu "inversion" — yüksek öncelik, düşük önceliğin altında işlenir.
    

Mars Pathfinder krizi (1997)

VxWorks RTOS üzerinde çalışan Mars Pathfinder'ın bilgi servisi, priority inversion nedeniyle watchdog timer'ı tetikledi ve sistemin kendini sıfırlamasına neden oldu. Düşük öncelikli meteorolojik veri toplama task'ı, bilgi servisinin beklediği bir mutex'i tutuyordu. Orta öncelikli bir task bu esnada sürekli çalışarak düşük öncelikli task'ın bitmesini engelliyordu. Bu bug görev planlamasında zaten bilinen, ancak ground test'te görülmemiş bir senaryoydu.

Çözüm: Priority Inheritance

Priority inheritance protokolünde, yüksek öncelikli bir task bir mutex'i almaya çalıştığında kernel, o mutex'i tutan task'ın önceliğini geçici olarak yüksek öncelikli task'ın seviyesine çeker. Mutex serbest bırakılınca orijinal öncelik geri yüklenir.

  LOW (prio=10) mutex tutarken HIGH (prio=90) beklerse:
       │
       ▼  priority inheritance devreye girer
  LOW task geçici olarak prio=90 alır
       │
       └─ MED task (prio=50) artık LOW'u preempt edemez
       └─ LOW mutex'i bitirir ve HIGH'a bırakır → orijinal prio=10'a döner
    

POSIX API: priority inheritance mutex

priority_inherit.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

static pthread_mutex_t g_mutex;

void init_pi_mutex(void) {
    pthread_mutexattr_t attr;

    /* mutex attribute nesnesi oluştur */
    pthread_mutexattr_init(&attr);

    /* PTHREAD_PRIO_INHERIT: mutex bekleyen yüksek prio task varsa
       tutan task'ın prio'sunu geçici yükselt */
    pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);

    /* Mutex tipini ERRORCHECK yap (debug için) */
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK);

    pthread_mutex_init(&g_mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

int main(void) {
    init_pi_mutex();
    /* ... RT task'lar bu mutex'i kullanır ... */
    pthread_mutex_destroy(&g_mutex);
    return 0;
}

Priority ceiling alternatifi

PTHREAD_PRIO_CEILING protokolünde mutex'e sabit bir tavan önceliği atanır. Mutex'i alan her task, o tavan önceliğine çekilir. Inheritance'dan farkı: bekleme olmadan tavan uygulanır, böylece blocking tamamen önlenebilir. Ancak tavan değerini statik olarak doğru belirlemek gerekir.

priority_ceiling.c
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_CEILING);
/* Tavan: sistemi kullanan en yüksek RT prio + 1 */
pthread_mutexattr_setprioceiling(&attr, 91);
pthread_mutex_init(&g_mutex, &attr);
DİKKAT

PREEMPT_RT olmayan standart kernel üzerinde PTHREAD_PRIO_INHERIT desteği POSIX uyumludur ama RT spinlock dönüşümleri yoktur; dolayısıyla kernel içi priority inversion tam olarak çözülmez. Gerçek RT güvencesi için PREEMPT_RT zorunludur.

Bu bölümde

  • Priority inversion mekanizması: LOW mutex tutar, HIGH bekler, MED bloke eder
  • Mars Pathfinder 1997 — gerçek dünya vakası
  • PTHREAD_PRIO_INHERIT ile priority inheritance mutex kurulumu
  • Priority ceiling alternatifi ve farkı

04 RT task yazımı

Doğru yazılmış bir RT task: memory kilitler, önceliğini ayarlar, stack'i prefault eder ve kesin zamanlamayla döngü çalıştırır.

Scheduling policy seçimi

PolicyDavranışKullanım
SCHED_FIFOAynı öncelikte preempt olmaz, önce gelen beklerDeterministik RT task'lar
SCHED_RRFIFO + round-robin time quantumEşit öncelikli RT task'lar arası adillik
SCHED_DEADLINECBS (Constant Bandwidth Server): runtime, deadline, periodSporadic task'lar, EDF scheduling
SCHED_OTHERCFS, normal nice-basedRT olmayan görevler

Tam RT task iskeleti

rt_task.c
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <sys/mman.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>

#define PERIOD_NS     1000000LL   /* 1 ms */
#define RT_PRIORITY   90
#define STACK_PREFAULT (8 * 1024 * 1024)  /* 8 MB */

/* Zaman yardımcıları */
static inline void timespec_add_ns(struct timespec *ts, long long ns) {
    ts->tv_nsec += ns;
    while (ts->tv_nsec >= 1000000000LL) {
        ts->tv_sec++;
        ts->tv_nsec -= 1000000000LL;
    }
}

void *rt_task_func(void *arg) {
    struct timespec next;
    unsigned char stack_buf[STACK_PREFAULT];

    /* ── Adım 1: Stack prefault — page fault'ları RT döngüsü dışına al ── */
    memset(stack_buf, 0, sizeof(stack_buf));

    /* ── Adım 2: Scheduling policy ve öncelik ayarla ── */
    struct sched_param sp = { .sched_priority = RT_PRIORITY };
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp) != 0) {
        perror("pthread_setschedparam");
        return NULL;
    }

    /* ── Adım 3: CPU affinity — bu thread'i CPU2'ye pinle ── */
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(2, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);

    /* ── Adım 4: Başlangıç zamanı al ── */
    clock_gettime(CLOCK_MONOTONIC, &next);

    while (1) {
        /* Bir sonraki periyota ilerle */
        timespec_add_ns(&next, PERIOD_NS);

        /* Mutlak zamana kadar bekle (TIMER_ABSTIME: drift birikmez) */
        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);

        /* ── Gerçek RT çalışma kodu buraya ── */
        do_control_loop();
    }
    return NULL;
}

int main(void) {
    /* ── Tüm memory'yi kilitle: page fault RT döngüsünde olmasın ── */
    if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
        perror("mlockall");
        return 1;
    }

    pthread_t tid;
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    /* Stack boyutunu büyüt */
    pthread_attr_setstacksize(&attr, 8 * 1024 * 1024);

    pthread_create(&tid, &attr, rt_task_func, NULL);
    pthread_join(tid, NULL);

    munlockall();
    return 0;
}

mlockall: neden zorunlu?

mlockall(MCL_CURRENT | MCL_FUTURE) process'in tüm sayfalarını fiziksel RAM'de kilitler. ÇağrılmazsA, RT thread ilk kez bir bellek sayfasına eriştiğinde Linux page fault üretir. Page fault handler kernel içinde uzun süre çalışabilir ve worst-case latency'yi onlarca milisaniye artırır. MCL_FUTURE ile gelecekte yapılacak malloc çağrıları da otomatik kilitlenir.

DİKKAT

mlockall root yetkisi gerektirir. Alternativ olarak setcap cap_ipc_lock+ep ./myapp ile capability bazlı izin verilebilir. Üretim sistemlerinde yetkiyi minimize etmek için capability yaklaşımı tercih edin.

Bu bölümde

  • SCHED_FIFO, SCHED_RR, SCHED_DEADLINE farkları
  • RT task başlatma sırası: mlockall → öncelik → prefault → döngü
  • clock_nanosleep + TIMER_ABSTIME ile drift-free periyodik zamanlama
  • pthread_setaffinity_np ile CPU pinleme

05 Threaded IRQ

PREEMPT_RT'nin en önemli yeniliklerinden biri: interrupt handler'ları kernel thread'lerine taşıyarak her yerden preempt edilebilir hâle getirmek.

Standart kernel: top-half / bottom-half modeli

  Standart kernel IRQ akışı:
  ─────────────────────────────────────────────────────────
  donanım interrupt
       │
       ▼
  top-half handler (non-preemptible, hızlı, kısa)
       │  IRQ disable sırasında çalışır
       ▼
  softirq / tasklet (bottom-half, hâlâ non-preemptible segment)
       │
       ▼
  kernel thread / workqueue (preemptible, ama geç)
  ─────────────────────────────────────────────────────────
  PREEMPT_RT kernel IRQ akışı:
  ─────────────────────────────────────────────────────────
  donanım interrupt
       │
       ▼
  IRQ kernel thread (irq/XX-devname, SCHED_FIFO, preemptible)
       │  Diğer RT task'lar bunu preempt edebilir
       ▼
  handler tamamlanır → thread uyur
    

IRQ thread'lerini görme

terminal
# /proc/interrupts: her CPU'da interrupt sayıları
cat /proc/interrupts | head -20

# IRQ kernel thread'lerini listele (irq/ prefix'i ile)
ps -eo pid,policy,rtprio,comm | grep "^.*irq"
# Örnek çıktı:
#  234 FF  50 irq/32-eth0
#  235 FF  50 irq/33-snd_hda
#  189 FF  50 irq/16-ehci_hcd

# Belirli IRQ'nun thread bilgisi
cat /proc/irq/32/sched

IRQ thread priority ayarlama

terminal
# IRQ thread PID'ini bul
IRQ_PID=$(ps -eo pid,comm | awk '/irq\/32-eth0/{print $1}')

# Priority'yi SCHED_FIFO:90 yap
sudo chrt -f -p 90 $IRQ_PID

# Veya doğrudan PID ile
sudo chrt -f -p 90 234

IRQF_NO_THREAD bayrağı

Bazı eski donanım sürücüleri veya zaman kritik kesmeler için IRQ handler'ın thread'e taşınması istenmiyor olabilir. Sürücü içinde IRQF_NO_THREAD bayrağı ile thread dönüşümü engellenir:

driver.c
/* Legacy donanım: threaded IRQ istemiyoruz */
request_irq(irq_num, my_handler,
            IRQF_SHARED | IRQF_NO_THREAD,
            "my_legacy_device", dev);

/* Modern sürücü: threaded IRQ tercih edilir */
request_threaded_irq(irq_num,
    my_hardirq_handler,   /* top-half: hızlı, ACK yeterli */
    my_thread_handler,    /* bottom-half: thread içinde çalışır */
    IRQF_ONESHOT,
    "my_device", dev);
NOT

IRQ thread'lerinin varsayılan önceliği genellikle SCHED_FIFO:50'dir. RT uygulamanız bu IRQ thread'lerinden daha yüksek öncelikte çalışıyorsa (örn. prio=90), interrupt işlenirken uygulamanız preempt edilmez — bu RT tasarımında bilinçli bir karar olmalıdır.

Bu bölümde

  • Standart kernel top-half/bottom-half modeli vs PREEMPT_RT threaded IRQ
  • /proc/interrupts ve ps ile IRQ thread'lerini izleme
  • chrt ile IRQ thread önceliği ayarlama
  • IRQF_NO_THREAD ve request_threaded_irq kullanımı

06 CPU isolation ve affinity

RT task'ları belirli CPU çekirdeklerine pinleyip bu çekirdekleri genel scheduler'dan ayırmak, en etkili latency azaltma tekniklerinden biridir.

isolcpus kernel boot parametresi

En kolay CPU izolasyon yöntemi, kernel komut satırına isolcpus= parametresi eklemektir. Bu parametre, belirtilen CPU'ları Linux CFS scheduler'ından çıkarır; o CPU'lara yalnızca affinity ile açıkça atanan task'lar yerleştirilir.

/etc/default/grub
# CPU 2 ve 3'ü izole et (4 çekirdekli sistemde)
GRUB_CMDLINE_LINUX_DEFAULT="quiet isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"

# Değişikliği uygula
# sudo update-grub && sudo reboot

nohz_full=2,3 ile izole CPU'larda timer tick devre dışı bırakılır (tickless mode). rcu_nocbs=2,3 ile RCU callback'leri izole CPU'lardan uzaklaştırılır. Her ikisi de latency'yi önemli ölçüde düşürür.

taskset ile affinity

terminal
# Uygulamayı CPU 2'de başlat
sudo taskset -c 2 ./rt_app

# Çalışan process'in affinity'sini değiştir
sudo taskset -c 2,3 -p $PID

# Mevcut affinity'yi oku
taskset -c -p $PID

cpuset ile gelişmiş izolasyon

cpuset-isolation.sh
# cset kurulumu
sudo apt install cpuset

# RT shield oluştur: CPU 2,3 için (kthread'ler dahil)
sudo cset shield --cpu=2,3 --kthread=on

# RT uygulamayı shield içine al
sudo cset shield --exec ./rt_app

# Shield durumunu göster
sudo cset shield --shield

# Shield'i kaldır
sudo cset shield --reset

IRQ affinity: kesmeler RT CPU'lardan uzaklaştır

RT CPU'larda interrupt yaşanması latency'yi bozar. IRQ'ları RT olmayan CPU'lara yönlendirmek için smp_affinity kullanılır. Değer CPU bitmask'tir (hex).

irq-affinity.sh
# IRQ 32'yi CPU 0 ve 1'e yönlendir (bitmask: 0x3 = CPU0+CPU1)
echo 3 | sudo tee /proc/irq/32/smp_affinity

# Tüm IRQ'ları CPU 0,1'e yönlendirmek için döngü
for irq in /proc/irq/[0-9]*/smp_affinity; do
    echo 3 | sudo tee $irq 2>/dev/null
done

# irqbalance servisini durdur (RT sistemde otomatik IRQ taşımasını kapat)
sudo systemctl stop irqbalance
sudo systemctl disable irqbalance
DİKKAT

irqbalance servisi çalışıyorsa IRQ affinity ayarlarınızı periyodik olarak sıfırlayabilir. RT sistemlerde bu servisi mutlaka devre dışı bırakın.

Bu bölümde

  • isolcpus + nohz_full + rcu_nocbs: kernel parametreleri ile tam CPU izolasyonu
  • taskset ve cset shield ile çalışma zamanı CPU pinleme
  • /proc/irq/XX/smp_affinity ile IRQ'ları RT CPU'lardan uzaklaştırma
  • irqbalance servisini RT sistemde devre dışı bırakma

07 Latency kaynakları ve ftrace

Worst-case latency'nin kökeni genellikle yazılım değil; SMI, BIOS C-state geçişleri ve memory subsystem'daki deterministik olmayan davranışlardır.

Latency kaynakları tablosu

KaynakTipik EtkiTespit YöntemiÇözüm
SMI (System Management Interrupt)100 µs – 10 mshwlatdetectBIOS'ta devre dışı bırak
CPU C-state geçişleri50 – 500 µscyclictest + powertopC1 dışındakileri kapat
Turbo Boost / frequency scaling10 – 200 µscpufreq-infoSabit frekans, performance gov.
Page fault1 – 50 µsperf stat -e page-faultsmlockall + prefault
Lock contention1 – 100 µsftrace lockdepLock granülasyonu, lock-free yapı
Scheduling overhead1 – 20 µssched_switch traceCPU affinity, isolcpus
Hyperthreading10 – 100 µscyclictest with HT on/offBIOS'ta kapat veya core izole et

hwlatdetect ile SMI tespiti

terminal
# rt-tests içinde gelir
sudo hwlatdetect --duration=60 --threshold=10

# Örnek çıktı — SMI yakalandıysa:
# hwlatdetect:  0 samples > threshold (10 us)
# hwlatdetect:  Maximum latency: 8.12 us

# SMI varsa:
# hwlatdetect:  3 samples > threshold (10 us)
# hwlatdetect:  Maximum latency: 47.3 us   ← BIOS SMI

tracecmd ile scheduling olayları

ftrace-latency.sh
# trace-cmd kur
sudo apt install trace-cmd

# RT process'in scheduling olaylarını kaydet (5 saniye)
sudo trace-cmd record -e sched_switch -P $RT_PID -s 5000 &
sudo ./rt_app &
wait

# Raporu görüntüle
trace-cmd report | head -50

ftrace preemptirq tracer

ftrace-preemptirq.sh
TRACING=/sys/kernel/debug/tracing

# preemptirq latency tracer'ı etkinleştir
echo preemptirq | sudo tee $TRACING/current_tracer

# Latency eşiğini 50 µs olarak ayarla
echo 50 | sudo tee $TRACING/tracing_thresh

# Tracer'ı başlat
echo 1 | sudo tee $TRACING/tracing_on

# ... RT uygulamayı çalıştır ...

# Worst-case latency değerini oku
cat $TRACING/tracing_max_latency

# Trace verisini al (worst-case olayı içerir)
cat $TRACING/trace | head -100

# Tracer'ı sıfırla
echo nop | sudo tee $TRACING/current_tracer

BIOS ayarları: RT için gerekli değişiklikler

BIOS AyarıRT ÖneriNedeni
C-StatesC1 dışındakileri kapatC3/C6 geçişi yüzlerce µs sürer
Turbo BoostKapatFrekans değişimi latency jitter yaratır
HyperthreadingKapat veya izole etShared execution unit contention
SMIMümkünse kapat (donanıma bağlı)BIOS kaynaklı non-maskable interrupt
NUMA InterleavingKapat (NUMA sistemlerde)Uzak NUMA node erişimi yüksek latency

Bu bölümde

  • Latency kaynakları: SMI, C-state, page fault, lock contention, hyperthreading
  • hwlatdetect ile hardware/SMI kaynaklı latency tespiti
  • trace-cmd ve ftrace preemptirq tracer ile scheduling analizi
  • BIOS optimizasyon listesi: RT sistem için kapatılması gerekenler

08 Pratik: motor kontrolcü örneği

1 kHz periyodik kontrol döngüsü, worst-case 100 µs latency hedefi: RT kernel, CPU izolasyonu ve jitter monitörü bir araya getirilir.

Sistem konfigürasyonu

Test sistemi: 4 çekirdekli x86_64, PREEMPT_RT kernel 6.6-rt. CPU 2 izole edilir, RT task bu CPU'ya pinlenir. IRQ'lar CPU 0,1'e yönlendirilir.

boot parametreleri
GRUB_CMDLINE_LINUX_DEFAULT="quiet isolcpus=2 nohz_full=2 rcu_nocbs=2 \
  processor.max_cstate=1 intel_idle.max_cstate=0 \
  intel_pstate=disable cpufreq.default_governor=performance"

Motor kontrolcü RT task

motor_control.c
#define _GNU_SOURCE
#include <pthread.h>
#include <sched.h>
#include <sys/mman.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define PERIOD_NS      1000000LL    /* 1 ms = 1 kHz */
#define RT_CPU         2
#define RT_PRIO        90
#define JITTER_LIMIT   100000LL     /* 100 µs worst-case hedef */
#define LOG_SIZE       10000

static long long jitter_log[LOG_SIZE];
static int       jitter_idx = 0;
static long long max_jitter = 0;

static inline long long ts_to_ns(const struct timespec *ts) {
    return (long long)ts->tv_sec * 1000000000LL + ts->tv_nsec;
}

void *motor_ctrl_thread(void *arg) {
    struct timespec now, next;
    unsigned char prefault_buf[1024 * 1024];

    /* Stack prefault */
    memset(prefault_buf, 0, sizeof(prefault_buf));

    /* SCHED_FIFO priority */
    struct sched_param sp = { .sched_priority = RT_PRIO };
    pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp);

    /* CPU affinity: sadece RT_CPU */
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(RT_CPU, &cpuset);
    pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset);

    /* Başlangıç zamanı */
    clock_gettime(CLOCK_MONOTONIC, &next);

    while (1) {
        /* Bir sonraki wake-up zamanı */
        long long target_ns = ts_to_ns(&next) + PERIOD_NS;
        next.tv_sec  = target_ns / 1000000000LL;
        next.tv_nsec = target_ns % 1000000000LL;

        clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);

        /* Gerçek wake-up zamanını ölç */
        clock_gettime(CLOCK_MONOTONIC, &now);
        long long actual_ns  = ts_to_ns(&now);
        long long jitter     = actual_ns - target_ns;
        if (jitter < 0) jitter = -jitter;

        /* Jitter log */
        if (jitter_idx < LOG_SIZE)
            jitter_log[jitter_idx++] = jitter;
        if (jitter > max_jitter) {
            max_jitter = jitter;
            if (jitter > JITTER_LIMIT)
                fprintf(stderr,
                    "[WARN] jitter = %lld ns (limit: %lld ns)\n",
                    jitter, JITTER_LIMIT);
        }

        /* ── Motor control loop ── */
        read_encoder();
        compute_pid();
        write_pwm();
    }
    return NULL;
}

int main(void) {
    if (mlockall(MCL_CURRENT | MCL_FUTURE)) {
        perror("mlockall"); return 1;
    }

    pthread_t tid;
    pthread_create(&tid, NULL, motor_ctrl_thread, NULL);
    pthread_join(tid, NULL);

    printf("Max jitter observed: %lld ns\n", max_jitter);
    munlockall();
    return 0;
}

Yük altında test

stress-test.sh
# CPU, bellek ve I/O yükü oluştur
stress-ng --cpu 4 --io 2 --vm 1 --vm-bytes 256M &
STRESS_PID=$!

# RT uygulamayı çalıştır
sudo ./motor_control &
RT_PID=$!

# Aynı anda cyclictest ile bağımsız doğrulama
sudo cyclictest -p 85 -t 1 -a 2 -n -i 1000 -l 60000 -q

# Yükü durdur
kill $STRESS_PID

Sonuç metrikleri

ÖlçümStandart KernelPREEMPT_RT + Optimizasyon
Min latency12 µs3 µs
Avg latency95 µs8 µs
Max latency (yük altında)6.340 µs47 µs
100 µs hedef tutuldu mu?HayırEvet
NOT

Bu değerler test donanımına özgüdür. BIOS SMI aktifse, PREEMPT_RT kernel ile bile max latency 100 µs'yi aşabilir. Her üretim sistemi için hwlatdetect ile SMI taraması yapın ve sonuçları belgeleyin.

Bu bölümde

  • Boot parametreleri: isolcpus + nohz_full + C-state devre dışı bırakma
  • Motor kontrol RT task: SCHED_FIFO, CPU affinity, jitter log, TIMER_ABSTIME döngü
  • stress-ng ile yük altında doğrulama testi
  • Standart kernel vs RT kernel: min/avg/max latency karşılaştırması