☰
Ubuntu 24.04源码编译Xenomai 4双内核实时环境实操指南
2026/9/27 1:50:26 网站建设 项目流程

先说个结论:在 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和EVL

1.1 从Xenomai 3到Xenomai 4:I-pipe谢幕,Dovetail接棒

很多接触 Xenomai 3 的人,印象最深的是 I-pipe(Interrupt Pipeline)那个中断流水线补丁。它的思路是在 Linux 内核下面垫一层中断分流机制,把实时中断信号优先导给 Xenomai 的 Cobalt 核心处理,再把非实时的中断按原路径喂给 Linux。这套设计在 3.x 时代跑了很多年,稳定性不错,但问题也很明显:补丁越做越庞大,跟内核的耦合越来越深,每升级一个内核版本,I-pipe 都要跟着做不小的适配。

到了 Xenomai 4,Philippe 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 是生成 BTF(BPF 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 仓库对应分支还没跟进,补丁打上去一堆冲突,整个流程卡了一晚上。后来重新翻官方文档才发现,版本配对表写得清清楚楚。

判断版本是否匹配,最快的方法是去拉一下官方仓库看看分支和 tag:

git 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.git

evl 仓库里包含了 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_DOVETAIL=y CONFIG_EVL=y CONFIG_HIGH_RES_TIMERS=y # 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

应该能看到类似“Ubuntu,with 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_LINUX="isolcpus=2-3 nohz_full=2-3 rcu_nocbs=2-3"

这个配置的意思是:把 CPU2 和 CPU3 从 Linux 的常规调度中隔离出来,专门跑 EVL 实时任务。我的测试机是 4 大核 8 线程,留 CPU2/3 给实时任务,其余给 Linux 系统和个人桌面,互不干扰。如果你的机器 CPU 核数较少,哪怕只隔离一个核也行,比如 isolcpus=1。这里面的原则是:实时任务所在的核越干净,延迟抖动越小。

改完执行 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-test

evl-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.rules:

KERNEL=="evl", MODE="0660", GROUP="evl"

规则内容很简单:当内核注册名为 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_BTF
scripts/extract-cert 相关签名错误缺少 libssl-dev安装 libssl-dev 后重新 make
make 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 指定 CPU:

taskset -c 2 evl-test latency

这样就把实时任务钉在 CPU2 上,不再受桌面环境干扰。

最后一个运行期提醒:如果你在虚拟机里做尝试,比如 VirtualBox 或 QEMU 里跑 Ubuntu 24.04,就会发现 evl-test 的结果一塌糊涂,延迟动不动就几百微秒甚至毫秒级。这不是编译问题,而是虚拟化层引入了无法预期的中断延迟。Xenomai 4 的实时性验证请在物理机上做,虚拟机只适合做“能不能编译通过、能不能启动”的功能验证,别拿它测延迟数据。

我个人在实际操作中还有一个习惯:每次换内核版本或者改配置之后,先把旧的编译产物清干净,再重新来一遍。这套流程从源码到 libevl 测试跑通一次大概需要两三个小时,但只要你把版本配对和 CPU 隔离这两个大方向把握住,后面基本就是一路顺畅。希望这份避坑笔记能帮你少走几段弯路,早点把实时环境跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询