00 Xenomai neden var
Genel amaçlı Linux çekirdeği, en iyi PREEMPT-RT yamasıyla bile belirli gerçek zamanlı görevler için yeterli gecikme garantisi veremez.
PREEMPT-RT'nin sınırları
PREEMPT-RT yaması Linux'u "soft real-time" düzeye taşır: büyük çoğunlukla 50–200 µs jitter elde edilir, ancak şu nedenlerle deterministik alt sınır garanti edilemez:
Hard RT gereksinimleri
Bazı endüstriyel ve güvenlik kritik uygulamalar deterministik worst-case execution time (WCET) ister:
| Uygulama | Tipik deadline | Kabul edilebilir jitter |
|---|---|---|
| EtherCAT master (servo kontrol) | 1 ms | < 10 µs |
| Dijital osiloskopin örnekleme loop'u | 100 µs | < 1 µs |
| Motor akım kontrolü (FOC) | 50 µs | < 5 µs |
| Ses işleme (96 kHz, 4 kanal) | ~10 µs | < 2 µs |
| Güç elektroniği PWM zamanlaması | 20 µs | < 500 ns |
Dual-kernel yaklaşım
Xenomai'nin çözümü, Linux'u küçük bir gerçek zamanlı mikro-çekirdeğin (Cobalt) altında "idle task" olarak çalıştırmaktır. Donanım kesmeleri önce Cobalt'a gider; Cobalt RT görevini işledikten sonra Linux'a iletir.
Donanım IRQ
│
▼
Cobalt (Xenomai mikro-çekirdek)
│ RT görevleri burada çalışır — deterministic
│ Linux'a iletilmemiş IRQ'lar yok sayılır
▼
Linux (co-kernel — en yüksek öncelikli idle task)
│ Normal süreçler, sürücüler, ağ, dosya sistemi
▼
Donanım (I-pipe / Dovetail interrupt pipeline)
PREEMPT-RT vs Xenomai karşılaştırması
| Kriter | PREEMPT-RT | Xenomai (Cobalt) |
|---|---|---|
| Minimum latency | ~10–30 µs | < 1 µs (ideal) |
| Worst-case jitter | 100–500 µs yük altında | 2–10 µs tipik |
| POSIX uyumu | Tam POSIX | POSIX skin (kısmi) |
| Sürücü uyumu | Tüm Linux sürücüleri | RTDM dışı sürücüler "mode switch" üretir |
| Öğrenme eğrisi | Düşük — standart Linux geliştirme | Yüksek — dual-kernel kavramları |
| Kernel yaması | Standart, upstream'e giriyor | Dovetail yaması gerekir |
| Kullanım alanı | Genel soft-RT, IIoT | Hard-RT, servo, güç elektroniği |
01 Xenomai mimarisi
Xenomai 3'ün mimarisi iki katmandan oluşur: çekirdek tarafında Dovetail interrupt pipeline ve Cobalt mikro-çekirdek; kullanıcı tarafında libcobalt ve çeşitli API skin'ler.
I-pipe'tan Dovetail'e
Xenomai 2, Linux çekirdeğini "I-pipe" (interrupt pipeline) yamasıyla değiştiriyordu. Xenomai 3.x, Linux 5.4+ ile birlikte daha temiz ve upstream dostu olan Dovetail arayüzüne geçti. Dovetail, Linux'un IRQ akışına iki ayrı "stage" (sahne) ekler:
Donanım IRQ gelir
│
┌────▼────────────────────────┐
│ Dovetail IRQ pipeline │
│ ┌─────────────────────┐ │
│ │ OOB stage (Cobalt) │◄── RT görevleri, RT timer
│ └──────────┬──────────┘ │
│ │ irq_exit_oob()│
│ ┌──────────▼──────────┐ │
│ │ In-band (Linux) │◄── normal sürücüler, syscall
│ └─────────────────────┘ │
└─────────────────────────────┘
Cobalt çekirdeği
Cobalt, Linux çekirdeği modülü olarak yüklenen küçük bir RTOS'tur. Temel bileşenleri:
Kullanıcı alanı: libcobalt ve skin'ler
| Bileşen | Açıklama | Başlık |
|---|---|---|
| libcobalt | Cobalt syscall wrapper; glibc'nin üstüne biner | dahili |
| POSIX skin | pthread, mq_*, sem_* — mevcut kodu port etmek kolay | pthread.h (cobalt) |
| Alchemy API | VxWorks/pSOS'a benzer RT_TASK, RT_MUTEX, RT_SEM API'si | alchemy/*.h |
| VXWORKS skin | VxWorks uyumu (taskSpawn, semTake vb.) | vxworks/*.h |
| pSOS skin | pSOS+ uyumu (t_start, sm_p vb.) | psos/*.h |
Mode switch (alan geçişi)
Bir RT görevinin Linux syscall'u çağırması (örn. printf, open, malloc) "mode switch" tetikler: görev OOB stage'den in-band'e geçer ve RT güvencesi kaybolur. Bu durum /proc/xenomai/stat'ta MSW sütununda sayılır.
02 Xenomai 3 kurulumu
Xenomai kurulumu üç aşamadan oluşur: Linux çekirdeğine Dovetail yaması uygulamak, yamayı içeren çekirdeği derlemek ve Xenomai kullanıcı alanı kitaplıklarını derlemek.
Gereksinimler
Dovetail yamasını uygulama
# 1. Kaynak indirme
wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.80.tar.xz
wget https://xenomai.org/downloads/xenomai/stable/xenomai-3.2.3.tar.bz2
tar xf linux-6.1.80.tar.xz
tar xf xenomai-3.2.3.tar.bz2
# 2. Dovetail yamasını uygula
cd xenomai-3.2.3
scripts/prepare-kernel.sh \
--linux=../linux-6.1.80 \
--arch=arm64 \
--dovetail=dovetail-6.1-arm64.patch
# 3. Kernel konfigürasyonu
cd ../linux-6.1.80
make ARCH=arm64 defconfig
# Xenomai için zorunlu seçenekler
# CONFIG_XENOMAI=y
# CONFIG_DOVETAIL=y
# CONFIG_IPIPE_LEGACY=n (Dovetail kullanıyoruz)
# CONFIG_HZ_1000=y (daha yüksek timer çözünürlüğü)
# CONFIG_NO_HZ_FULL=n (RT ile uyumsuz)
# CONFIG_CPU_FREQ=n (frekans ölçekleme RT gecikme üretir)
# CONFIG_CPU_IDLE=n (C-state gecikmesi engellenir)
make ARCH=arm64 menuconfig
Çekirdek derleme
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
-j$(nproc) Image modules dtbs
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=../rootfs modules_install
# Çekirdek imajını kopyala
cp arch/arm64/boot/Image ../rootfs/boot/Image-xenomai
cp arch/arm64/boot/dts/*/my-board.dtb ../rootfs/boot/
Xenomai kullanıcı alanı derleme
cd xenomai-3.2.3
./configure \
--host=aarch64-linux-gnu \
--with-core=cobalt \
--enable-smp \
--disable-lores-clock \
CFLAGS="-O2 -march=armv8-a" \
LDFLAGS="-Wl,-rpath,/usr/xenomai/lib"
make -j$(nproc)
make DESTDIR=../rootfs install
# Kurulan dosyalar:
# /usr/xenomai/lib/libcobalt.so — RT syscall wrapper
# /usr/xenomai/lib/libalchemy.so — Alchemy API
# /usr/xenomai/bin/xeno-latency — latency test aracı
# /usr/xenomai/bin/xeno-config — derleme bayrakları
Uygulama derleme
# xeno-config ile bayrakları al
CFLAGS=$(xeno-config --skin=posix --cflags)
LDFLAGS=$(xeno-config --skin=posix --ldflags)
aarch64-linux-gnu-gcc $CFLAGS -o rt_task my_rt_task.c $LDFLAGS
# Alternatif: Alchemy skin
CFLAGS=$(xeno-config --skin=alchemy --cflags)
LDFLAGS=$(xeno-config --skin=alchemy --ldflags)
aarch64-linux-gnu-gcc $CFLAGS -o alchemy_task alchemy_task.c $LDFLAGS
Çalışma zamanı gereksinimleri
# RT görevleri için önerilen sistem ayarları (/etc/sysctl.conf)
kernel.sched_rt_runtime_us = -1 # RT bandwidth sınırı kaldır
# Kullanıcı limitleri (/etc/security/limits.conf)
@realtime - rtprio 99
@realtime - memlock unlimited
# Modüller
modprobe xenomai
modprobe xeno_cobalt
03 POSIX skin
Xenomai'nin POSIX skin'i, mevcut POSIX çok iş parçacıklı kodu Cobalt üzerine minimal değişiklikle taşımak için en uygun yoldur.
RT thread oluşturma
#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <sys/mman.h>
#include <xenomai/init.h>
#define TASK_PERIOD_NS 1000000LL /* 1 ms */
#define STACK_SIZE 65536
static void *rt_loop(void *arg)
{
struct timespec next;
int overruns = 0;
/* Cobalt POSIX: clock_gettime ile zamanı al */
clock_gettime(CLOCK_MONOTONIC, &next);
for (;;) {
/* Periyodik bekleme */
next.tv_nsec += TASK_PERIOD_NS;
if (next.tv_nsec >= 1000000000LL) {
next.tv_nsec -= 1000000000LL;
next.tv_sec++;
}
int ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
if (ret == EINTR) {
overruns++;
continue;
}
/* --- Gerçek zamanlı görev gövdesi --- */
do_control_loop();
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t thread;
pthread_attr_t attr;
struct sched_param param;
/* Xenomai başlatma */
xenomai_init(&argc, &argv);
/* Tüm belleği kilitle — sayfa hatasını önle */
mlockall(MCL_CURRENT | MCL_FUTURE);
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, STACK_SIZE);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
param.sched_priority = 80;
pthread_attr_setschedparam(&attr, ¶m);
pthread_create(&thread, &attr, rt_loop, NULL);
pthread_join(thread, NULL);
return 0;
}
Priority Inheritance Protocol (PIP)
Xenomai POSIX skin'de mutex oluşturulurken protokol açıkça belirtilmelidir; aksi hâlde öncelik terslemesi (priority inversion) oluşabilir.
#include <pthread.h>
pthread_mutex_t rt_mutex;
pthread_mutexattr_t mattr;
void setup_rt_mutex(void)
{
pthread_mutexattr_init(&mattr);
/* Priority Inheritance protokolü */
pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT);
/* Robust mutex — sahip thread ölürse kurtarılabilir */
pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
/* Priority Protect (ceiling) alternatif:
* pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_PROTECT);
* pthread_mutexattr_setprioceiling(&mattr, 90);
*/
pthread_mutex_init(&rt_mutex, &mattr);
pthread_mutexattr_destroy(&mattr);
}
RT mesaj kuyruğu
#include <mqueue.h>
mqd_t create_rt_mqueue(const char *name)
{
struct mq_attr attr = {
.mq_flags = 0,
.mq_maxmsg = 16,
.mq_msgsize = 64,
.mq_curmsgs = 0,
};
/* Cobalt POSIX mq: /cobalt/mqueue ad alanında */
mqd_t mq = mq_open(name, O_CREAT | O_RDWR, 0666, &attr);
if (mq == (mqd_t)-1) {
perror("mq_open");
return (mqd_t)-1;
}
return mq;
}
Mode switch kaçınma kuralları
04 Alchemy API
Alchemy API, VxWorks ve pSOS'tan alışkın geliştiriciler için daha sezgisel bir RT programlama modeli sunar; RT_TASK, RT_MUTEX, RT_SEM, RT_QUEUE gibi nesne türleri içerir.
RT_TASK oluşturma ve periyodik çalıştırma
#include <alchemy/task.h>
#include <alchemy/timer.h>
RT_TASK control_task;
void control_loop(void *arg)
{
RTIME period = 1000000; /* 1 ms, nanosaniye cinsinden */
/* Periyodik göreve dönüştür */
rt_task_set_periodic(NULL, TM_NOW, period);
for (;;) {
rt_task_wait_period(NULL);
/* --- Kontrol algoritması --- */
read_encoder();
compute_pid();
write_pwm();
}
}
int main(void)
{
/* RT_TASK oluştur: isim, yığın boyutu, öncelik, mod */
rt_task_create(&control_task, "control", 0, 80, T_JOINABLE);
/* Görevi başlat */
rt_task_start(&control_task, control_loop, NULL);
/* Ana thread Cobalt değil; rt_task_join ile bekle */
rt_task_join(&control_task);
return 0;
}
RT_MUTEX
#include <alchemy/mutex.h>
RT_MUTEX shared_mutex;
void init_resources(void)
{
/* Mutex oluştur — priority inheritance otomatik */
rt_mutex_create(&shared_mutex, "shared_mtx");
}
void producer_task(void *arg)
{
for (;;) {
rt_mutex_acquire(&shared_mutex, TM_INFINITE);
update_shared_buffer();
rt_mutex_release(&shared_mutex);
rt_task_sleep(500000); /* 500 µs */
}
}
void cleanup(void)
{
rt_mutex_delete(&shared_mutex);
}
RT_SEM ve RT_QUEUE
#include <alchemy/sem.h>
#include <alchemy/queue.h>
RT_SEM irq_sem;
RT_QUEUE data_queue;
/* Interrupt handler tarafından sinyal gönderilir */
void irq_handler_rt(void)
{
rt_sem_v(&irq_sem); /* semafor arttır */
}
void processing_task(void *arg)
{
struct data_packet pkt;
ssize_t sz;
for (;;) {
/* IRQ gelene kadar bekle */
rt_sem_p(&irq_sem, TM_INFINITE);
/* Kuyruğa veri gönder (non-blocking) */
sz = rt_queue_write(&data_queue, &pkt, sizeof(pkt), Q_NORMAL);
if (sz < 0)
rt_printf("Queue full: %d\n", (int)sz);
}
}
void init_ipc(void)
{
rt_sem_create(&irq_sem, "irq_sem", 0, S_PRIO);
/* 16 mesaj, her biri 128 byte */
rt_queue_create(&data_queue, "data_q", 16 * 128, 16, Q_PRIO);
}
RT_EVENT — olay bayrakları
#include <alchemy/event.h>
RT_EVENT sys_events;
#define EVT_ENCODER_READY 0x01
#define EVT_ADC_READY 0x02
#define EVT_FAULT 0x80
void sensor_task(void *arg)
{
for (;;) {
read_encoder_hw();
rt_event_signal(&sys_events, EVT_ENCODER_READY);
rt_task_sleep(1000000);
}
}
void fusion_task(void *arg)
{
unsigned int flags;
for (;;) {
/* Her iki olay da gelene kadar bekle */
rt_event_wait(&sys_events,
EVT_ENCODER_READY | EVT_ADC_READY,
&flags,
EV_ALL, /* AND koşulu */
TM_INFINITE);
compute_sensor_fusion();
}
}
05 Latency testi
Xenomai kurulumunun doğrulanması için yerleşik xeno-latency aracı kullanılır; SMP sistemlerde her CPU çekirdeği ayrı ayrı test edilmelidir.
xeno-latency kullanımı
# Temel latency testi — 1000 µs periyot, 10000 örnek
xeno-latency -p 1000 -l 10000
# SMP: tüm CPU'ları test et (-s: SMP modu)
xeno-latency -s -p 1000 -l 50000
# Histogram çıktısı (her 1 µs'lik aralık için sayı)
xeno-latency -H 100 -p 1000 -l 100000 > latency_hist.txt
# Yük altında test: başka terminalde stress çalıştır
stress-ng --cpu 0 --io 4 --vm 2 --vm-bytes 256M &
xeno-latency -p 500 -l 100000
Örnek çıktı ve yorum
== Sampling period: 1000 us
== Test mode: periodic user-mode task
== All results in microseconds
warming up...
RTT| 00:00:01 (periodic user-mode task, 1000 us period, priority 99)
RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
RTD| 0.499| 0.612| 1.847| 0| 0| 0.499| 1.847
RTD| 0.501| 0.618| 2.103| 0| 0| 0.499| 2.103
RTD| 0.498| 0.609| 1.956| 0| 0| 0.498| 2.103
...
---|-----------|-----------|-----------|--------|------|-----------|----------
RTS| 0.498| 0.614| 3.201| 0| 0| 0.498| 3.201
PREEMPT-RT ile karşılaştırmalı ölçüm
| Ortam | lat min | lat avg | lat max (yük altında) |
|---|---|---|---|
| Vanilla Linux 6.1 | 5 µs | 25 µs | 800 µs+ |
| PREEMPT-RT Linux 6.1 | 3 µs | 12 µs | 120 µs |
| Xenomai 3.2 (Cobalt) | 0.5 µs | 0.8 µs | 5 µs |
| Xenomai 3.2 + CPU izolasyon | 0.3 µs | 0.5 µs | 2 µs |
CPU izolasyonu ile latency iyileştirme
# Kernel komut satırına ekle (grub veya device tree bootargs)
isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3
# RT görevi için izole CPU'yu belirle
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset);
sched_setaffinity(0, sizeof(cpuset), &cpuset);
# İzole CPU'da IRQ affinity ayarı
echo 3 > /proc/irq/<irq_num>/smp_affinity_list
06 RTDM sürücü modeli
RTDM (Real-Time Driver Model), RT görevlerinden çağrılabilen çekirdek sürücüsü yazmanın standart Xenomai yoludur; mode switch olmadan donanım erişimi sağlar.
RTDM sürücü çatısı
#include <rtdm/driver.h>
struct my_rtdm_priv {
rtdm_irq_t irq_handle;
rtdm_lock_t lock;
unsigned int tx_count;
unsigned int rx_count;
};
/* Dosya işlemleri */
static int my_open(struct rtdm_fd *fd, int oflags)
{
struct my_rtdm_priv *ctx = rtdm_fd_to_private(fd);
rtdm_lock_init(&ctx->lock);
ctx->tx_count = 0;
ctx->rx_count = 0;
return 0;
}
static void my_close(struct rtdm_fd *fd)
{
/* kaynakları serbest bırak */
}
static ssize_t my_read_rt(struct rtdm_fd *fd,
void __user *buf, size_t size)
{
struct my_rtdm_priv *ctx = rtdm_fd_to_private(fd);
rtdm_lockctx_t lock_ctx;
char kbuf[64];
ssize_t ret;
rtdm_lock_get_irqsave(&ctx->lock, lock_ctx);
ret = read_hw_register(kbuf, size);
ctx->rx_count += ret;
rtdm_lock_put_irqrestore(&ctx->lock, lock_ctx);
return rtdm_safe_copy_to_user(fd, buf, kbuf, ret);
}
static ssize_t my_write_rt(struct rtdm_fd *fd,
const void __user *buf, size_t size)
{
struct my_rtdm_priv *ctx = rtdm_fd_to_private(fd);
char kbuf[64];
if (rtdm_safe_copy_from_user(fd, kbuf, buf, size))
return -EFAULT;
write_hw_register(kbuf, size);
ctx->tx_count += size;
return size;
}
/* IOCTL — RT bağlamından çağrılabilir */
static int my_ioctl_rt(struct rtdm_fd *fd,
unsigned int request, void __user *arg)
{
switch (request) {
case MY_IOC_RESET:
reset_hw();
return 0;
default:
return -ENOTTY;
}
}
static struct rtdm_fd_ops my_fd_ops = {
.open = my_open,
.close = my_close,
.read_rt = my_read_rt,
.write_rt = my_write_rt,
.ioctl_rt = my_ioctl_rt,
};
static struct rtdm_driver my_driver = {
.profile_info = RTDM_PROFILE_INFO(my_drv,
RTDM_CLASS_SERIAL, 0, 1),
.device_flags = RTDM_NAMED_DEVICE,
.device_count = 1,
.context_size = sizeof(struct my_rtdm_priv),
.ops = &my_fd_ops,
};
static struct rtdm_device my_device = {
.driver = &my_driver,
.label = "my_rtdm%d",
};
static int __init my_rtdm_init(void)
{
return rtdm_dev_register(&my_device);
}
static void __exit my_rtdm_exit(void)
{
rtdm_dev_unregister(&my_device);
}
module_init(my_rtdm_init);
module_exit(my_rtdm_exit);
MODULE_LICENSE("GPL");
RTDM IRQ yönetimi
/* RT IRQ handler — OOB stage'de çalışır */
static int my_irq_handler(rtdm_irq_t *irq)
{
struct my_rtdm_priv *ctx = rtdm_irq_get_arg(irq, struct my_rtdm_priv);
/* Donanım kesme bayrağını temizle */
clear_hw_irq();
/* RT event sinyal gönder */
rtdm_event_signal(&ctx->data_ready);
return RTDM_IRQ_HANDLED;
}
/* IRQ'yu kaydet */
ret = rtdm_irq_request(&ctx->irq_handle,
platform_get_irq(pdev, 0),
my_irq_handler,
RTDM_IRQTYPE_EDGE,
"my_rtdm",
ctx);
07 Xenomai + CAN, SPI, UART
Xenomai, endüstriyel haberleşme protokolleri için özel RTDM sürücüler içerir; RTnet (Ethernet), XDDP (RT↔Linux köprü soket) ve platforma özgü CAN/SPI/UART sürücüleri bunların başında gelir.
XDDP: RT görev ↔ Linux süreci köprüsü
/* ---- Xenomai (RT) tarafı ---- */
#include <rtdm/ipc.h>
int rt_to_linux_bridge(void)
{
struct sockaddr_ipc saddr;
int sock, ret;
char msg[] = "sensor_data";
sock = socket(AF_RTIPC, SOCK_DGRAM, IPCPROTO_XDDP);
/* RT tarafı: port 0'a bağlan */
saddr.sipc_family = AF_RTIPC;
saddr.sipc_port = 0;
bind(sock, (struct sockaddr *)&saddr, sizeof(saddr));
/* Linux tarafına gönder (/dev/rtp0 üzerinden okunabilir) */
sendto(sock, msg, sizeof(msg), 0, NULL, 0);
close(sock);
return 0;
}
/* ---- Linux (non-RT) tarafı ---- */
int linux_reader(void)
{
int fd;
char buf[64];
fd = open("/dev/rtp0", O_RDONLY);
read(fd, buf, sizeof(buf)); /* RT'den gelen veri */
printf("Received: %s\n", buf);
close(fd);
return 0;
}
RT CAN sürücüsü (RTnet)
/* RTnet CAN socket — RT bağlamından kullanılabilir */
#include <rtdm/can.h>
int rt_can_init(int ifindex)
{
struct sockaddr_can addr;
int sock;
sock = rt_dev_socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (sock < 0) return sock;
addr.can_family = AF_CAN;
addr.can_ifindex = ifindex;
rt_dev_bind(sock, (struct sockaddr *)&addr, sizeof(addr));
/* CAN frame gönder */
struct can_frame frame = {
.can_id = 0x123,
.can_dlc = 4,
.data = {0xDE, 0xAD, 0xBE, 0xEF},
};
rt_dev_send(sock, &frame, sizeof(frame), 0);
rt_dev_close(sock);
return 0;
}
RT SPI transferi
/* Xenomai SPI RTDM sürücüsü ile RT transferi */
#include <rtdm/spi.h>
int rt_spi_transfer(int fd)
{
struct rtdm_spi_config cfg = {
.speed_hz = 10000000, /* 10 MHz */
.bits_per_word = 8,
.mode = SPI_MODE_0,
};
rtdm_ioctl(fd, SPI_RTIOC_SET_CONFIG, &cfg);
uint8_t tx[] = {0x01, 0x02, 0x03, 0x04};
uint8_t rx[4] = {0};
struct rtdm_spi_iobufs bufs = {
.io_len = sizeof(tx),
.i_offset = 0,
.o_offset = sizeof(tx),
.map_len = sizeof(tx) * 2,
};
/* DMA eşlemeli çift tamponlu transfer */
rtdm_ioctl(fd, SPI_RTIOC_SET_IOBUFS, &bufs);
memcpy(bufs.tx_buf, tx, sizeof(tx));
rtdm_write(fd, NULL, 0); /* transferi başlat */
memcpy(rx, bufs.rx_buf, sizeof(rx));
return 0;
}
Endüstriyel kontrol döngüsü örneği
/* EtherCAT + Xenomai motor kontrol döngüsü (1 ms periyot) */
void ethercat_control_loop(void *arg)
{
rt_task_set_periodic(NULL, TM_NOW, 1000000); /* 1 ms */
for (;;) {
rt_task_wait_period(NULL);
/* 1. EtherCAT frame al (RTnet RTDM socket) */
receive_ethercat_frame();
/* 2. Encoder konumunu oku */
int32_t pos = read_encoder_rtdm();
/* 3. PID hesapla */
int32_t torque = pid_compute(target_pos, pos);
/* 4. Akım referansı yaz (SPI RTDM) */
write_current_ref_spi(torque);
/* 5. EtherCAT yanıt gönder */
send_ethercat_response(pos, torque);
}
}
08 Hata ayıklama
Xenomai, RT görevlerinin durumunu ve performansını izlemek için /proc dosya sistemi, xeno-config aracı ve GDB entegrasyonu sunar.
/proc/xenomai arayüzü
# Cobalt çekirdek versiyonu ve yapılandırma
cat /proc/xenomai/version
# Çıktı: Xenomai/cobalt v3.2.3
# Çalışan RT görevleri
cat /proc/xenomai/sched/threads
# PID CPU PRI PERIOD TIMEOUT STAT NAME
# 142 0 80 1000us -- RUN control
# 143 1 70 2000us -- WAIT sensor
# Gecikme istatistikleri (her görev için min/avg/max)
cat /proc/xenomai/sched/stat
# Mode switch sayaçları — sıfır olmalı
cat /proc/xenomai/stat | grep MSW
# Timer istatistikleri
cat /proc/xenomai/timer
xeno-config ile derleme bilgisi
# Kurulu Xenomai yapılandırması
xeno-config --version
xeno-config --prefix
xeno-config --skin=alchemy --cflags
xeno-config --skin=alchemy --ldflags
# SMP desteği var mı?
xeno-config --smp
# Desteklenen skin'ler
xeno-config --skins
Mode switch uyarıları
/* Geliştirme ortamında MSW yakalamak için sinyal işleyici */
#include <signal.h>
#include <xenomai/signal.h>
void msw_handler(int sig, siginfo_t *si, void *ctx)
{
/* Bu handler Linux bağlamında çalışır */
fprintf(stderr, "MODE SWITCH in PID %d! Check for Linux syscalls.\n",
getpid());
/* Stack trace için */
// backtrace_symbols_fd(...)
}
void setup_msw_warn(void)
{
struct sigaction sa = {
.sa_sigaction = msw_handler,
.sa_flags = SA_SIGINFO,
};
sigaction(SIGDEBUG, &sa, NULL);
/* MSW uyarısını etkinleştir */
pthread_setmode_np(0, PTHREAD_WARNSW, NULL);
}
GDB ile Xenomai RT görevi hata ayıklama
# 1. Sembolle derle
aarch64-linux-gnu-gcc -g -O0 $CFLAGS -o rt_app_debug rt_app.c $LDFLAGS
# 2. Hedef üzerinde gdbserver başlat
gdbserver :1234 ./rt_app_debug
# 3. Çapraz GDB ile bağlan
aarch64-linux-gnu-gdb ./rt_app_debug
(gdb) target remote 192.168.1.100:1234
(gdb) set sysroot /path/to/rootfs
(gdb) break control_loop
(gdb) continue
# Not: GDB'nin breakpoint'i RT görevini durduracağı için
# zaman kısıtlamaları geçersiz olur — yalnızca mantık
# hatası ayıklamak için kullan, zamanlama analizi için değil
Yaygın sorunlar ve çözümleri
| Belirti | Olası neden | Çözüm |
|---|---|---|
| MSW > 0 | RT threadden Linux syscall | printf, malloc, open kaldır; XDDP kullan |
| overrun artıyor | CPU yükü, yanlış öncelik | isolcpus, öncelik artır, periyot uzat |
| lat max spike | SMI, CPU frekans geçişi | CPU_FREQ=n, SMI sayacını izle (rdmsr) |
| SIGXCPU alındı | RT görev CPU quotasını aştı | sched_rt_runtime_us = -1 yap |
| cobalt modülü yüklenmiyor | Dovetail yaması eksik/uyumsuz | kernel config'i kontrol et, yama sürümünü eşleştir |
Performans izleme betiği
#!/bin/sh
# Xenomai RT sistem sağlık kontrolü
echo "=== Xenomai Versiyon ==="
cat /proc/xenomai/version
echo "=== Aktif RT Görevler ==="
cat /proc/xenomai/sched/threads
echo "=== Mode Switch Sayaçları ==="
cat /proc/xenomai/stat | awk 'NR==1 || $6 > 0'
echo "=== Timer Çözünürlüğü ==="
cat /proc/xenomai/timer | grep resolution
echo "=== CPU Izolasyon ==="
cat /sys/devices/system/cpu/isolated
echo "=== IRQ Affinities ==="
for irq in $(ls /proc/irq/); do
aff=$(cat /proc/irq/$irq/smp_affinity_list 2>/dev/null)
echo "IRQ $irq: $aff"
done