2.1 Xenomai3体系结构
Xenomai 3 是一个高度模块化、可扩展的,双内核实时扩展框架。Xenomai 3 的 Cobalt 实时内核,支持调度运行全路径硬实时任务,而 Linux 整体作为一个 idle 任务运行。为了完成这个目标,Xenomai 3 在中断、内核、任务等方面的设计都是基于Out-of-Band (OOB) 核心思想。
Xenomai可以分为
- 两个处理阶段:
- In-band: 标准的 Linux 上下文(任务执行,中断处理等)
- Out-of-band: 自主的中断处理与调度 — 实时核心
- 两个 Linux 的基线补丁
- Dovetail: 现代补丁,自 Linux 内核 5.10 起使用
- I-pipe: 遗留补丁, 已不再推荐使用
- 两个调度核心
- Cobalt: Xenomai 3 系列的核心, 与用户态库和工具一并维护
- EVL:Xenomai 4 系列的核心, 以dovetail之上的内核补丁形式维护

2.1.1 中断管道机制
中断管道机制是 Xenomai 的核心机制,这个机制也是Xenomai实现"硬实时性"的基础之一,也是与传统Linux实时机制(如PREEMPT_RT)最大的区别之一。
什么是中断管道机制?
中断在中断管道中流动,经历 2 个处理阶段: Head Stage 和 Root Stage:
-
Head Stage 运行在 Head Domain(头域)中,即运行在 Cobalt 实时内核中。在 Head Stage 中运行的代码,被称为
Out-of-Band(OOB) 代码。 -
Root Stage 运行在root domain(根域)中,即运行在 Linux 内核中。在Root Stage中运行的代码,被称为 In-band 代码。

从二者执行的优先级来分析:
-
Head Stage 是一个独立的高优先级执行阶段,一旦收到中断,立刻调用
Out-of-Band(OOB) 中断处理程序,丝毫不会被 Root Stage 中的 In-band 代码影响。 -
Root stage 是一个低优先级执行阶段,只有当 Head Stage 让出 CPU 时,Root stage 的 In-band 中断处理程序才得以执行并处理推迟传递过来的中断。
总而言之,中断是从 Head Stage 流向 Root Stage,这两个阶段形成了一个基于优先级进行中断传递的两级处理机制。当然并不是所有的中断都经历完整的两个阶段,只有特定的中断才会经历完整的两个阶段:
- 经历完整的两个阶段的中断:例如时钟中断。
- 仅仅经历 Head Stage 的中断:硬件设备仅在 Cobalt 实时内核中注册中断处理函数,处理完毕后,中断即告结束。
- 仅仅经历 Root Stage 的中断:硬件设备仅在 Linux 内核中注册中断处理函数,在 Cobalt 实时内核中路过并放行,流到 Linux 内核中处理,处理完毕后,中断即告结束。
Xenomai 如何实现中断管道机制?
从 Xenoma 2 开始,ipipe 项目实现了中断管道机制,并且沿用到了 Xenomai 3。
为了优化中断管理机制并改善可移植性,dovetail 项目重新实现了中断管道机制,它是 ipipe 的演进版本。
Xenomai 3 推荐使用 dovetail ,I-pipe已经不再维护。本书在后续章节,均基于 dovetail 进行讨论。

在linux-dovetail/include/irqstage.h 中可以看到 Linux(in-band)的 IRQ 掩码是被虚拟的, OOB 的 IRQ 掩码也是虚拟的
| 层(stage) | 屏蔽方式 | IRQ是否“到达” CPU | IRQ是否被记录/接收 |
|---|---|---|---|
| Linux in-band 挂起(stall) | 虚拟, 设置 stall_bit | 是 | 写入 in-band log |
| Linux in-band 取消挂起(unstall) | 虚拟,清除 stall_bit | 是 | 重放(replay) in-band log |
| OOB 挂起(stall) | 硬件 + 设置 stall_bit | 否 | 写入OOB log |
| OOB 取消挂起 (stall) | 恢复硬件中断 + 清除 stall_bit | 是 | 重放(replay)out-of-band log |
这里面需要注意的是,Dovetail/Xenomai 或 Linux 内核中有些场景仍然是需要真正的硬件中断屏蔽( hard IRQ masking ),即 hard_local_irq_disable(),而不能只依赖虚拟化的 stall/unstall。
那么都有哪些情况需要硬中断屏蔽?
- 虚拟 IRQ log 处理
- 虽然大部分
in-band / OOB中断可以通过log阻塞和重放处理,但在某些关键路径(比如pipeline内部修改log数据结构)仍需保证原子性,必须真正关意见IRQ, 防止中断干扰。
- 虽然大部分
- 任务上下文切换(task context switcher)
- 切换进程/线程涉及CPU寄存器、栈等敏感数据,必须阻止中断,否则可能导致数据结构不一致或调度异常
- 特殊路径
- CPU 热插拔、休眠(hibernation)、KVM虚拟化、跟踪器(trace)、位操作(bitops)等,都涉及对全局和关键数据的操作,需要硬中断屏蔽来保证安全性。
- 硬化驱动(意为驱动经过增强设计,提高安全性、可靠性或对错误/攻击的耐受性)与硬
spinlock- 对于硬化驱动并使用硬
spinlock,为保证共享硬件资源访问的原子性,必须禁用硬件中断;单纯依靠虚拟stall无法完全防止竞争。 - 多个CPU之间的互斥可以由硬
spinlock本身保证,但是在单CPU内部,如果本CPU在临界区期间被硬件中断打断,中断上下文可能产生竞争。比如中断控制器(IRQ chips),时钟/定时器,GPIO/pinctrl,DMA,SPI,I2C,这些硬件路径对时序和状态一致性要求严格,必须通过硬件屏蔽中断保证原子性。
- 对于硬化驱动并使用硬
| 硬件子系统 | 访问特性 | 是否涉及中断上下文 | 单CPU安全 | 多CPU互斥 | 注意点 / 风险 |
|---|---|---|---|---|---|
| 中断控制器 | 中断控制器寄存器 | 是 | ❌ 如果不屏蔽硬件IRQ,可能被打断导致死锁 | ✅ 硬 spinlock 导致跨CPU互斥 | 获取锁时必须屏蔽 CPU IRQ |
| 时钟/定时器 | 定时器寄存器/时钟事件 | 是 | ❌ 中断 handler 内持锁可能被更高优先级中断打断 | ✅ | 高优先级中断可能导致自锁或竞态 |
| GPIO / pinctrl | GPIO 寄存器/配置寄存器 | 是 | ❌ 同 CPU 内中断嵌套可能破坏临界区 | ✅ | 临界区需要硬 spinlock + 硬 IRQ 禁用 |
| DMA | 描述符链表/控制寄存器 | 是 | ❌ DMA完成中断回调可能打断临界区,造成CPU访问DMA队列出现死锁 | DMA 完成中断回调可能打断临界区,必须屏蔽本 CPU IRQ | |
SPI | 传输队列 / 控制寄存器 | 是 | ❌ 同CPU中断嵌套可能自锁 | ✅ | IRQ handler 内访问共享队列需要关中断 |
I2C | 总线控制寄存器/队列 | 是 | ❌ IRQ handler 内访问临界区可能被打断 | ✅ | IRQ handler 内操作共享资源需要硬 spinlock |
为了进一步说明 Xenomai 的 hard_spinlock_t
可以参考一下Linux的实时扩展PREEMPT_RT,它充分利用了Linux的SMP能力来增加额外的抢占能力,而不是重写LInux内核。某种程度,可以认为这也是一种双核设计,相当于增加了一个CPU,并利用原有锁与新抢占任务同步。这里面就有并不禁用硬件中断的可抢占自旋锁(spinlock_t)和禁用硬件中断的非抢占原始自旋锁(raw_spinlock_t)。
hard_spinlock_t 是一种类型封装,通过定义一个新的类型,把底层实现隐藏起来,让外来者不用关心具体实现细节。也就是说,hard_spinlock_t带内是不屏蔽中断的,可以被抢占;带外是屏蔽中断的,不可以被抢占。
这是通过一系列封装类型实现的,比如 RTDM 锁的链条:
typedef hard_spinlock_t pipeline_spinlock_t; // include/cobalt/kernel/dovetail/pipeline/lock.h
typedef pipeline_spinlock_t rtdm_lock_t; // include/cobalt/kernel/rtdm/driver.h
hard_spinlock_t 不是一个固定实现,而是根据上下文选择实现。Xenomai 并没有实现自己的 raw_spinlock_t, 直接使用了 Linux 内核原生的 raw_spinlock_t , 只是在 hard_spinlock_t 封装中根据上下文选择使用它,同时要看到raw_spinlock_t 没有改变底层CPU架构相关的自旋锁实现逻辑。
2.1.2 双内核机制
从 Cobalt 实时内核和 Linux 内核的双内核架构来看,可以认为 Cobalt 实时内核运行在 Out-of-Band (OOB) 上,而 Linux 内核运行在 In-band (In-band) 上。
通过中断管道机制, Cobalt 实时内核劫持了 Linux 内核的中断入口,其中最重要的是接管了硬件时钟中断。硬件时钟中断推动着 Cobalt 实时内核优先执行,调度实时任务运行。为了避免 Linux 内核直接修改硬件时钟寄存器,Cobalt 实时内核提供了一个软件定时器,为 Linux 内核提供模拟硬件时钟。Linux 内核自认为是在操作硬件时钟,其实是在操作软件定时器。
Cobalt 实时内核总是优先执行,得以保证实时任务的硬实时,完全不受 Linux 影响。实时任务支持 FIFO 和 RR 调度策略。只有当所有的实时任务都让出 CPU 时,才会调度运行代表 Linux 的 idle 任务。
当 Linux 内核得以运行,它将响应从 Cobalt 实时内核流动过来的时钟中断,并执行调度,运行 Linux 常规任务。注意在编写实时应用时,应避免激进地错误地尝试占用 CPU,导致 Linux 无法获得 CPU 时间片而发生饥饿。Xenomai watchdog 机制,可以监控 Xenomai 实时任务(一个或连续多个)是否长期占用 CPU,而导致 Linux 内核无法获得时间片运行的情况。
2.1.3 影子线程与交替调度
Cobalt 实时任务(进程或线程)的创建,实际上是将一个 Linux 任务影化( shadow )的过程。
以创建一个 Cobalt 实时线程为例,说明影化( shadow )的过程:
- 在 Linux 中创建一个调度策略为 SCHED_FIFO/SCHED_RR 的线程。Linux 内核会为线程创建一个新的 任务控制块 TCB,其数据结构类型为 struct task_struct 结构体。
- 在 Cobalt 实时内核中,为上述 Linux 线程创建一个关联的 Cobalt 实时线程。Cobalt 实时内核为实时线程创建一个新的任务控制块 TCB,其数据结构类型为 struct cobalt_thread 结构体。
最终,把与 Linux 线程相关联的 Cobalt 实时线程称为影子线程。
从 Out-of-Band (OOB) 核心思想来看,Cobalt 实时任务仅仅被视为一个具有额外调度能力的 Linux 任务。因此,Cobalt 实时任务即可以保证硬实时特性,又能最大限度的复用 Linux 提供的功能。
为了达成这个理念,Xenomai 允许实时任务在 Cobalt 实时内核和 Linux 内核两种执行上下文中交替运行,这被称为交替调度。
当实时任务在 Cobalt 实时内核中调度运行,处于硬实时状态,也称其处于主模式中。
当实时任务在 Linux 内核中运行,代表退出了硬实时状态,也称处于次模式中。一般用于调用 Linux 内核负责的系统调用,处理某些 CPU 异常等。
所谓交替,本质上是指实时任务在主模式和次模式之间切换,运行在不同上下文中。实时任务从次模式切换到主模式,称为 harden 操作;实时任务从主模式切换到次模式,称为 relax 操作。
Xenomai 的影子线程和交替调度机制,使得 Xenomai 具备了以下优点:
- 确保了开发灵活性
- 允许程序员使用熟悉的 Linux 用户空间编程方式来写实时任务。
- 确保了实时和功能之间的平衡,按需进入不同的上下文。
- 通过Xenomai提供的API(如POSIX皮肤)实现低延迟任务调度、同步和通信。
- 只要不调用"非实时函数",程序就能运行在
OOB实时路径上 - 在必要时也可以"退回"到Linux提供的调度(牺牲实时性换取通用性)
2.1.4 系统调用
为了保证全路径硬实时,在 Linux 现有的系统调用之外,Xenomai 引入了 Cobalt 实时系统调用。用户态程序通过 Cobalt 实时系统调用,调用 Cobalt 实时内核提供的服务,从而保证实时任务的硬实时。
考虑到实时任务支持交替调度,可能在主模式和次模式之间切换,运行在不同上下文中,因此,Cobalt 实时系统调用需要在两种上下文中进行处理。又考虑到,实时任务为了使用 Linux 内核提供的功能,可能调用 Linux 内核提供的系统调用,因此,需要改造系统调用处理路径,兼容不同的场景。
基于 Out-of-Band (OOB) 核心思想,改造后的系统调用处理路径,共支持处理 6 种场景:
- 快速路径(Fast Path):
- 实时任务运行在主模式,调用 Cobalt 实时系统调用。
- 慢速路径(Slow Path):
- 实时任务运行在主模式,调用 Linux 系统调用,可能导致切换到次模式。
- 实时任务运行在次模式,调用 Cobalt 实时系统调用,可能导致切换到主模式。
- 实时任务运行在次模式,调用 Linux 系统调用。
- Linux 常规任务,调用 Cobalt 实时系统调用,一般是为创建影子线程。
- 正常路径:
- Linux 常规任务,调用 Linux 系统调用,直接返回给 Linux 内核处理。
2.1.5 实时任务接口
为了在用户态程序中兼容 POSIX 接口,基于 Out-of-Band (OOB) 核心思想,Xenomai 3 通过 --wrap 链接选项,实现了从 POSIX 接口到 Cobalt 实时系统调用的转换。
--wrap 链接选项能够引导链接器按照指定的规则进行符号链接,在处理 POSIX 相关接口(如 pthread_create 等)时,优先使用 cobalt.wrappers 文件中定义的包装符号,从而实现对 POSIX 函数的重定向。cobalt.wrappers 文件中,共计包含 120 个函数的包装符号,覆盖了 pthread 多线程编程、网络编程、文件系统、信号处理等方面。
除了兼容 POSIX 接口之外,Xenomai 3 还提供了对多种 RTOS API 的支持,包括 Alchemy/pSOS/VxWorks 等接口。
2.1.6 实时硬件驱动模型
为了支持全路径硬实时,Xenomai 3 引入了实时硬件驱动模型 RTDM,在 Out-of-Band (OOB) 直接接管硬件设备。
RTDM(Real-Time Device Model)是实时驱动模型,旨在统一实时设备驱动程序和应用程序的开发接口,解决传统双内核硬实时Linux扩展(如RTLinux、RTAI)中接口碎片化和平台移植成本高的问题。
RTDM提供了一个跨平台的RTOS服务抽象层,封装了任务调度、同步原语等服务,使驱动程序仅依赖RTDM API,提高可移植性。
RTDM支持两种主要类型的设备:协议设备和命名设备。协议设备支持面向消息的通信,命名设备则通过open函数实例化。此外,RTDM还定义了设备规范(Device Profiles),以简化设备驱动程序的实现。在Xenomai3中,RTDM驱动涵盖串行通信、CAN总线、网络接口等多种设备。
RTDM的应用不仅限于设备驱动,还包括扩展RTnet协议栈和RTIPC功能,使其成为Cobalt内核的有效补充。
7695

被折叠的 条评论
为什么被折叠?



