00 EDF teorisi — Earliest Deadline First
SCHED_DEADLINE politikasının temeli, 1973 yılında Liu ve Layland tarafından yayımlanan klasik gerçek zamanlı zamanlama teorisine dayanır. EDF algoritması, dinamik öncelikli optimal bir zamanlayıcıdır.
EDF Algoritması
EDF (Earliest Deadline First), hazır görevler arasında en erken mutlak son tarihe (absolute deadline) sahip olana CPU'yu verir. Öncelikler statik değil; her an yeniden hesaplanır. Bu dinamik yapı, EDF'yi sabit öncelikli algoritmalardan (Rate Monotonic, Deadline Monotonic) daha esnek kılar.
t=0 Görev A hazır (deadline t=10)
Görev B hazır (deadline t=15)
Görev C hazır (deadline t=8)
EDF kararı: C çalıştır (deadline=8 en küçük)
t=3 C tamamlandı
EDF kararı: A çalıştır (deadline=10 < 15)
t=7 A tamamlandı
EDF kararı: B çalıştır (deadline=15)
Sonuç: Tüm görevler kendi son tarihlerinden önce tamamlandı
Liu ve Layland Teoremi — Utilization Bound
Liu ve Layland (1973), periyodik görevler için EDF'nin kullanılabilirlik sınırını kanıtladı. N görev için toplam işlemci kullanımı aşağıdaki koşulu sağladığı sürece tüm son tarihler karşılanır:
U = toplam(Ci / Ti) <= 1 Ci: Görev i'nin en kötü durum çalışma süresi (WCET) Ti: Görev i'nin periyodu U: Toplam işlemci kullanımı Örnek: Görev A: C=2ms, T=10ms -> U_A = 0.20 (yuzde 20) Görev B: C=3ms, T=15ms -> U_B = 0.20 (yuzde 20) Görev C: C=1ms, T=5ms -> U_C = 0.20 (yuzde 20) Toplam U = 0.60 -> EDF planlanabilir (0.60 <= 1.0)
Rate Monotonic ile Karşılaştırma
Sabit öncelikli Rate Monotonic (RM) algoritmasının utilization bound'u N görev için yaklaşık N * (2^(1/N) - 1) formülüyle hesaplanır; N sonsuza giderken bu değer ln(2) ≈ 0.69'a yaklaşır. EDF ise teorik olarak 1.0'a (yüzde 100 CPU kullanımı) kadar planlanabilir yük destekler.
| Algoritma | Öncelik Tipi | Utilization Bound | Aşım Durumu |
|---|---|---|---|
| Rate Monotonic (RM) | Statik (periyoda göre) | ~0.69 (N sonsuza giderken) | Son tarih kaçırılabilir ama deterministik |
| Deadline Monotonic (DM) | Statik (deadline'a göre) | RM'den biraz yüksek | Deterministik |
| EDF (SCHED_DEADLINE) | Dinamik | 1.0 (teorik) | Herhangi bir görev kaçırabilir (domino etkisi) |
EDF'nin Pratik Sınırları
Teorik mükemmelliğine karşın EDF'nin uygulamada dikkat gerektiren yanları vardır. U > 1 durumunda EDF'de herhangi bir görev son tarihini kaçırabilirken RM'de yalnızca düşük öncelikli görevler kaçırır. Öte yandan WCET tahminindeki hatalar, tüm sistemin kararlılığını etkileyebilir. Linux'ta SCHED_DEADLINE bu sorunları admission control (kabul denetimi) mekanizmasıyla hafifletir.
01 SCHED_DEADLINE parametreleri
SCHED_DEADLINE, görevin zamanlama karakterini üç parametre üzerinden tanımlar. Bu parametreler CBS (Constant Bandwidth Server) algoritmasının implementasyonunda kullanılır.
sched_attr Yapısı
#include <linux/sched/types.h>
struct sched_attr {
__u32 size; /* struct boyutu (version uyumluluğu) */
__u32 sched_policy; /* SCHED_DEADLINE = 6 */
__u64 sched_flags; /* Ek bayraklar */
__s32 sched_nice; /* SCHED_OTHER için (DL'de kullanılmaz) */
__u32 sched_priority; /* RT öncelik (DL'de 0 olmak zorunda) */
/* SCHED_DEADLINE özel parametreler (nanosaniye cinsinden): */
__u64 sched_runtime; /* Ct: En kötü durum çalışma süresi */
__u64 sched_deadline; /* Dt: Göreli son tarih */
__u64 sched_period; /* Pt: Periyot */
};
Üç Temel Parametre
Parametre İlişkileri ve Kısıtlar
sched_runtime <= sched_deadline <= sched_period Görev aktivasyonu (t=0): |<---- sched_runtime ---->| |<---------- sched_deadline ---------->| |<-------------- sched_period --------------->| CBS sunucu modeli: - Bütçe (budget) = sched_runtime - Yenileme periyodu = sched_period - Deadline = aktivasyon + sched_deadline - Budget tükenince: throttle (periyot sonuna kadar bekle)
Parametre Seçim Rehberi
| Parametre | Önerilen Değer | Açıklama |
|---|---|---|
| sched_runtime | WCET * 1.1 - 1.5 | WCET'in biraz üstünde bir güvenlik payı. Fazla yüksek vermek CPU israfına yol açar. |
| sched_deadline | = sched_period (çoğunlukla) | Implicit deadline: deadline == period. Daha sıkı son tarih gerekiyorsa daha küçük verin. |
| sched_period | Görevin fiziksel aktivasyon süresi | Motor kontrol: 1ms, ses işleme: 5-10ms, video: 16ms (60 Hz). |
02 Görev oluşturma — sched_setattr syscall
sched_setattr syscall'ı, SCHED_DEADLINE politikasını bir işleme veya iş parçacığına atar. Bu işlem CAP_SYS_NICE ayrıcalığı gerektirir.
syscall Kullanımı
#include <linux/sched/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Glibc henüz sched_setattr'ı doğrudan sunmayabilir */
static int sched_setattr(pid_t pid,
const struct sched_attr *attr,
unsigned int flags)
{
return syscall(SYS_sched_setattr, pid, attr, flags);
}
static int sched_getattr(pid_t pid,
struct sched_attr *attr,
unsigned int size,
unsigned int flags)
{
return syscall(SYS_sched_getattr, pid, attr, size, flags);
}
Motor Kontrol Görevi Örneği
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/sched/types.h>
#define NSEC_PER_MSEC 1000000ULL
/* Motor kontrol döngüsü: 1 ms periyot, 300 us WCET */
static struct sched_attr motor_ctrl_attr = {
.size = sizeof(struct sched_attr),
.sched_policy = SCHED_DEADLINE,
.sched_flags = 0,
.sched_runtime = 300 * 1000ULL, /* 300 us cinsinden ns */
.sched_deadline = 1 * NSEC_PER_MSEC, /* 1 ms */
.sched_period = 1 * NSEC_PER_MSEC, /* 1 ms */
};
static void motor_control_task(void)
{
struct timespec next_wakeup;
int ret;
/* SCHED_DEADLINE politikasını uygula (PID=0: kendisi) */
ret = sched_setattr(0, &motor_ctrl_attr, 0);
if (ret < 0) {
perror("sched_setattr");
if (errno == EPERM)
fprintf(stderr, "CAP_SYS_NICE gerekli\n");
if (errno == EBUSY)
fprintf(stderr, "Admission control reddetti\n");
exit(1);
}
printf("Motor kontrol gorevi SCHED_DEADLINE modunda\n");
while (1) {
/* Gerçek zamanlı iş: motor PWM güncelleme */
update_motor_pwm();
read_encoder_feedback();
compute_pid_output();
/* Bir sonraki döneme kadar bekle */
/* sched_yield() SCHED_DEADLINE altında CBS süresini
tüketmeden sonraki periyoda geçişi tetikler */
sched_yield();
}
}
int main(void)
{
motor_control_task();
return 0;
}
İş Parçacığı (Thread) ile Kullanım
#include <pthread.h>
static void *rt_thread_func(void *arg)
{
struct sched_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = 500000ULL; /* 500 us */
attr.sched_deadline = 2000000ULL; /* 2 ms */
attr.sched_period = 5000000ULL; /* 5 ms */
/* Mevcut thread'e uygula */
if (sched_setattr(0, &attr, 0) < 0) {
perror("sched_setattr");
return NULL;
}
while (!should_exit) {
do_realtime_work();
sched_yield(); /* Periyot sonu: CPU'yu bırak */
}
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_attr_t tattr;
pthread_attr_init(&tattr);
/* SCHED_DEADLINE thread'lerin önden oluşturulmuş stack'i olmalı */
pthread_attr_setstacksize(&tattr, 1024 * 1024); /* 1 MB */
pthread_create(&tid, &tattr, rt_thread_func, NULL);
pthread_join(tid, NULL);
return 0;
}
CAP_SYS_NICE Gerekliliği
SCHED_DEADLINE, yanlış parametrelerle sistem kararlılığını bozabileceğinden root veya CAP_SYS_NICE kapasitesi gerektirir. Gömülü sistemlerde bu kapasiteyi özel bir hizmet daemon'ına vermek için:
# systemd service dosyasında:
[Service]
AmbientCapabilities=CAP_SYS_NICE
CapabilityBoundingSet=CAP_SYS_NICE
# Veya setcap ile ikili dosyaya atama:
setcap cap_sys_nice+ep /usr/bin/motor_ctrl
# Doğrulama:
getcap /usr/bin/motor_ctrl
03 Admission control mekanizması
SCHED_DEADLINE, CPU aşırı yüklenmesini önlemek için görev kabul denetimi (admission control) uygular. Yeni bir görev eklendiğinde kernel, toplam kullanımın izin verilen üst sınırı aşıp aşmadığını kontrol eder.
Utilization Hesabı
Her SCHED_DEADLINE görevi bir kullanım katkısı (utilization contribution) ekler. Bu katkı, görevin bant genişliğidir:
Görev katkısı = sched_runtime / sched_period Sistem toplam kullanımı: U_toplam = toplam(runtime_i / period_i), tüm DL görevleri için Kabul koşulu: U_toplam <= dl_bw.bw / 2^BW_SHIFT dl_bw.bw varsayılan = (1 - 0.03) * 2^BW_SHIFT yani CPU kapasitesinin yuzde 97'si DL görevlerine ayrılır yuzde 3 OS overhead için rezerve
Sistem Reddi Davranışı
Admission control başarısız olduğunda sched_setattr çağrısı EBUSY hatasıyla döner. Görev mevcut zamanlama politikasında (genellikle SCHED_NORMAL) kalmaya devam eder:
ret = sched_setattr(0, &attr, 0);
if (ret < 0) {
if (errno == EBUSY) {
/* Admission control reddi:
* Toplam DL kullanımı sınırı aştı.
* Seçenekler:
* 1. runtime'ı azalt
* 2. period'u artır
* 3. Başka bir görevi kaldır/azalt
*/
fprintf(stderr, "DL admission control reddi: "
"toplam kullanim siniri asildi\n");
} else if (errno == EINVAL) {
/* Parametre geçersiz: runtime > deadline vb. */
fprintf(stderr, "Gecersiz DL parametresi\n");
}
return -1;
}
Admission Control Sınırlarını Görüntüleme
# Sistem geneli DL bant genişliği limiti:
cat /proc/sys/kernel/sched_rt_runtime_us
# -1: sınırsız
# 950000: toplam RT/DL görevleri maks 950ms/saniye (yuzde 95)
cat /proc/sys/kernel/sched_rt_period_us
# 1000000 (1 saniye)
# Admission oranı: sched_rt_runtime / sched_rt_period
# 950000 / 1000000 = 0.95 = yuzde 95
# Mevcut DL kullanımını görüntüle (/proc veya ftrace ile):
cat /proc/sched_debug | grep -A5 "dl_bw"
Admission Control Sınırını Ayarlama
# DL + RT görevlerin toplamda CPU'nun yüzde 90'ını kullanmasına izin ver:
echo 900000 > /proc/sys/kernel/sched_rt_runtime_us
# Sınırsız mod (üretimde önerilmez — sistem kilitlenebilir):
echo -1 > /proc/sys/kernel/sched_rt_runtime_us
# CPU başına admission limit (NUMA/SMP sistemler):
# /proc/sys/kernel/sched_rt_* değerleri sistemin tamamına uygulanır
# Per-CPU limit için cpuset + dl_bw kombinasyonu gerekir
Admission Control Örnek Hesabı
Mevcut sistem: 4 çekirdek, sched_rt_runtime_us=950000 Görev A: runtime=2ms, period=10ms -> katkı = 0.20 Görev B: runtime=3ms, period=15ms -> katkı = 0.20 Görev C: runtime=1ms, period=5ms -> katkı = 0.20 Toplam mevcut U = 0.60 Yeni görev D: runtime=3ms, period=8ms -> katkı = 0.375 Toplam olurdu: 0.60 + 0.375 = 0.975 > 0.95 (limit) Sonuç: EBUSY — Görev D reddedildi Düzeltme: Görev D: runtime=3ms, period=10ms -> katkı = 0.30 Toplam: 0.60 + 0.30 = 0.90 <= 0.95 Sonuç: Kabul edildi
04 CPU pinning ve bant genişliği yönetimi
Çok çekirdekli gömülü sistemlerde SCHED_DEADLINE görevlerini belirli CPU'lara bağlamak hem öngörülebilirliği artırır hem de bant genişliği yönetimini kolaylaştırır.
SCHED_DEADLINE ve CPU Pinning
SCHED_DEADLINE görevleri varsayılan olarak tüm CPU'lara migrate edebilir. Ancak gerçek zamanlı sistemlerde göçü (migration) sınırlamak, önbellek tutarlılığı ve gecikme öngörülebilirliği açısından avantajlıdır.
#include <sched.h>
/* CPU 2 ve 3'e SCHED_DEADLINE görevi bağla */
cpu_set_t cpus;
CPU_ZERO(&cpus);
CPU_SET(2, &cpus);
CPU_SET(3, &cpus);
/* Önce affinity ayarla */
if (sched_setaffinity(0, sizeof(cpus), &cpus) < 0) {
perror("sched_setaffinity");
return -1;
}
/* Sonra SCHED_DEADLINE uygula */
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_DEADLINE,
.sched_runtime = 1000000ULL, /* 1 ms */
.sched_deadline = 5000000ULL, /* 5 ms */
.sched_period = 5000000ULL, /* 5 ms */
};
if (sched_setattr(0, &attr, 0) < 0) {
perror("sched_setattr");
return -1;
}
cpuset ile İzole CPU Havuzu
cgroup v1 cpuset veya cgroup v2 cpuset.cpus ile belirli CPU'ları yalnızca gerçek zamanlı görevlere ayırmak mümkündür. Bu yaklaşım, yönetim görevleri ve kesintilerin RT çekirdeklere girmesini engeller:
# cgroup v1 ile RT CPU havuzu oluşturma:
mkdir /sys/fs/cgroup/cpuset/realtime
echo 2-3 > /sys/fs/cgroup/cpuset/realtime/cpuset.cpus
echo 0 > /sys/fs/cgroup/cpuset/realtime/cpuset.mems
echo 1 > /sys/fs/cgroup/cpuset/realtime/cpuset.cpu_exclusive
# PID'yi RT grubuna ekle:
echo MOTOR_CTRL_PID > /sys/fs/cgroup/cpuset/realtime/cgroup.procs
# CPU 2 ve 3'ü genel zamanlayıcıdan izole et (önyükleme parametresi):
# isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3
dl_bw.bw — Per-CPU Bant Genişliği Limiti
Admission control, SCHED_DEADLINE görevlerinin CPU başına tüketebileceği maksimum bant genişliğini dl_bw yapısıyla takip eder. cpuset ile birden fazla CPU'yu bir göreve tahsis ettiğinizde, toplam bant genişliği o CPU'ların sayısıyla çarpılır:
/* Çekirdek içi dl_bw yapısı (basitleştirilmiş) */
struct dl_bw {
raw_spinlock_t lock;
u64 bw; /* Maksimum izin verilen toplam bant genişliği */
u64 total_bw; /* Mevcut kullanılan toplam bant genişliği */
};
/* Per-root-domain bant genişliği kontrolü:
* 2 CPU'lu bir root domain için bant genişliği 2x artar.
* Gorev affinity 2 CPU'ya yayılırsa 2x bant genişliğinden yararlanır.
*/
/proc/sys/kernel/sched_rt_* Parametreleri
| Parametre | Varsayılan | Açıklama |
|---|---|---|
| sched_rt_period_us | 1000000 (1 s) | RT/DL bant genişliği ölçüm periyodu (mikrosaniye) |
| sched_rt_runtime_us | 950000 (950 ms) | Periyot başına RT/DL görevlere ayrılan maksimum CPU süresi. -1 = sınırsız |
| sched_deadline_period | Görev başına | SCHED_DEADLINE görevine özgü; sched_setattr ile ayarlanır |
CPU İzolasyonu ve Kesinti Denetimi
# Önyükleme zamanı tam izolasyon (Yocto/Buildroot bootargs):
isolcpus=2,3 # CPU 2,3 genel zamanlayıcıdan çıkar
nohz_full=2,3 # Zamanlayıcı tick'i devre dışı (tickless)
rcu_nocbs=2,3 # RCU callback'leri bu CPU'lara gönderilmez
# Çalışma zamanında donanım kesintilerini CPU 0,1'e yönlendir:
for irq in /proc/irq/*/smp_affinity; do
echo 3 > $irq 2>/dev/null /* CPU 0 ve 1 (binary 11) */
done
# RT görevin CPU 2'de kesintisiz çalıştığını doğrula:
taskset -p MOTOR_CTRL_PID
cat /proc/MOTOR_CTRL_PID/status | grep Cpus_allowed
05 GRUB — Kullanılmayan Bant Genişliğini Geri Kazanma
GRUB (Greedy Reclaiming of Unused Bandwidth), SCHED_DEADLINE görevleri bütçelerini tam olarak tüketmediğinde kalan bant genişliğini diğer görevlere aktaran bir mekanizmadır.
GRUB Çalışma Prensibi
CBS (Constant Bandwidth Server) modelinde her görevin bir bütçesi (runtime) ve bir yenileme periyodu (period) vardır. Görev, bütçesini tükenmeden işini bitirip sched_yield() çağırdığında kalan bütçe atıl kalabilir. GRUB bu atıl bütçeyi sisteme geri kazandırır.
Görev A: runtime=3ms, period=10ms
t=0: Görev A çalışmaya başlar
t=1: Görev A işini tamamlar (1ms kullandı, 2ms kaldı)
sched_yield() çağrılır
GRUB etkin değilse:
Kalan 2ms bütçe heba olur, toplam CPU kullanımı yuzde 10 olur
GRUB etkin (aktif bant genişliği takibi):
CBS aktif kalır, kalan bütçe düşük öncelikli görevlere sunulur
Gerçek CPU kullanımı yuzde 10'un altında, sistem daha verimli
Aktif Bant Genişliği (Active Bandwidth)
Linux'ta GRUB, aktif bant genişliği (dbf_active) kavramıyla uygulanır. Yalnızca çalışmak isteyen (runnable) görevlerin bant genişliği hesaba katılır. Bekleyen bir görevin bant genişliği aktif değildir ve diğer görevler bu fırsatı değerlendirmek için zamanlayıcı tarafından yönlendirilir:
/* Kernel içi GRUB mantığı (basitleştirilmiş):
*
* Her döngüde aktif_bw hesaplanır:
* aktif_bw = toplam(runtime_i / period_i), yalnızca runnable DL görevleri
*
* Mevcut görev çalışırken bütçesi şu hızda azalır:
* azalma_hizi = aktif_bw / toplam_bw
*
* aktif_bw < toplam_bw ise bütçe daha yavaş tükenir
* (bant genişliği "geri kazanılıyor" demektir)
*/
GRUB Etkisini Gözlemleme
# Bir DL görevinin bütçe tüketim hızını izle:
# /proc/PID/sched çıktısında se.sum_exec_runtime'ı takip et
cat /proc/MOTOR_PID/sched | grep sum_exec
# ftrace ile CBS bütçe yenileme olaylarını izle:
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_switch/enable
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable
cat /sys/kernel/debug/tracing/trace | grep "motor_ctrl"
GRUB ve SCHED_NORMAL Görevleri
GRUB yalnızca SCHED_DEADLINE katmanı içinde çalışır. SCHED_NORMAL (CFS) görevleri, SCHED_DEADLINE görevleri CPU'yu boş bıraktığında çalışma fırsatı bulur. Bu mekanizma gömülü sistemlerde arka plan görevlerinin (log daemon, network stack) gerçek zamanlı görevler boştayken kaynak kullanmasını sağlar ve sistem genelinde CPU verimliliğini artırır.
Bütçe Aşımı ve Throttling
/* Görev runtime bütçesini aşarsa ne olur? */
/* CBS modeli: bütçe tükenince görev throttle edilir */
/* Throttle: görev SCHED_DEADLINE listesinden çıkar,
* bir sonraki periyot başlayana kadar çalışamaz */
/* Bu durumu tespit etmek için: */
# cat /proc/PID/sched | grep throttled
# veya perf stat ile:
perf stat -e sched:sched_stat_sleep,sched:sched_stat_blocked \
-p MOTOR_PID sleep 5
06 SCHED_DEADLINE ve SCHED_FIFO karşılaştırması
Gömülü gerçek zamanlı görev tasarımında SCHED_FIFO ile SCHED_DEADLINE arasında seçim yapmak, sistemin gereksinimlerine bağlıdır. Her iki politikanın güçlü ve zayıf yönlerini anlamak doğru karar almayı sağlar.
Temel Farklılıklar
| Özellik | SCHED_FIFO | SCHED_DEADLINE |
|---|---|---|
| Öncelik tipi | Statik (1-99) | Dinamik (deadline'a göre) |
| Preemption | Daha yüksek öncelik önce | Daha yakın deadline önce |
| CPU kullanım garantisi | Yok (açlık riski) | Var (admission control) |
| Throttling | Yok | Var (bütçe aşınca) |
| Starvation riski | Düşük öncelikli görevler açlık çekebilir | Yok (admission ile sınırlanmış) |
| Konfigürasyon karmaşıklığı | Düşük (tek sayı: öncelik) | Orta (3 parametre + WCET ölçümü) |
| Teorik temel | Rate Monotonic uyumlu | EDF optimal |
| Deadline kaçırma davranışı | Öncelik sırasına göre devam | Throttle (sonraki periyot bekler) |
Gecikme Garantisi
SCHED_DEADLINE, CBS mekanizması sayesinde bir görevin periyot başına kullanacağı CPU süresini garanti eder. Başka bir görev ne kadar yoğun olursa olsun, DL görevi bütçesi kadar CPU alacaktır. SCHED_FIFO'da ise daha yüksek öncelikli bir görev sürekli çalışırsa düşük öncelikli görev hiç CPU almayabilir:
/* SCHED_FIFO ile starvation riski */
/* Görev A: SCHED_FIFO öncelik=99, sürekli çalışıyor */
/* Görev B: SCHED_FIFO öncelik=50 */
/* Sonuç: Görev A çalıştığı sürece Görev B çalışamaz */
/* SCHED_DEADLINE ile garanti */
/* Görev A: DL runtime=8ms, period=10ms */
/* Görev B: DL runtime=1ms, period=10ms */
/* Sonuç: Görev B her 10ms'de en az 1ms CPU garantisi alır */
Hangi Durumda Hangisi?
SCHED_FIFO'dan SCHED_DEADLINE'a Geçiş
/* Mevcut SCHED_FIFO görevini SCHED_DEADLINE'a dönüştürme */
/* Adım 1: WCET ölçümü — en kötü durumda kaç ns çalışıyor? */
struct timespec t_start, t_end;
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t_start);
do_realtime_work();
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &t_end);
long wcet_ns = (t_end.tv_sec - t_start.tv_sec) * 1e9
+ (t_end.tv_nsec - t_start.tv_nsec);
/* Adım 2: Güvenlik payı ile runtime belirle */
long runtime = wcet_ns * 1.3; /* yüzde 30 pay */
/* Adım 3: sched_setattr ile politikayı değiştir */
struct sched_attr attr = {
.size = sizeof(attr),
.sched_policy = SCHED_DEADLINE,
.sched_runtime = runtime,
.sched_deadline = period_ns,
.sched_period = period_ns,
};
sched_setattr(0, &attr, 0);
07 Gömülü senaryo örneği
Bu bölümde endüstriyel bir motor kontrol sisteminde üç farklı gerçek zamanlı görevin SCHED_DEADLINE ile nasıl planlanacağı, Rate Monotonic ile karşılaştırılarak incelenir.
Sistem Tanımı
Bir CNC tezgahı denetleyicisinde üç temel gerçek zamanlı görev bulunmaktadır:
| Görev | Açıklama | WCET | Periyot | Son Tarih |
|---|---|---|---|---|
| motor_ctrl | Eksen motoru PWM ve encoder okuma | 300 µs | 1 ms | 1 ms |
| sensor_read | ADC sensör verisi toplama ve filtre | 500 µs | 5 ms | 5 ms |
| comm_task | EtherCAT / Modbus çerçeve gönderme | 800 µs | 10 ms | 10 ms |
Utilization Analizi
EDF utilization hesabı: U_motor = 300µs / 1000µs = 0.30 (yuzde 30) U_sensor = 500µs / 5000µs = 0.10 (yuzde 10) U_comm = 800µs / 10000µs = 0.08 (yuzde 8) U_toplam = 0.48 = yuzde 48 EDF planlanabilir? 0.48 <= 1.0 ✓ Rate Monotonic utilization bound (N=3): U_rm_bound = 3 * (2^(1/3) - 1) ≈ 0.78 RM ile planlanabilir? 0.48 <= 0.78 ✓ (Her iki algoritma da çalışır, EDF daha geniş marjla)
SCHED_DEADLINE Yapılandırması
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <linux/sched/types.h>
#define US_TO_NS(x) ((x) * 1000ULL)
/* Görev parametreleri tablosu */
struct task_params {
const char *name;
unsigned long runtime_ns;
unsigned long deadline_ns;
unsigned long period_ns;
void (*work_fn)(void);
};
static struct task_params tasks[] = {
{
.name = "motor_ctrl",
.runtime_ns = US_TO_NS(390), /* 300us + yüzde 30 pay */
.deadline_ns = US_TO_NS(1000),
.period_ns = US_TO_NS(1000),
.work_fn = do_motor_control,
},
{
.name = "sensor_read",
.runtime_ns = US_TO_NS(650), /* 500us + yüzde 30 pay */
.deadline_ns = US_TO_NS(5000),
.period_ns = US_TO_NS(5000),
.work_fn = do_sensor_read,
},
{
.name = "comm_task",
.runtime_ns = US_TO_NS(1040), /* 800us + yüzde 30 pay */
.deadline_ns = US_TO_NS(10000),
.period_ns = US_TO_NS(10000),
.work_fn = do_comm_frame,
},
};
static void *rt_task_main(void *arg)
{
struct task_params *p = arg;
struct sched_attr attr;
memset(&attr, 0, sizeof(attr));
attr.size = sizeof(attr);
attr.sched_policy = SCHED_DEADLINE;
attr.sched_runtime = p->runtime_ns;
attr.sched_deadline = p->deadline_ns;
attr.sched_period = p->period_ns;
if (sched_setattr(0, &attr, 0) < 0) {
perror(p->name);
return NULL;
}
printf("[%s] SCHED_DEADLINE aktif: "
"runtime=%luus period=%lums\n",
p->name,
p->runtime_ns / 1000,
p->period_ns / 1000000);
while (1) {
p->work_fn();
sched_yield(); /* Periyot sonu */
}
return NULL;
}
int main(void)
{
pthread_t tids[3];
int i;
for (i = 0; i < 3; i++) {
pthread_create(&tids[i], NULL, rt_task_main, &tasks[i]);
}
for (i = 0; i < 3; i++)
pthread_join(tids[i], NULL);
return 0;
}
Rate Monotonic ile Karşılaştırma
08 Hata ayıklama ve izleme
SCHED_DEADLINE görevlerinin davranışını izlemek, deadline kaçırmalarını ve bütçe aşımlarını tespit etmek için Linux'un tracing altyapısını etkin kullanmak gerekir.
trace_sched_switch ile Zamanlama İzleme
# ftrace ile zamanlama olaylarını kaydet:
cd /sys/kernel/debug/tracing
# Gerekli olayları etkinleştir:
echo 1 > events/sched/sched_switch/enable
echo 1 > events/sched/sched_wakeup/enable
echo 1 > events/sched/sched_wakeup_new/enable
# İzlemeyi başlat:
echo 1 > tracing_on
# Birkaç saniye bekle, sonra durdur:
sleep 5
echo 0 > tracing_on
# Sonuçları incele — motor_ctrl görevini filtrele:
grep "motor_ctrl" trace | head -30
# Örnek çıktı:
# kworker-5 [002] d... 1234.567890: sched_switch:
# prev_comm=motor_ctrl prev_pid=1234 prev_prio=0 prev_state=S
# next_comm=sensor_read next_pid=1235 next_prio=0
/proc/PID/sched ile Görev İstatistikleri
# Görev zamanlama istatistiklerini görüntüle:
cat /proc/MOTOR_PID/sched
# Önemli alanlar:
# se.sum_exec_runtime : Toplam çalışma süresi (ns)
# nr_involuntary_switches: Zorla bağlam değişimi sayısı
# nr_voluntary_switches : Gönüllü bağlam değişimi (sched_yield)
# policy : Zamanlama politikası (6 = SCHED_DEADLINE)
# dl.runtime : Mevcut bütçe (ns)
# dl.deadline : Mutlak son tarih (ns, CLOCK_MONOTONIC)
# dl.period : Periyot (ns)
# dl.flags : CBS bayrakları
# SCHED_DEADLINE bilgisini doğrula:
cat /proc/MOTOR_PID/sched | grep -E "policy|dl\."
Deadline Kaçırma Tespiti
Linux'ta SCHED_DEADLINE, deadline kaçırmayı kernel log'una yazar. Bunu izlemek için:
# Kernel log'da deadline kaçırma:
# kernel: sched: DEADLINE period overrun for PID 1234 (motor_ctrl)
# kernel: sched: dl_runtime=300000 dl_deadline=1000000 dl_period=1000000
# Gerçek zamanlı izleme:
dmesg -w | grep -i "deadline\|DEADLINE"
# Daha ayrıntılı: trace event aktifleştir
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_deadline_exceeded/enable
cat /sys/kernel/debug/tracing/trace | grep "deadline_exceeded"
latency-top ile Gecikme Analizi
# latency-top kurulumu ve kullanımı:
# (latency-top aracı kernel CONFIG_LATENCYTOP gerektirir)
CONFIG_LATENCYTOP=y
CONFIG_SCHEDSTATS=y
# Çalıştır (root gerekli):
latency-top
# Alternatif: cyclictest ile SCHED_DEADLINE gecikme ölçümü
# cyclictest, gerçek zamanlı görev gecikmelerini mikrosaniye
# hassasiyetiyle ölçer:
cyclictest --policy=dl \
--dl-runtime=300 \
--dl-deadline=1000 \
--dl-period=1000 \
--loops=10000 \
--histogram=1000
# Çıktı: min/avg/max gecikme (microsaniye)
# T: 0 ( 1234) P: 0 I:1000 C: 10000
# Min: 3 Avg: 7 Max: 42
perf ile Zamanlayıcı Profili
# SCHED_DEADLINE görev geçişlerini perf ile izle:
perf sched record -a -- sleep 5
perf sched latency | head -20
# Bağlam değişim istatistikleri:
perf sched timehist -p MOTOR_PID | head -20
# Örnek çıktı:
# time cpu task name wait time sch delay run time
# ------- --- --------- --------- --------- --------
# 1234.5 002 motor_ctrl 0.012 0.003 0.287 ms
Sysctl ile Dinamik Tuning
# Zamanlayıcı istatistiklerini etkinleştir:
echo 1 > /proc/sys/kernel/sched_schedstats
# CFS bandwidth throttling'i izle (DL görevleri etkiler):
cat /proc/schedstat | grep -A3 "cpu2"
# Admission control sınırını acil artır (geçici):
echo 990000 > /proc/sys/kernel/sched_rt_runtime_us
# Tüm çekirdeklerin SCHED_DEADLINE durumunu görüntüle:
cat /proc/sched_debug | grep -E "runnable_avg|dl_bw|nr_deadline"