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.
| Kategori | Deadline Miss Sonucu | Örnek |
|---|---|---|
| Soft RT | Performans düşüşü (degraded) | Video codec, UI refresh, ses işleme |
| Hard RT | Sistem hatası (catastrophic) | Motor kontrol, ABS, robotik eklem |
| Firm RT | Sonuç geçersiz, görmezden gelinir | Finans 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ği | Ad | Preemption noktası | Tipik latency |
|---|---|---|---|
| PREEMPT_NONE | No Forced Preemption | Yalnızca explicit yield / syscall dönüşü | ms – onlarca ms |
| PREEMPT_VOLUNTARY | Voluntary Kernel Preemption | Ekstra might_sleep() noktaları | yüzlerce µs |
| PREEMPT | Low-Latency Desktop | Tüm kernel kodu (spinlock dışı) | ~100 µs |
| PREEMPT_RT | Full Preemption | Her 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ı
| Kriter | PREEMPT_RT Linux | FreeRTOS / Zephyr |
|---|---|---|
| Worst-case latency | 10–50 µs (iyi konfigürasyonla) | 1–10 µs |
| POSIX uyumu | Tam | Kısmi / yok |
| Sürücü ekosistemi | Geniş (Linux driver tree) | Sınırlı BSP |
| Bellek koruma | MMU ile tam izolasyon | MPU / opsiyonel |
| Geliştirme araçları | GDB, perf, ftrace, valgrind | Sınırlı |
| Determinizm | Yüksek (bounded) | Çok yüksek (hard) |
| İşletim maliyeti | Yüksek (full OS) | Düşük (< 10 KB ROM) |
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:
# 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 ...
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:
# 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_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ğı)
# Ç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.
# 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:
# 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.
# 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ı
Örnek çıktı ve yorumlama
# 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 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
Ö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
#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.
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);
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
| Policy | Davranış | Kullanım |
|---|---|---|
| SCHED_FIFO | Aynı öncelikte preempt olmaz, önce gelen bekler | Deterministik RT task'lar |
| SCHED_RR | FIFO + round-robin time quantum | Eşit öncelikli RT task'lar arası adillik |
| SCHED_DEADLINE | CBS (Constant Bandwidth Server): runtime, deadline, period | Sporadic task'lar, EDF scheduling |
| SCHED_OTHER | CFS, normal nice-based | RT olmayan görevler |
Tam RT task iskeleti
#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.
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
# /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
# 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:
/* 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);
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.
# 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
# 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
# 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 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
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
| Kaynak | Tipik Etki | Tespit Yöntemi | Çözüm |
|---|---|---|---|
| SMI (System Management Interrupt) | 100 µs – 10 ms | hwlatdetect | BIOS'ta devre dışı bırak |
| CPU C-state geçişleri | 50 – 500 µs | cyclictest + powertop | C1 dışındakileri kapat |
| Turbo Boost / frequency scaling | 10 – 200 µs | cpufreq-info | Sabit frekans, performance gov. |
| Page fault | 1 – 50 µs | perf stat -e page-faults | mlockall + prefault |
| Lock contention | 1 – 100 µs | ftrace lockdep | Lock granülasyonu, lock-free yapı |
| Scheduling overhead | 1 – 20 µs | sched_switch trace | CPU affinity, isolcpus |
| Hyperthreading | 10 – 100 µs | cyclictest with HT on/off | BIOS'ta kapat veya core izole et |
hwlatdetect ile SMI tespiti
# 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ı
# 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
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 Öneri | Nedeni |
|---|---|---|
| C-States | C1 dışındakileri kapat | C3/C6 geçişi yüzlerce µs sürer |
| Turbo Boost | Kapat | Frekans değişimi latency jitter yaratır |
| Hyperthreading | Kapat veya izole et | Shared execution unit contention |
| SMI | Mümkünse kapat (donanıma bağlı) | BIOS kaynaklı non-maskable interrupt |
| NUMA Interleaving | Kapat (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.
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
#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
# 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çüm | Standart Kernel | PREEMPT_RT + Optimizasyon |
|---|---|---|
| Min latency | 12 µs | 3 µs |
| Avg latency | 95 µs | 8 µs |
| Max latency (yük altında) | 6.340 µs | 47 µs |
| 100 µs hedef tutuldu mu? | Hayır | Evet |
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ı