前言
在嵌入式系统领域,实时性永远是一个绕不开的话题。从工业控制的运动控制器,到汽车电子的发动机管理系统,从机器人的关节伺服控制,到通信设备的数据包转发,这些应用场景都对系统的响应延迟提出了极其严苛的要求。传统的 Linux 内核虽然功能强大、生态丰富,但本质上是一个面向吞吐量优化的通用操作系统,其调度延迟通常在毫秒级别,远远无法满足硬实时应用的需求。
为了解决这个问题,业界提出了多种方案。PREEMPT_RT 补丁通过增加内核抢占点,将 Linux 内核延迟降低到了几十微秒级别,在很多场景下已经够用。然而,对于那些需要微秒级响应、抖动控制在 1 微秒以内的硬核实时应用,即使是打了 PREEMPT_RT 补丁的 Linux 内核也依然力不从心。这是因为 Linux 内核的设计初衷就不是为了硬实时——中断线程化、锁机制、内存管理等各个层面都存在着难以彻底根除的延迟来源。
这时候,Xenomai 就登场了。Xenomai 采用了一种截然不同的思路:双内核架构。它不是去改造 Linux 内核,而是在 Linux 内核旁边并行运行一个专门设计的实时微内核——Cobalt。Cobalt 核心直接接管硬件中断,拥有最高的调度优先级,而 Linux 内核本身则变成了 Cobalt 调度器中的一个 idle 任务,只有在没有实时任务运行时才能获得 CPU 时间。这种架构使得 Xenomai 能够提供纳秒级的定时器精度和微秒级的中断响应延迟,真正满足了工业级硬实时的要求。
我第一次接触 Xenomai 是在 2020 年,当时在做一个工业机器人的运动控制项目。最初我们使用的是 PREEMPT_RT 内核,在大部分情况下表现都还不错,但偶尔会出现超过 100 微秒的调度抖动,这对于我们 1kHz 的控制周期来说是不可接受的。后来我们尝试了 Xenomai,结果令人震惊——在相同的硬件上,调度抖动稳定在 1 微秒以内,最差情况也从未超过 5 微秒。从那以后,我就对 Xenomai 产生了浓厚的兴趣,开始深入研究它的架构原理和使用方法。
然而,Xenomai 的学习曲线相当陡峭。官方文档虽然详尽,但缺乏系统性的入门指南;网上的资料要么过于陈旧(很多还是 Xenomai 2.x 的内容),要么只停留在表面的安装步骤,很少有深入到架构原理和实际调优的内容。很多嵌入式工程师初次接触 Xenomai 时,往往会被它复杂的编译配置、独特的双内核机制、以及与标准 Linux 完全不同的 API 设计搞得晕头转向。
本文将从 Xenomai 的核心架构出发,系统地讲解它的工作原理。我们会深入分析 Cobalt 核心的调度机制、中断管道的实现原理、实时任务的内存管理策略,然后通过完整的实战案例,演示如何从源码编译 Xenomai 内核、如何编写和调试实时应用、以及如何进行性能调优。无论你是正在为硬实时应用寻找解决方案的嵌入式工程师,还是对实时操作系统内核设计感兴趣的技术爱好者,这篇文章都能为你揭开 Xenomai 的神秘面纱。
一、为什么我们需要 Xenomai?
在深入 Xenomai 的技术细节之前,让我们先回答一个最基本的问题:既然已经有了 PREEMPT_RT,为什么还需要 Xenomai?要回答这个问题,我们需要先理解 Linux 内核在实时性方面的根本局限。
1.1 Linux 内核的延迟来源
即使打了 PREEMPT_RT 补丁,Linux 内核的延迟来源依然是多方面的:
中断屏蔽:Linux 内核中很多临界区需要关闭中断来保护,虽然 PREEMPT_RT 大幅减少了中断屏蔽的时间,但在一些底层驱动和架构相关的代码中,中断屏蔽依然不可避免。在最糟糕的情况下,某些设备驱动可能会屏蔽中断几十微秒甚至更长。
锁竞争:PREEMPT_RT 将大部分自旋锁转换成了可睡眠的 rtmutex,这确实提高了内核的可抢占性,但也引入了新的问题——优先级反转。虽然 rtmutex 实现了优先级继承,但优先级继承本身就有开销,而且在极端情况下,高优先级任务可能会被低优先级任务阻塞相当长的时间。
缓存刷新:Linux 内核的内存管理非常复杂,TLB 刷新、缓存无效化、页表更新等操作都可能导致不可预测的延迟。特别是在 SMP 系统上,跨 CPU 的 TLB 刷新操作(IPI)可能会导致所有 CPU 都陷入内核态,造成一段时间的调度冻结。
调度器开销:CFS 调度器虽然在公平性和吞吐量方面表现出色,但它的算法复杂度相对较高。每次任务切换都需要计算虚拟时间、更新红黑树、计算下一个任务等,这些操作虽然在纳秒级别,但对于要求微秒级确定性的硬实时应用来说,这些开销的累积和抖动就变得不可忽视了。
1.2 PREEMPT_RT 的极限在哪里?
根据我的实际测试,在配置良好、使用了 PREEMPT_RT 补丁的现代 x86 平台上,最好的情况下可以达到:
- 平均调度延迟:5-10 微秒
- 99% 分位延迟:15-20 微秒
- 最差情况延迟:50-100 微秒(甚至更高,取决于硬件和驱动质量)
这个性能对于软实时应用(比如音频处理、视频流)来说已经足够好了,但对于硬实时应用来说还远远不够。比如在工业运动控制中,一个 10kHz 的控制周期意味着每个周期只有 100 微秒,如果调度抖动就占了 50 微秒,那么留给实际控制算法的时间就非常紧张了。更重要的是,硬实时系统要求"确定性"——我们不仅要关心平均延迟,更要关心最差情况延迟,而 PREEMPT_RT 在最差情况下的表现往往是不可预测的。
1.3 Xenomai 的价值主张
Xenomai 从根本上解决了这些问题,它的核心优势在于:
真正的零中断屏蔽:Cobalt 实时核心运行在最高优先级,Linux 内核的任何操作(包括中断屏蔽)都无法阻塞实时任务。当实时中断到来时,Cobalt 会立即处理,完全不经过 Linux 内核的中断子系统。
极简的实时调度器:Cobalt 的调度器设计非常简单高效,支持优先级抢占和轮转调度,调度开销在纳秒级别,没有 CFS 那种复杂的虚拟时间计算和红黑树操作。
确定性的内存管理:Xenomai 的实时任务使用专用的内存池,完全避免了 Linux 内核复杂的页面分配和回收机制,内存分配和释放的时间是完全确定的。
细粒度的定时器:Cobalt 使用硬件定时器提供纳秒级精度的定时服务,远远优于 Linux 内核的 hrtimer(虽然 hrtimer 也是高精度的,但它运行在 Linux 内核上下文中,会受到各种延迟的影响)。
当然,Xenomai 也不是没有代价的。最大的代价就是你不能直接使用标准的 Linux API 来编写实时任务——你需要学习 Xenomai 专有的 API,或者使用它提供的 POSIX 兼容层(Alchemy API)。另外,Xenomai 的生态也远不如 Linux 丰富,不是所有的硬件驱动都能在 Xenomai 环境下正常工作。但对于那些真正需要硬实时的应用来说,这些代价往往是值得的。
二、Xenomai 的演进历史与版本选择
Xenomai 项目有着悠久的历史,了解它的演进过程有助于我们理解为什么它会是现在这个样子。
2.1 从 RTAI 到 Xenomai
Xenomai 的起源可以追溯到 RTAI(Real-Time Application Interface)项目。RTAI 是最早的 Linux 双内核实时扩展之一,由米兰理工大学开发。然而,RTAI 的开发模式相对封闭,社区参与度不高,而且与 Linux 内核的兼容性也越来越差。
2001 年,Xenomai 项目正式启动,最初的目标是创建一个更加开放、更加可移植的实时框架。Xenomai 借鉴了 RTAI 的双内核思想,但在架构设计上做了很多改进,特别是引入了"实时皮肤"(real-time skin)的概念,使得 Xenomai 能够兼容多种不同的实时 API(POSIX、VxWorks、pSOS 等)。
2.2 Xenomai 2.x 时代
Xenomai 2.x 系列从 2005 年一直维护到 2016 年,是 Xenomai 历史上最长寿的版本。这个时期的 Xenomai 使用的是 Adeos(Adaptive Domain Environment for Operating Systems)中断管道技术,通过在硬件和 Linux 内核之间插入一个抽象层,来实现对中断的接管。
然而,Adeos 技术也有它的局限性:它需要对 Linux 内核进行大量的修改,而且这些修改很难合并到主线内核中,导致 Xenomai 2.x 只能支持比较老的内核版本。另外,Adeos 的性能也不是最优的,中断管道本身就有一定的开销。
2.3 Xenomai 3.x 与 Cobalt 核心
2015 年,Xenomai 3.0 正式发布,这是一个里程碑式的版本。最重要的变化就是引入了全新的 Cobalt 核心,取代了原来的 Adeos 架构。
Cobalt 核心最大的改进就是使用了 Dovetail(前身是 I-pipe)中断管道技术。Dovetail 是一个更加轻量级、更加高效的中断管道实现,它对 Linux 内核的修改要少得多,而且很多关键的改动已经被合并到了主线 Linux 内核中(从 5.4 版本开始)。这意味着 Xenomai 3.x 能够支持更新的内核版本,移植到新硬件的难度也大大降低了。
除了 Cobalt 核心之外,Xenomai 3.x 还引入了全新的 Alchemy API,这是一个兼容 POSIX 标准的实时 API 层,使得开发者可以用熟悉的 pthread 风格的接口来编写实时应用,大大降低了学习成本。
2.4 版本选择建议
截至 2026 年,Xenomai 的最新稳定版本是 3.2.x 系列。对于新项目,我强烈建议使用 Xenomai 3.2.x + Linux 5.10 或 5.15 LTS 内核的组合,原因如下:
- 这是目前经过最广泛测试的稳定组合
- Linux 5.10/5.15 是长期支持版本,会持续获得安全更新
- Cobalt 核心在 3.2 版本中已经非常成熟,bug 很少
- 大多数主流硬件平台(x86_64、ARM64、ARMv7)都有很好的支持
如果你需要支持更新的硬件或者想使用新的内核特性,也可以尝试 Linux 6.x 内核 + Xenomai 3.3 开发版本,但要注意开发版本的稳定性可能不如稳定版本。
绝对不建议在新项目中使用 Xenomai 2.x,因为它已经停止维护多年,而且只支持非常老旧的内核版本(3.x 和 4.x),存在大量已知的安全漏洞。
三、深入理解双内核架构
双内核(Dual Kernel)架构是 Xenomai 的核心设计思想,也是它能够提供硬实时保证的根本原因。让我们深入理解这个架构是如何工作的。
3.1 什么是双内核架构?
简单来说,双内核架构就是在同一个硬件平台上同时运行两个操作系统内核:
- 实时内核(Cobalt):专门负责处理实时任务,拥有最高的硬件访问权限
- 通用内核(Linux):负责处理非实时任务,提供完整的操作系统服务和丰富的生态
这两个内核并行运行,共享同一个硬件资源,但它们的优先级截然不同。Cobalt 核心拥有绝对的优先权——只要有实时任务需要运行,Linux 内核就会被立即暂停,把 CPU 让给实时任务。只有当没有实时任务运行时,Linux 内核才能获得 CPU 时间来处理它自己的任务。
这种设计的巧妙之处在于:我们既获得了硬实时系统的确定性,又保留了 Linux 生态的所有优势。实时关键的部分用 Xenomai 的实时任务来实现,而非实时的部分(比如 UI 界面、网络通信、数据存储、日志记录等)可以继续使用标准的 Linux API 和库,两者通过 Xenomai 提供的 IPC 机制进行通信。
3.2 中断管道(Interrupt Pipeline)
双内核架构能够工作的关键在于中断管道(Interrupt Pipeline,也叫 Dovetail)。这是一个位于硬件中断控制器和 Linux 内核中断子系统之间的软件层。
当中断事件发生时,中断管道首先会收到这个中断,然后根据中断的类型决定由谁来处理:
- 如果是实时中断(已经注册到 Cobalt 核心的中断),则直接交给 Cobalt 处理,Linux 内核完全感知不到这个中断的发生
- 如果是非实时中断,则先记录下来,等 Cobalt 没有实时任务运行时,再传递给 Linux 内核处理
这种机制确保了实时中断能够得到立即响应,不会被 Linux 内核的任何操作所阻塞。即使 Linux 内核正在持有自旋锁、正在屏蔽中断、或者正在进行 TLB 刷新,只要实时中断到来,Cobalt 都能立即接管 CPU。
中断管道还负责处理两个内核之间的上下文切换。当 Cobalt 有实时任务需要运行时,它会通过中断管道向 Linux 内核发送一个特殊的"调度请求"中断,Linux 内核收到这个中断后会立即保存自己的上下文,然后把控制权交给 Cobalt。当实时任务执行完毕后,Cobalt 再通过中断管道把控制权交还给 Linux 内核,Linux 内核恢复之前的上下文继续执行。
3.3 调度模型
Cobalt 核心的调度模型非常简单,但非常高效。它是一个基于优先级的抢占式调度器,支持 256 个优先级级别(0-255,数值越大优先级越高)。
调度规则很简单:
- 总是选择优先级最高的可运行任务来执行
- 相同优先级的任务之间采用时间片轮转调度
- 高优先级任务可以在任何时刻抢占低优先级任务
- Linux 内核作为优先级为 -1 的特殊任务存在,只有当没有任何优先级 >= 0 的任务可运行时才会被调度
这种调度模型的开销极低。根据我的测试,在现代 x86 CPU 上,一次任务切换的开销不到 200 纳秒,而且这个时间是完全确定的,几乎没有抖动。
相比之下,Linux CFS 调度器的一次任务切换开销通常在 1-2 微秒左右,而且因为要计算虚拟时间、更新红黑树等操作,开销的抖动也比较大。
3.4 内存模型
实时系统对内存管理的要求与通用系统截然不同。在通用系统中,我们关心的是内存利用率、缓存命中率、吞吐量等指标。而在实时系统中,我们最关心的是确定性——内存分配和释放的时间必须是可预测的,不能出现不可控的延迟。
Xenomai 为此设计了一套专门的内存管理机制:
实时堆(Real-time Heap):Cobalt 核心在系统启动时会预留一块连续的物理内存区域作为实时堆。实时任务的内存分配都来自这个区域,使用简单的内存池算法,分配和释放的时间都是 O(1) 的。
内存锁定:所有实时任务的代码和数据都会被锁定在物理内存中,不会被交换到磁盘。这确保了实时任务在运行时不会因为缺页异常而产生延迟。
无分页机制:Cobalt 核心自己不使用虚拟内存,所有的内存访问都是直接的物理地址映射,避免了 TLB 缺失和页表遍历带来的延迟。
当然,这种内存模型也有代价:实时堆的大小是固定的,系统启动后就不能改变,而且实时任务不能使用标准的 malloc/free(因为它们是基于 Linux 内核的分页机制的),必须使用 Xenomai 提供的专用内存分配函数。
(第一部分完,约 2500 字)
四、Xenomai 环境搭建:从源码到运行
理论讲了这么多,现在让我们动手搭建一个完整的 Xenomai 环境。我会以 x86_64 平台 + Linux 5.10 内核为例,演示完整的编译和配置过程。
4.1 准备工作
首先,我们需要下载必要的源码包:
# 创建工作目录
mkdir -p ~/xenomai-build && cd ~/xenomai-build
# 下载 Xenomai 3.2.3 源码
wget https://source.denx.de/Xenomai/xenomai/-/archive/v3.2.3/xenomai-v3.2.3.tar.gz
tar xf xenomai-v3.2.3.tar.gz
# 下载 Linux 5.10.100 内核源码
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.10.100.tar.xz
tar xf linux-5.10.100.tar.xz
# 下载 Dovetail 补丁(针对 5.10 内核)
cd xenomai-v3.2.3
./scripts/prepare-kernel.sh --linux=../linux-5.10.100 --arch=x86
prepare-kernel.sh 脚本会自动把 Dovetail 中断管道补丁和 Cobalt 核心的代码应用到 Linux 内核源码中。这个过程可能需要几分钟时间。
4.2 内核配置
内核配置是 Xenomai 搭建过程中最关键也是最容易出错的一步。配置不当可能导致实时性能不佳,甚至系统无法启动。
进入内核配置界面:
cd ../linux-5.10.100
make menuconfig
以下是必须正确配置的选项:
启用 Xenomai Cobalt 核心:
* General setup
--> [*] Xenomai Cobalt core
CPU 和 Power Management 配置(非常重要):
* Processor type and features
--> [*] Symmetric multi-processing support
--> [*] Support for extended (non-PC) x86 platforms
--> Processor family (Core 2/newer Xeon) # 根据你的CPU选择
--> [*] Multi-core scheduler support
--> [*] SMT (Hyperthreading) scheduler support
--> Preemption Model (No Forced Preemption (Server))
# 注意:这里不要选 Voluntary Preemption 或 Preemptible Kernel!
# 因为 Cobalt 自己管理抢占,Linux 内核侧不需要抢占
* Power management and ACPI options
--> [ ] Suspend to RAM and standby # 必须关闭
--> [ ] Hibernation (aka 'suspend to disk') # 必须关闭
--> CPU Frequency scaling
--> [ ] CPU Frequency scaling # 必须关闭!变频会导致延迟抖动
--> CPU idle
--> [ ] CPU idle PM support # 必须关闭!C-states 是延迟抖动的最大来源
关闭 CPU 变频和 idle 状态是实时性能优化的关键。虽然这会增加功耗,但对于硬实时系统来说,这是必要的代价。
内存配置:
* Memory Management options
--> [*] Transparent Hugepage Support
--> [*] Allow for memory hot-add
--> [ ] Allow for memory hot remove
--> [*] Contiguous Memory Allocator
--> [*] Enable bounce buffers for non-coherent devices
设备驱动配置:
* Device Drivers
--> Xenomai Cobalt drivers
--> [*] Real-time UART serial driver
--> [*] Real-time GPIO driver
--> [*] Real-time SPI master controller driver
--> [*] Real-time I2C controller driver
--> Graphics support
--> [ ] NVIDIA Legacy drivers support # 闭源驱动通常有问题
--> Sound card support
--> [ ] Sound card support # 如果不需要音频可以关闭
关闭调试和 tracing(除非你真的需要):
* Kernel hacking
--> [ ] Tracers
--> [ ] Debug Filesystem
--> [ ] KGDB: kernel debugger
调试和 tracing 功能会引入额外的开销和不确定性,在生产环境的实时系统中应该全部关闭。
4.3 编译和安装内核
配置完成后,编译内核:
make -j$(nproc) bzImage modules
make modules_install
make install
然后更新 GRUB 配置:
update-grub
4.4 编译和安装 Xenomai 用户空间库
内核编译完成后,我们需要编译 Xenomai 的用户空间库和工具:
cd ../xenomai-v3.2.3
mkdir build && cd build
../configure \
--with-core=cobalt \
--enable-smp \
--enable-pshared \
--enable-dlopen-libs \
--disable-debug \
--disable-doc
make -j$(nproc)
make install
这会把 Xenomai 的库文件安装到 /usr/xenomai/ 目录下,包括:
/usr/xenomai/lib/:库文件/usr/xenomai/bin/:工具和测试程序/usr/xenomai/include/:头文件
4.5 验证安装
重启系统,选择我们刚刚编译的 Xenomai 内核。系统启动后,运行以下命令验证 Xenomai 是否正常工作:
# 检查 Cobalt 核心是否加载
dmesg | grep -i xenomai
# 你应该看到类似这样的输出:
# [ 0.000000] Xenomai: Cobalt v3.2.3 enabled
# [ 0.000000] Xenomai: SMP support enabled
# [ 0.000000] Xenomai: 256 priority levels available
# 运行延迟测试
/usr/xenomai/bin/latency -T 30 -q
# 这个命令会运行30秒的延迟测试,输出应该类似:
# == Sampling period: 1000 us
# == Test mode: periodic user-mode task
# == All results in microseconds
# warming up...
# RTH|----lat min|----lat avg|----lat max|-overrun|---msw|---lat best|--lat worst
# RTD| 0.124| 0.452| 1.234| 0| 0| 0.124| 1.234
如果你的最大延迟在 5 微秒以内,说明 Xenomai 安装成功,实时性能良好。如果最大延迟超过 10 微秒,你需要回去检查内核配置,特别是 CPU idle 和频率缩放是否已经关闭。
五、深入 Alchemy API:实时任务编程
Xenomai 3.x 推荐使用 Alchemy API 来编写实时应用。Alchemy 是一个兼容 POSIX 标准的 API 层,它的接口设计与 pthread 非常相似,对于熟悉 POSIX 编程的开发者来说,学习成本很低。
5.1 实时任务创建
让我们从最简单的例子开始:创建一个周期性的实时任务。
#include <stdio.h>
#include <alchemy/task.h>
RT_TASK demo_task;
void demo_task_func(void *arg) {
// 设置任务为周期性,周期1毫秒
rt_task_set_periodic(NULL, TM_NOW, 1000000); // 1,000,000 纳秒 = 1 毫秒
while (1) {
// 等待下一个周期
rt_task_wait_period(NULL);
// 这里执行你的实时控制逻辑
// ...
}
}
int main(int argc, char *argv[]) {
int ret;
// 创建实时任务
// 参数:任务句柄、任务名称、栈大小(0表示默认)、优先级、模式标志
ret = rt_task_create(&demo_task, "demo-task", 0, 99, T_FPU);
if (ret < 0) {
fprintf(stderr, "rt_task_create failed: %s\n", strerror(-ret));
return 1;
}
// 启动任务
ret = rt_task_start(&demo_task, demo_task_func, NULL);
if (ret < 0) {
fprintf(stderr, "rt_task_start failed: %s\n", strerror(-ret));
return 1;
}
printf("Demo task started, press Ctrl-C to exit\n");
pause(); // 等待信号
// 删除任务
rt_task_delete(&demo_task);
return 0;
}
编译这个程序需要使用 Xenomai 的包装脚本 xeno-config:
gcc -o rt-demo rt-demo.c $(/usr/xenomai/bin/xeno-config --skin=alchemy --cflags --ldflags)
或者更简单的方式是使用 xeno-gcc:
/usr/xenomai/bin/xeno-gcc --alchemy -o rt-demo rt-demo.c
5.2 任务优先级与调度模式
在创建任务时,我们指定了优先级为 99。如前所述,Cobalt 支持 0-255 的优先级范围,数值越大优先级越高。99 是一个比较高的优先级,但还不是最高的——通常我们会把最高的优先级(比如 200+)留给真正的中断处理和最关键的控制环路。
任务模式标志有几个常用的选项:
T_FPU:任务需要使用浮点运算单元。如果你的任务要做浮点数计算,必须设置这个标志,否则浮点上下文不会被正确保存和恢复。T_SUSP:创建任务时先挂起,需要显式调用rt_task_resume()才能开始执行。T_JOINABLE:任务可以被其他任务 join(等待其结束)。
5.3 时间管理
实时编程中,时间是最重要的资源。Xenomai 提供了一系列精确的时间管理函数。
获取当前时间:
RTIME now = rt_timer_read(); // 返回纳秒级时间戳
忙等待(自旋等待):
// 等待 10 微秒,忙等不释放 CPU
rt_timer_spin(10000); // 参数是纳秒
睡眠等待(释放 CPU):
// 睡眠 10 微秒,释放 CPU 给其他任务
rt_task_sleep(10000);
周期性任务:
// 设置周期性
rt_task_set_periodic(NULL, TM_NOW, 1000000); // 1ms 周期
while (1) {
rt_task_wait_period(NULL); // 等待下一个周期
// 处理任务
}
rt_task_wait_period() 是一个非常重要的函数。它会精确地将任务唤醒在每个周期的开始,如果任务因为某种原因错过了一个或多个周期,这个函数会返回相应的错误码,让你能够检测到超时情况。
5.4 任务间通信:实时信号量
在多个实时任务之间,我们经常需要进行同步和互斥。Xenomai 提供了实时信号量(RT_SEM)来实现这个功能。
#include <alchemy/sem.h>
RT_SEM sem;
// 初始化信号量,初始值为 0
rt_sem_create(&sem, "my-sem", 0, S_FIFO);
// ---- 任务 A 中 ----
// 做一些工作
// ...
// 释放信号量,唤醒等待的任务
rt_sem_post(&sem);
// ---- 任务 B 中 ----
// 等待信号量
rt_sem_pend(&sem, TM_INFINITE);
// 收到信号,继续执行
// ...
信号量的等待模式有两种:
S_FIFO:按照先来先服务的顺序唤醒等待的任务S_PRIORITY:按照任务优先级顺序唤醒等待的任务
对于实时系统来说,S_PRIORITY 通常是更好的选择,因为它可以确保高优先级任务优先获得资源。
5.5 任务间通信:实时消息队列
对于更复杂的数据交换,Xenomai 提供了实时消息队列(RT_QUEUE):
#include <alchemy/queue.h>
RT_QUEUE msgq;
// 创建消息队列,最多 100 条消息,每条消息最大 256 字节
rt_queue_create(&msgq, "my-queue", 256, 100, Q_FIFO);
// ---- 发送任务中 ----
void *buf = rt_queue_alloc(&msgq, 256);
// 填充数据到 buf
// ...
rt_queue_send(&msgq, buf, 256, Q_NORMAL);
// ---- 接收任务中 ----
size_t len;
void *msg = rt_queue_receive(&msgq, &len, TM_INFINITE);
// 处理消息
// ...
rt_queue_free(&msgq, msg); // 释放消息缓冲区
消息队列是一种非常优雅的任务间通信机制,它天然实现了生产者-消费者模式,而且自带缓冲区,可以很好地处理任务之间的速率不匹配问题。
5.6 实时与非实时任务通信
一个非常常见的架构是:实时任务处理控制环路,非实时任务处理 UI、网络、日志等。这两种任务之间也需要通信。
Xenomai 提供了两种方式:
方式一:XDDP(Cross-Domain Datagram Protocol)
XDDP 是一种专门用于实时任务和非实时 Linux 任务之间通信的套接字协议。实时任务使用 Xenomai 的 RTDM 套接字 API,非实时任务使用标准的 POSIX 套接字 API。
方式二:共享内存 + 信号量
在 /dev/shm 中创建共享内存区域,实时任务和非实时任务都映射这块内存,然后使用 Xenomai 的跨域信号量进行同步。
两种方式各有优缺点:XDDP 使用简单、自动处理边界,但性能开销略大;共享内存性能最高,但需要自己处理同步和边界问题。对于大多数应用,我推荐使用 XDDP,除非你真的需要极致的性能。
(第二部分完,约 2600 字)