
先说个结论在 Ubuntu 24.04 上从源码编译 Xenomai 4 双内核实时环境真正的难点不在“编译”动作本身而在于版本配对和内核配置。这套东西和 Xenomai 3 时代的玩法完全不同网上能搜到的教程大半还停留在 I-pipe 加 Cobalt 的旧路线上你照着做大概率会在打补丁阶段就卡住甚至搞出“编译过了但启动就 panic”的尴尬局面。这篇文章把我从准备环境、打补丁、配内核、编 libevl到最后跑通 evl-test 的完整过程写清楚顺手把那些坑也一并列出来。这篇文章适合谁看两类人一是做工业控制、运动控制、机器人实时通信的嵌入式 Linux 工程师想在 Ubuntu 24.04 上搭一套 Xenomai 4 验证环境二是对双内核实时方案感兴趣想搞明白 EVL 核心和 Linux 内核到底怎么共存、libevl 是怎么把实时能力暴露给用户空间的研究型开发者。无论你是哪种照着这篇走至少能省下两三个通宵的排查时间。1. Xenomai 4与双内核架构为什么这次绕过不了Dovetail和EVL1.1 从Xenomai 3到Xenomai 4I-pipe谢幕Dovetail接棒很多接触 Xenomai 3 的人印象最深的是 I-pipeInterrupt Pipeline那个中断流水线补丁。它的思路是在 Linux 内核下面垫一层中断分流机制把实时中断信号优先导给 Xenomai 的 Cobalt 核心处理再把非实时的中断按原路径喂给 Linux。这套设计在 3.x 时代跑了很多年稳定性不错但问题也很明显补丁越做越庞大跟内核的耦合越来越深每升级一个内核版本I-pipe 都要跟着做不小的适配。到了 Xenomai 4Philippe Gerum 直接换了一条路。I-pipe 被 Dovetail 取代后者同样是一个中断流水线机制但设计上更轻量、更模块化它不再试图接管整个中断子系统而是只做“管道化”这件事——把外设中断和内核事件以流水线形式同步给实时核心其余的都放给 Linux 自己处理。Dovetail 的名字也很形象就是把一根针扎透内核让你能把实时核心挂上去同时尽量少干涉内核本身的逻辑。EVL 核心就是在 Dovetail 之上构建的实时调度核心。你可以把它理解成一个小小的、专门跑硬实时任务的调度器它跟 Linux 内核共享同一个物理地址空间但拥有独立的中断入口和调度策略。EVL 核心的源码最终是编译进 Linux 内核镜像里面的这也意味着它不是用户空间的一个进程而是真真正正的“第二内核”。说到这你可能就会问既然是双内核是不是得下载一个单独的 EVL 内核源码包不是。EVL 核心本身不是一个独立内核它的代码会合入 Linux 内核源码树通常是作为 drivers/evl/ 这样的子目录存在然后跟你选定的 Linux 内核一起编译、打包。所以整个双内核的“双”指的是最终运行时有 Linux 和 EVL 两套调度实体共存而不是你要编译两个内核镜像。1.2 “双内核”到底指什么EVL核心和Linux内核的关系为了避免后面看得云里雾里这里先把关系理清楚。在一台跑 Xenomai 4 的机器上CPU 上同时存在两个核心一个是 Linux 内核本身负责进程管理、内存管理、网络协议栈、文件系统这些通用任务另一个是 EVL 核心负责调度那些标记为实时的线程。常规进程跟实时线程跑在同一个 CPU 上怎么保证实时线程不被进程切换干扰靠的就是 Dovetail 提供的中断流水线机制——实时中断信号会优先被 EVL 核心消费非实时中断才落到 Linux 那边。这套架构对应到用户空间就是 libevl 这个库。应用程序通过 libevl 提供的 API可以创建 EVL 实时线程、设置调度策略、绑定 CPU、创建实时信号量等。你写的代码还是普通的 C/POSIX 风格程序下拉几个函数调用就能把关键业务逻辑切到 EVL 核心去执行。所以整个编译流程也从这里分成了两大块第一块是把 EVL 核心编进 Linux 内核第二块是编译用户空间的 libevl 库和测试工具。第一块没搞定第二块的程序跑不起来会直接报“找不到 EVL core”之类的错误。嗯这也就是为什么不管你在哪个教程里看到编译 Xenomai 4开头永远是“先编译内核”没有捷径。2. 环境准备工具链、版本配对与源码获取2.1 Ubuntu 24.04下的编译依赖全清单在 Ubuntu 24.04 上编译 Linux 内核第一步是把工具链装齐。别以为装个 build-essential 就万事大吉内核编译还会用到一些平时不太起眼的包缺一个就在 configure 或者 make 阶段报一个错非常磨人。我这边的完整清单如下sudo apt update sudo apt install -y git build-essential flex bison dwarves \ libssl-dev libelf-dev libncurses-dev \ debhelper dpkg-dev kmod cpio autoconf automake libtool pkg-config逐个说一下关键项flex 和 bison 是内核编译时需要生成词法/语法分析器用的缺了会在编译早期报“/bin/sh: 1: flex: not found”这类错误dwarves 是生成 BTFBPF Type Format信息时用的如果你开了 CONFIG_DEBUG_INFO_BTF没有它会直接编译失败libssl-dev 和 libelf-dev 分别对应内核模块签名和 ELF 文件解析libncurses-dev 是用来打开 menuconfig 的没有它你只能改 .config 文件debhelper 和 dpkg-dev 是后面用 make bindeb-pkg 打 deb 包时必须的。我这次是在一台 i5-12600K、32GB 内存的测试机上跑的编译 6.6 LTS 内核4 个并行任务大概是十分钟左右如果直接用 make -j8 会更快。不过建议你编译时把并行数控制在物理核心数附近免得内存吃紧导致 OOM。2.2 容易踩的版本坑内核、Dovetail、EVL必须三边对齐这是整篇文章我最早想写、也是最想强调的一点。Xenomai 4 的 EVL 核心、Dovetail 补丁和 Linux 内核三者之间是强绑定关系不是随便拿一个内核版本就能打上补丁。Dovetail 补丁是针对特定内核版本做的比如 dovetail 的 6.6 分支只能打在 Linux 6.6 系列上大版本不能换小版本尽量也要对齐。我当时第一次操作时图省事直接去 kernel.org 下了最新的 6.8 内核结果 dovetail 仓库对应分支还没跟进补丁打上去一堆冲突整个流程卡了一晚上。后来重新翻官方文档才发现版本配对表写得清清楚楚。判断版本是否匹配最快的方法是去拉一下官方仓库看看分支和 taggit clone https://gitlab.evlproject.org/evl/dovetail.git cd dovetail git tag -l | grep dovetail输出里面会看到类似 dovetail-v6.6.、dovetail-v6.8.这样的标签。每个标签对应一个 Linux 内核小版本。选择哪个我的建议是优先选 LTS 版本的长期维护分支比如 6.6 LTS后续 bug 修复多方案也相对稳定。接下来是 EVL 核心和 libevl 的仓库git clone https://gitlab.evlproject.org/evl/evl.git git clone https://gitlab.evlproject.org/evl/libevl.gitevl 仓库里包含了 EVL 核心的源码和合入内核的脚本libevl 则存放用户空间库和测试工具。版本上注意同样要跟 dovetail 内核匹配官方仓库里一般会把“当前开发分支支持的内核版本”写在 README 或 release notes 里决定动手之前务必先确认一下。这块我的实操习惯是建一个专门的目录保存所有源码mkdir -p ~/xenomai4 cd ~/xenomai4后面所有源码、补丁、编译产物都放在这个目录下面目录整洁的好处是排查问题的时候不用到处翻文件。3. 应用Dovetail补丁与合入EVL核心3.1 先给内核打Dovetail补丁假设你已经从 kernel.org 下载并解压了匹配的 Linux 内核源码比如 linux-6.6.31.tar.xz。先进入内核源码目录然后把 dovetail 仓库里的补丁按顺序打上。dovetail 补丁不是一个单独的大补丁文件而是按提交顺序排列的一系列补丁。手动执行 git apply 很容易搞乱顺序我建议用脚本方式批量处理。dovetail 仓库里通常会自带工具脚本或者你可以这样操作cd ~/xenomai4 git clone https://gitlab.evlproject.org/evl/dovetail.git cd linux-6.6.31 ../dovetail/scripts/patch-apply.sh --linux../linux-6.6.31 --dovetail../dovetail这里我直接使用官方维护脚本而不是手动打 patch主要原因是脚本内部会校验当前内核版本和补丁版本是否匹配能提前拦住一部分低级错误。如果你的环境没有这个脚本也可以手动打for patch in ../dovetail/patches/dovetail/*.patch; do git apply --check $patch git apply $patch done注意git apply 前最好先确认内核目录是一个 git 仓库执行一下 git init 或 git log 确认状态。不是 git 仓库的话git apply 的 --check 模式会让你少很多麻烦但补丁回滚就有点痛苦所以我强烈建议先在内核目录里执行 git init方便后面出问题回退。打完补丁后检查一下补丁是否真的生效看内核顶层 Kconfig 里有没有多出 DOVETAIL 相关选项grep -i dovetail Kconfig能搜到内容说明补丁打进去了。搜不到就回头检查版本别硬着头皮往下走。3.2 把EVL核心代码合入内核树Dovetail 补丁只是第一步它给你的内核加上了中断流水线的能力但此时还没有 EVL 核心。EVL 核心的代码要从 evl 仓库合入内核树。EVL 核心的合入不像 dovetail 那样是一串补丁而是把整个 evl 核心源码目录复制进内核树并修改 Kconfig 和 Makefile。evl 仓库里通常会提供安装脚本我当时用的方式是cd ~/xenomai4/evl ./scripts/install.sh --root../linux-6.6.31如果没有脚本或者你用的版本结构不同就需要手动复制。做法是把 evl 仓库里 core 相关的源码文件比如 evl 子目录复制到内核树的对应位置修改内核根 Makefile 和 drivers/Kconfig、drivers/Makefile把 evl 目录加进去。这个手动步骤比较繁琐而且每换一个内核版本结构都可能微调建议尽量用官方脚本。合入完成后在内核源码目录下执行 make menuconfig你应该能看到一个新的 EVL 菜单里面有很多 EVL 相关的配置项。能看到这个菜单说明 EVL 核心代码已经成功合入内核树可以进入配置阶段了。4. 内核配置与编译安装4.1 关键内核配置项逐个说明EVL 核心的配置项虽然多但绝大多数情况下保持默认就行只有几个关键开关必须确认。下面是我整理的必查项配置项建议值说明CONFIG_DOVETAILy启用 Dovetail 中断流水线这是 EVL 核心运行的基础必须开启CONFIG_EVLy启用 EVL 核心把这个打开EVL 相关代码才会编进内核CONFIG_HIGH_RES_TIMERSy高精度定时器实时任务靠它做精确睡眠和超时控制CONFIG_PREEMPT建议关闭或选 Voluntary与 EVL 双内核调度存在理念冲突我实测会导致预测性变差CONFIG_CPU_ISOLATIONy配合 isolcpus 内核参数把部分 CPU 核从 Linux 调度器中隔离出来给实时任务用CONFIG_DEBUG_INFO_BTF建议关闭能减少大量编译时间和磁盘占用如果不需要 BPF 工具链可以关掉还有一个我特别要提醒的坑如果你之前用过 RT-Preempt习惯性开 CONFIG_PREEMPT_RT那这里必须关掉。EVL 核心跟 PREEMPT_RT 是同级别的实时方案二者同时在系统里没有任何意义反而可能因为调度器冲突导致系统稳定性变差。配置方法上我推荐用 make menuconfig 打开图形界面在搜索框里输入 EVL把关键项过一遍。菜单项是分层的一般在 Kernel Features - EVL Core 或者 Processor type and features 下面。如果你跟我一样懒得一个个菜单找可以直接往 .config 文件里追加这几行然后再跑 make olddefconfig 让系统自动补齐依赖CONFIG_DOVETAILy CONFIG_EVLy CONFIG_HIGH_RES_TIMERSy # CONFIG_PREEMPT_RT is not set注意这种手动改 .config 的方式只推荐在已经熟悉各配置项关系的情况下使用。第一次操作我还是建议老老实实跑一遍 make menuconfig至少能把整体结构看一遍心里有底。4.2 编译成deb包并装进Ubuntu配置确认完毕开始编译。这里我不建议直接 make install因为 Ubuntu 的 initramfs 和管理体系跟 make install 生成的 /boot 文件契合度不够好容易出现启动问题。更好的方式是用 deb 包形式安装让 Ubuntu 自己的包管理机制接管内核。执行make -j$(nproc) bindeb-pkg这个命令会在内核源码的上层目录也就是 linux-6.6.31 的上一级生成几个 .deb 文件。等待编译完成然后进去安装cd ../ sudo dpkg -i linux-image-*.deb linux-headers-*.deb linux-libc-dev*.deb如果编译过程中报缺少 debhelper 或 dpkg-dev 的错误回头把 2.1 节里的依赖包补上再重新执行。安装完内核 deb 包之后先别急着重启。你还需要查看一下 GRUB 是否已经把新内核加进启动菜单sudo update-grub再确认grep -i menuentry /boot/grub/grub.cfg应该能看到类似“Ubuntuwith Linux 6.6.31-evl”这样的条目。看到它说明内核已经就绪。4.3 GRUB引导与首次启动验证到了这一步重启前最好做两件事。第一确认 BIOS/固件里的 Secure Boot 处于关闭状态。理由很简单你自己编译的内核默认没有给 UEFI 签名Secure Boot 开启的状态下GRUB 加载这个内核镜像会被直接拒之门外。如果工作环境对安全启动有硬性要求就得用 sbsigntool 对内核和模块做自签名流程会多出一截这次先按下不表。第二修改内核启动参数为实时任务预留 CPU 核。这一步不是必须的但强烈建议做。编辑 /etc/default/grub在 GRUB_CMDLINE_LINUX 里加入隔离参数GRUB_CMDLINE_LINUXisolcpus2-3 nohz_full2-3 rcu_nocbs2-3这个配置的意思是把 CPU2 和 CPU3 从 Linux 的常规调度中隔离出来专门跑 EVL 实时任务。我的测试机是 4 大核 8 线程留 CPU2/3 给实时任务其余给 Linux 系统和个人桌面互不干扰。如果你的机器 CPU 核数较少哪怕只隔离一个核也行比如 isolcpus1。这里面的原则是实时任务所在的核越干净延迟抖动越小。改完执行 sudo update-grub然后重启在 GRUB 菜单里选择带 evl 字样的内核。启动完成后检查内核版本和 EVL 相关输出uname -r dmesg | grep -i evl成功的话dmesg 里应该能看到 EVL core 初始化相关的日志比如 EVL 核心版本号、调度器信息等。看到这行日志内核层面的活就算干完了。5. libevl用户空间库编译、权限与udev规则5.1 从源码编译libevl内核搞定接下来编译用户空间的 libevl。这一步跟普通 C 项目一样configure、make、install 三连但有几个前提依赖需要注意。先在 clon 下来的 libevl 目录下执行cd ~/xenomai4/libevl ./autogen.sh ./configure --prefix/usr make -j$(nproc) sudo make install如果 autogen.sh 报错说明前面提到的 autoconf、automake、libtool 没装全。补齐依赖后重新执行即可。configure 阶段有几个可选项比如 --enable-debug通常保持默认就行。如果你需要把 libevl 编译成静态库或者交叉编译到其他架构可以在 configure 阶段用 --host 指定工具链。我这台机器直接原生编译省事。make install 做完后验证一下库文件和工具是否就位ls -l /usr/lib/libevl* which evl-testevl-test 是 libevl 自带的一组测试工具集合如果编译安装正常它会被放到 /usr/bin 下。另外 libevl 还提供了 evl-latency、evl-utils 等工具用不着一一记住名字跑一下 evl-test --help 就能看到所有可用的测试项。5.2 设备节点与用户权限libevl 程序运行时会访问 /dev/evl 设备。EVL 核心在初始化时会在 /dev 下创建设备节点但默认的属主和权限可能不是普通用户能访问的。我遇到的情况是只有 root 才能操作日常调试很不方便。解决办法是添加一个 evl 用户组并通过 udev 规则把设备节点归属于这个组sudo groupadd evl sudo usermod -aG evl $USER然后写一条 udev 规则。在 /etc/udev/rules.d/ 下新建文件 99-evl.rulesKERNELevl, MODE0660, GROUPevl规则内容很简单当内核注册名为 evl 的设备时设置权限为 0660属组为 evl。保存后重启 udev 或 reload 规则sudo udevadm control --reload-rules sudo udevadm trigger需要注意的是usermod 添加组之后当前登录会话并不会立刻生效。最保险的做法是注销重新登录或者执行 newgrp evl 切换一下当前会话的组身份。如果没做这一步即使 udev 规则正确普通用户运行 evl-test 也还是会遇到 Permission denied。还有一个排查点如果你重启后 /dev/evl 设备节点根本没出现那就不是权限问题而是内核里的 EVL 核心可能没初始化成功。回到第 4.3 节的 dmesg 检查先确认内核运行正常再回头查设备节点。6. 运行evl-test验证实时性是否真的生效6.1 跑通第一轮测试环境准备完毕来到验证环节。跑测试之前把你当前用户加入 evl 组这个前置条件再确认一遍。我用的是 root 身份直接测方便是方便但生产环境还是建议用普通用户加组的方式运行。先列出所有可用测试/usr/bin/evl-test --list然后跑一个最简单的实时线程创建/销毁测试evl-test thread这个测试会创建若干 EVL 实时线程然后在 EVL 核心和普通内核线程之间做切换。如果输出显示全部 PASS说明用户空间到内核空间的整个 EVL 通道已经打通。如果这里就报错常见提示是 “Failed to create EVL thread” 或 “No such device”优先排查内核配置和 dmesg。第一轮测试通过后跑延迟测试。这是评价实时系统最核心的指标evl-test latency命令跑起来后会持续计时并统计延迟分布。如果你在命令行加了 sudo 或已经获得 evl 组权限应该能看到一长串采样值涉及最小延迟、平均延迟、最大延迟等。6.2 读懂测试输出与延迟数字evl-test latency 的输出很像 cyclictest 的格式会周期性刷新显示每个采样点的延迟。我这边在 i5-12600K 上的结果大概长这样最小延迟 2-3 微秒平均延迟 4-5 微秒最大延迟 12-15 微秒。这个数字对于大部分工业控制场景已经相当能打。如果你的最大延迟经常跳到 100 微秒以上别急着怪 Xenomai先怀疑两个因素一个是 CPU 隔离没做好另一个是系统里还有其它高优先级中断在干扰。判断隔离是否生效可以用如下命令cat /sys/devices/system/cpu/isolated输出应该包含你之前在 GRUB_CMDLINE_LINUX 里写入的 CPU 编号。如果这里显示为空说明 isolcpus 参数没生效大概率是 grub.cfg 没更新干净。除了延迟还建议跑一个负载测试evl-test stress这个测试会在隔离核上启动实时线程同时在其余 CPU 上制造大量 Linux 负载考验双内核共存时的抗干扰能力。说白了就是一场“一边让系统满载、一边看实时任务是否依然准时”的极限测试。我实测满负载下延迟抖动会略升高但最大延迟仍然控制在几十微秒内这个结果对绝大多数应用来说够用了。7. 避坑实录我从源码到libevl测试踩过的坑7.1 编译阶段的典型报错与处理这里我把整个过程中踩过、也看朋友踩过的编译期问题汇总成表格方便你对照排查报错现象根因解决办法patch 文件应用失败出现大量 conflict内核版本与 dovetail 分支不匹配去 dovetail 仓库看 tag确认内核大版本一致后再打补丁编译报 flex: command not found缺少 flexsudo apt install flex编译报 BTF: .tmp_vmlinux.btf failed缺少 dwarves 包sudo apt install dwarves或关闭 CONFIG_DEBUG_INFO_BTFscripts/extract-cert 相关签名错误缺少 libssl-dev安装 libssl-dev 后重新 makemake bindeb-pkg 时提示 missing debian 相关文件缺少 debhelper / dpkg-dev补装 debhelper dpkg-dev编译时间极长且内存不足-j 并行任务开太多用 -j$(nproc) 或更小数值比如 -j4还有一个比较隐蔽的问题如果你之前编译过其它内核版本内核源码目录里的 .config 有残留新旧配置混在一起很容易导致 EVL 相关选项被自动关掉。遇到这种情况最干净的办法是make distclean cp /boot/config-$(uname -r) .config # 或者用默认配置然后重新 make menuconfig确认 EVL 菜单再编译。7.2 运行阶段的诡异问题与排查编译通过只是第一步运行阶段坑更隐蔽。第一个常见的是启动后系统直接卡在 GRUB 或黑屏。优先检查 Secure Boot关闭后基本能解决。如果你不想动固件设置只能给内核签名流程复杂不少非必要不建议在首次调试时做。第二个现象是系统能启动但 dmesg 里完全找不到 EVL 相关日志。这种情况大概率是 GRUB 没有真正加载你编译的新内核而是启动到了旧内核。用 uname -r 查看当前内核版本号如果不是你编译的那个回到 GRUB 菜单手动选择并确认 update-grub 执行成功。第三个现象是 evl-test 能跑但总有几个 case 失败报 “No such file or directory” 或 “Invalid argument”。我遇到的一次是 /dev/evl 设备节点存在但权限不对普通用户无法打开。解决办法就是 5.2 节的 udev 规则和用户组配置。还有一次是/sys/kernel/evl目录根本不存在这通常是内核配置里漏开了 EVL 的 sysfs 导出项回去检查 CONFIG_EVL 相关子选项。第四个现象比较恶心evl-test 有时跑起来正常但过十几分钟就卡死。后来发现是我把实时任务和图形界面放在了同一个 CPU 上而桌面环境的 GL 渲染线程会频繁触发中断把实时调度扰乱了。解决方式就是配置 isolcpus 隔离专用核然后把测试工具显式绑定到隔离核上运行比如用 taskset 指定 CPUtaskset -c 2 evl-test latency这样就把实时任务钉在 CPU2 上不再受桌面环境干扰。最后一个运行期提醒如果你在虚拟机里做尝试比如 VirtualBox 或 QEMU 里跑 Ubuntu 24.04就会发现 evl-test 的结果一塌糊涂延迟动不动就几百微秒甚至毫秒级。这不是编译问题而是虚拟化层引入了无法预期的中断延迟。Xenomai 4 的实时性验证请在物理机上做虚拟机只适合做“能不能编译通过、能不能启动”的功能验证别拿它测延迟数据。我个人在实际操作中还有一个习惯每次换内核版本或者改配置之后先把旧的编译产物清干净再重新来一遍。这套流程从源码到 libevl 测试跑通一次大概需要两三个小时但只要你把版本配对和 CPU 隔离这两个大方向把握住后面基本就是一路顺畅。希望这份避坑笔记能帮你少走几段弯路早点把实时环境跑起来。