如果你正准备在Ubuntu上跑EtherCAT主站、机器人实时控制或者高精度数据采集,那么“给内核打上PREEMPT_RT实时补丁”几乎是绕不开的一步。但我要先给你打个预防针:补丁本身不难打,真正让人血压飙升的,是打完之后显卡驱动那一堆烂摊子。上个月我在一块工控板上装RT内核,内核编译一次通过,结果进系统后NVIDIA驱动直接罢工,桌面起不来,nvidia-smi报错,折腾到凌晨两点才把问题理清楚。这篇文章就是把整条路线完整捋一遍:从要不要打补丁、怎么选内核和补丁版本、如何编译安装RT内核,到补丁装完后显卡驱动挂掉的排查与修复,全程在Ubuntu 22.04上验证过,内核采用6.6.119配合对应实时补丁,这个组合对EtherCAT常用的igc网卡驱动支持也很到位。适合搞工业控制、机器人实时通信、机器视觉集成的朋友参考,也建议那些想在带NVIDIA显卡的机器上跑实时Linux的人先看完再动手。
1. 为什么说Ubuntu默认内核撑不起“实时”这件事
1.1 PREEMPT_RT改了什么:中断线程化、可抢占锁与优先级继承
Ubuntu发行版自带的generic内核,日常跑Web服务、写代码、看视频完全没问题,但它解决不了“确定性”问题。普通内核里,一个高优先级任务想抢CPU时,可能撞上中断处理、自旋锁临界区或者其他不可抢占的内核路径,调度延迟的波动范围可以到几十毫秒甚至上百毫秒。对于人机交互这种场景,几百毫秒延迟你根本感知不到,可对EtherCAT这种要求周期性抖动在微秒级的协议来说,这简直是灾难。
PREEMPT_RT补丁做的事情,本质上是把内核变得“几乎处处可抢占”:
- 中断线程化:以前一个硬中断来了,CPU立刻跳进去处理,处理完之前谁也别想抢。打补丁后,中断变成内核线程,有优先级,可以被调度器管理,高优先级实时任务可以压过中断线程先跑。
- 可抢占的自旋锁:普通自旋锁持有期间禁止抢占,RT补丁把大多数自旋锁改成可睡眠的互斥锁,锁等待不会让实时任务傻等。
- 优先级继承:当低优先级任务占着锁、高优先级任务在等锁时,低优先级任务会临时提升优先级,避免“优先级反转”导致实时任务被无限期卡住。
用生活化的方式理解:普通内核像一个特别忙的餐厅,厨师炒菜时手上的活不干完绝不接新单,所以VIP客人来了也得等。RT内核相当于给餐厅装了传菜铃,任何时刻有VIP订单进来,厨师都能立刻放下手里的活去响应,而且其他服务员还得给VIP让路。实时补丁不会让系统“变快”,它让系统对关键任务的响应时间变得可预测、波动极小,这才是“实时”的意义。
1.2 用EtherCAT和igc网卡来理解“实时抖动”
EtherCAT主站软件(比如IgH、Acontis、TwinCAT)依赖网卡以固定周期发送帧,周期通常是1ms甚至250us。主站每发一帧,从站处理完再传回,主站等到回帧后立刻开始下一周期。如果内核调度抖动大,主站线程错过发帧窗口,轻则周期漂移,重则总线断连、从站进入Safe-OP失败。
散热器上这类主站最常用的网卡是Intel I210/I225/I226等型号,对应内核里的igc或igb驱动。6.6.119这个内核版本值得特别提一下,它对igc系列网卡的支持比较成熟,很多EtherCAT主站厂商的兼容性列表里也包含这个驱动。配合RT补丁后,实时主站线程的周期抖动可以从几十微秒降到个位数微秒。
所以你在选内核版本时,不能只看“最新”,还得看两个匹配:一个是RT补丁是否发布了对应版本,另一个是网卡驱动在你的内核里是否稳定。6.6.119正好同时满足这两个条件,这也是为什么它成了当前不少工控方案的默认选择。
1.3 先分清你是真需要RT,还是看着厉害
动手之前先泼一盆冷水:不是所有项目都需要RT补丁。如果你只是普通的数据采集,或者控制周期在10ms以上,Ubuntu默认内核配好nice优先级,可能就够了。打了RT补丁的内核,因为增加了大量可抢占点,整体吞吐量会有一定下降,某些批量运算场景反而变慢。
我的判断标准很简单:
- 控制周期在1ms或更短,且对抖动有硬性要求的,必须RT。
- 控制周期在几毫秒以上,容忍几十微秒抖动的,先用普通内核+实时线程优先级试试,通常能撑住。
- 纯粹跑深度学习训练、科学计算,完全不用碰RT,把GPU驱动伺候好就完了。
想清楚了再往下走,免得白折腾半天,最后发现瓶颈根本不在内核。
2. 动手前的版本匹配与工具链准备
2.1 为什么6.6.119配patch-6.6.119-rt是当前最稳的选择
实时补丁的发布节奏和主线内核严格一一对应。拿到的补丁文件是patch-6.6.119-rt.patch,它只能打在linux-6.6.119.tar.xz这个源码包上,版本差一点都不行。你没法拿一个6.6.120的内核去硬打6.6.119的补丁,这种错位会引发大量patch冲突,修起来非常痛苦。
选版本时要考虑三点:
- 长期维护分支:6.6是LTS(长期支持)版本,RT补丁更新持续且稳定,社区和厂商都会持续跟进。
- 与周边模块的兼容性:EtherCAT主站、NVIDIA驱动、第三方内核模块,它们对新内核的适配总是滞后的。选一个已经发布一段时间、被大量人验证过的版本,比追新稳妥得多。
- 补丁发布时间:去内核官网的RT分支页面看,
patch-6.6.119-rt已经正式发布,说明该版本没被社区发现明显问题。
你的Ubuntu版本只要是20.04以上,编译这个内核都没问题。20.04用老一点工具链也能编,22.04是当前体验最好的,24.04注意一下gcc版本较新,个别配置项可能有小变化,但整体不影响。
2.2 编译依赖清单与磁盘规划
编译内核需要装一堆工具,直接用apt一把梭:
sudo apt update sudo apt install build-essential libncurses-dev libssl-dev bc bison flex libelf-dev dwarves逐个说下这些是干嘛的:
build-essential:提供gcc、make等基础编译工具链。libncurses-dev:make menuconfig图形化配置界面需要它。libssl-dev:内核里部分模块(比如内核模块签名)需要OpenSSL头文件。bc:内核编译脚本里大量用到这个计算器工具,少它会在配置阶段直接报错。bison和flex:解析内核配置文件和设备树文件的语法分析器。libelf-dev:编译某些ELF相关工具需要。dwarves:如果开启BTF(BPF Type Format)功能,pahole工具需要这个包,Ubuntu 22.04以后的内核配置经常默认带BTF。
磁盘空间也要提前看。完整编译一次内核,源码加编译产物大概需要15~20GB空间,其中.o文件占大头。我吃过一次亏,只给/分了30GB,编译到一半磁盘满了,整个构建失败。建议编译前用df -h检查,给/usr/src所在的挂载点留够空间,如果不够就提前清理APT缓存或者调整挂载。
下载源码时,注意别用Git仓库自己切tag再等RT补丁,最省心的方式是直接下载两个文件:
cd /usr/src sudo wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.119.tar.xz sudo wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.6/older/patch-6.6.119-rt.patch.xz如果你在的网络环境访问kernel.org比较慢,可以用镜像站或者把文件下载后传到/usr/src。文件不大,源码压缩包大概130MB左右,补丁压缩包只有几MB,传起来不难。
3. 打补丁与内核配置:紧盯着抢占模型就对了
3.1 解压、打补丁、校验
把源码包解压,然后进入目录打补丁:
cd /usr/src sudo xz -dk linux-6.6.119.tar.xz sudo tar xf linux-6.6.119.tar sudo xz -dk patch-6.6.119-rt.patch.xz cd linux-6.6.119 sudo patch -p1 < ../patch-6.6.119-rt.patchpatch -p1的意思是去掉路径里的第一层目录,因为补丁文件内的路径格式是a/kernel/sched/core.c b/kernel/sched/core.c,需要从linux-6.6.119目录内执行才能正确命中。
打完补丁后,建议看一眼是否有FAILED字样。正常情况下输出会是一长串patching file ...,如果你看到任何.rej文件生成,说明补丁没打干净。用find . -name "*.rej"搜一下,有结果就说明源码和补丁版本不匹配,需要重新核对。
3.2 基于现有Ubuntu配置生成.config的三种做法
内核编译前必须先生成.config配置文件。最省事的不是从零配置,而是基于当前Ubuntu内核的配置修改,这样能保证默认配置和你现有硬件兼容。
做法一:复制当前内核配置(最推荐)
cp /boot/config-$(uname -r) .config这里有个坑:当前如果你原本跑的就是generic内核,它的配置里开启了很多Ubuntu定制项,这些项在RT补丁下大多数没问题,少数会有Kconfig依赖冲突。别慌,先跑一下make olddefconfig让它自动把失效项清理掉:
make olddefconfig做法二:用scripts/config脚本精确打开RT模式
scripts/config --enable PREEMPT_RT make olddefconfig这种做法比进menuconfig里翻菜单高效得多,尤其适合脚本化操作。打开PREEMPT_RT后,内核会自动关闭其他抢占模式(比如PREEMPT、PREEMPT_DYNAMIC),因为实时抢占模型是互斥的,不可能同时存在。
做法三:make menuconfig手动确认
想直观确认配置状态的话,跑make menuconfig,进入Kernel Features -> Preemption Model,选中Fully Preemptible Kernel (Real-Time)。如果你看不到这一项,十有八九是.config里还有别的抢占模型冲突项,回到做法一重新来。
3.3 关键配置项详解与常见配置冲突
打开RT模式后,有几个配置项值得顺手检查:
CONFIG_HZ:建议保持在1000Hz,也就是CONFIG_HZ_1000=y。实时任务调度粒度更细,EtherCAT周期精度更高。CONFIG_NO_HZ_FULL:可以开启,让用户态的实时任务所在CPU核不再接收周期性时钟中断,减少干扰。但注意,这个配置需要配合内核启动参数nohz_full=使用,否则不会生效。CONFIG_DEBUG_INFO:如果你的磁盘空间紧张,可以考虑关掉,能省不少编译时间和空间。不过没了它,后续排查内核问题时不方便看符号,建议空间够就保留。CONFIG_SCHED_DEBUG:调试用,生产环境关掉,减少调度路径上的额外开销。CONFIG_FTRACE:保留,做实时性排查时ftrace几乎是必备工具,后续想看中断延迟、函数调用耗时都靠它。
最常见的配置冲突是:你复制了Ubuntu的config后,里面可能打开了CONFIG_PREEMPT(普通抢占),当你通过menuconfig切到RT时,Kconfig会自动把CONFIG_PREEMPT关闭。但如果你在旧配置上同时开了某些依赖具体抢占模型的调试选项,会报出类似“warning: ... selects PREEMPT_NONE which has unmet direct dependencies”这样的错误。这时候不用手工去改那一堆依赖项,直接跑make olddefconfig让它自动按Kconfig规则调整。
有个小技巧:改完配置后,把.config备份一份。万一后面编译失败,不用从头再来。
cp .config /usr/src/config-6.6.119-rt.backup4. 编译、安装与启动后的实时性验证
4.1 用bindeb-pkg生成deb包而不是直接make install
我强烈建议你用deb包方式安装,而不是传统的make install && make modules_install。原因很简单:deb包能让内核纳入dpkg管理,卸载、升级都有章可循,不至于在系统里留下一堆不知道是谁的内核文件。
在linux-6.6.119目录下执行:
make -j$(nproc) bindeb-pkg-j$(nproc)是让编译器用满所有CPU核心,我8核16线程的机器编译这个内核,大概花了25分钟。如果你的是4核老机器,做好等1小时以上的准备。编译期间可以干点别的,但别开make clean或者删源码目录。
编译完成后,会在/usr/src生成几个deb文件:
cd /usr/src ls -lh linux-*.deb重点装这两个:
sudo dpkg -i linux-image-6.6.119-rt*.deb linux-headers-6.6.119-rt*.deblinux-headers包千万别省,后续DKMS编译NVIDIA、EtherCAT等第三方内核模块全靠它。很多人在这一环省了,结果显卡驱动编译时找不到头文件,白折腾半天。
4.2 安装、更新GRUB、启动选内核
装完deb包后,更新GRUB菜单:
sudo update-grub重启后,在GRUB菜单的Advanced options for Ubuntu下能看到6.6.119-rt这个内核。第一次建议手动选它启动,别直接改默认项,先确认能正常进系统再做调整。
如果想默认进RT内核,编辑/etc/default/grub:
GRUB_DEFAULT="Advanced options for Ubuntu>Ubuntu, with Linux 6.6.119-rt"然后sudo update-grub。注意这个写法是两级子菜单的索引方式,每台机器实际的菜单名可能有差异,用之前先看grep menuentry /boot/grub/grub.cfg确认一下条目名。
4.3 cyclictest实测:怎么看实时性达标没达标
启动RT内核后,先做基础验证:
uname -r # 输出应包含 6.6.119-rt cat /sys/kernel/realtime # 输出 1 表示当前内核启用了RT如果/sys/kernel/realtime输出是0,说明内核虽然带着rt后缀,但抢占模型实际没生效,基本可以确定是配置阶段没把CONFIG_PREEMPT_RT打开。
进一步做实时性定量测试,需要安装rt-tests:
sudo apt install rt-tests然后跑一个典型的cyclictest测试:
sudo cyclictest -t 1 -p 99 -i 1000 -l 100000参数含义:-t 1只跑一个实时线程,-p 99设为SCHED_FIFO策略最高优先级,-i 1000周期设为1000微秒,-l 100000跑10万个周期总共100秒。看输出里的max列:
T: 0 ( 1234) P:99 I:1000 C: 100000 Min: 2 Act: 3 Avg: 3 Max: 18Max: 18表示最大延迟18微秒。在RT内核上,这个数值通常能压在20微秒以内;如果跑出上百微秒甚至毫秒级,说明系统里有干扰源,可能是NVIDIA驱动占用大量中断、BIOS电源管理设置不合理、或者CPUFreq调节器不合适。这时候先别急着优化,继续看下面显卡驱动的问题,很多时候显卡驱动修好了,实时性数据也跟着变好了。
5. 显卡驱动在RT内核下崩了的完整排查链路
5.1 先认清崩掉的三种典型症状
RT内核装好、重启、选新内核进入,结果发现:
- 桌面起不来:GDM/ LightDM 登录界面一直转圈,或者黑屏只有一个鼠标光标。
- 进到桌面但分辨率不对:识别不出显示器,图形卡在低分辨率,或者显示
nouveau相关的报错。 - 终端里nvidia-smi直接报错:比如
NVIDIA-SMI has failed because it couldn't communicate with the NVIDIA driver。
这三种症状本质是同一个根源:RT内核启动时,NVIDIA内核模块没有被正确加载。要么是模块编译失败,要么是模块签名无效被拒绝加载,要么是nouveau驱动抢先占用了GPU。
5.2 按顺序排查:dmesg、DKMS状态、头文件、Secure Boot
别一上来就重装驱动,先按链路排查,每步都有明确结论。
第一步,在GRUB菜单按e,在linux行末尾加nomodeset,然后按Ctrl+X启动。nomodeset会让内核不做模式设置,用基本显示输出,至少能进终端排查问题。如果你用Ubuntu Server版没桌面,可以跳过这步。
第二步,进入系统后看日志,确认NVIDIA模块加载失败的现场:
dmesg | grep -i nvidia | tail -n 30 journalctl -b -g nvidia常见的报错信息有“module verification failed: signature not found”和“Unknown symbol”,前者指向Secure Boot签名问题,后者指向DKMS编译失败或者头文件不匹配。
第三步,查DKMS模块状态:
dkms status正常输出应该是类似:
nvidia/535.xx.x, 6.6.119-rt, x86_64: installed如果显示build failed,说明DKMS尝试在新内核上重新编译NVIDIA模块时失败了。失败的详细日志在/var/lib/dkms/nvidia/535.xxx/build/make.log,直接打开看最后几十行,能定位到具体编译错误。
第四步,确认内核头文件是否装全:
ls /usr/src/linux-headers-6.6.119-rt*如果你用bindeb-pkg方式安装,头文件是单独的deb包,必须确认已经装好。没有头文件,DKMS根本无从构建。
第五步,查Secure Boot状态:
mokutil --sb-state如果输出SecureBoot enabled,而你的NVIDIA模块没有通过MOK签名,内核会拒绝加载它。这就是开头那个“signature not found”报错的来源。
5.3 修复方案:重装DKMS、runfile安装与核显兜底
方案A:重装已安装的NVIDIA驱动(最简单,但请先处理Secure Boot)
如果排查确认是DKMS编译失败,最直接的办法是重新触发DKMS构建:
sudo apt install --reinstall nvidia-driver-535 sudo dkms autoinstallUbuntu 20.04、22.04、24.04上NVIDIA驱动版本号不同,可以根据自己机器执行ubuntu-drivers devices查看推荐版本。如果Secure Boot处于启用状态,重装前我建议先去BIOS把它关掉,或者用mokutil --import导入DKMS生成的公钥,流程有点繁琐,工控机上没有特殊安全需求的话直接BIOS关闭最快。
方案B:runfile手动安装(适合apt源里没有的驱动版本)
如果你需要特定版本NVIDIA驱动(比如CUDA版本配套的),用官方runfile更灵活:
sudo telinit 3 sudo ./NVIDIA-Linux-x86_64-550.xx.run --dkmstelinit 3会切到多用户文本模式,把图形界面完全停掉,否则安装会提示“You appear to be running an X server”。安装时选--dkms参数,让驱动注册到DKMS,这样以后换内核时它能自动重新编译。runfile安装完,nvidia-smi能出来就说明模块加载成功。
方案C:核显兜底
如果是纯工控场景,控制任务和图形界面可以分离。主板有核显输出的话,把显示器插到主板接口上,BIOS里设核显为首选显示,NVIDIA GPU只留给CUDA计算。这样就算NVIDIA驱动在RT内核下没好,系统也能正常显示,实时控制不受影响。这个方法看起来有点“逃避”,但在生产环境里特别实用,因为RT内核下NVIDIA驱动的长期稳定性,说句实在话,没有官方保证,能分离就别硬绑。
5.4 教训:驱动与内核的先后顺序到底怎么安排
我这次踩坑的根因,就是我先在内核源码目录里折腾编译,又在旧内核上把NVIDIA驱动卸载了,结果RT内核装好后DKMS找不到可用的模块源,又因为Secure Boot开着,新模块签名失败,整个链路彻底崩掉。
复盘出来的合理顺序应该是:
- 在旧内核系统上,先把NVIDIA驱动装好,确认
nvidia-smi正常。 - 再编译RT内核并安装,让DKMS用已安装的驱动源自动适配新内核。
- 重启进RT内核,如果
nvidia-smi报错,再按上面的排查链路走。
换句话说,驱动安装最好在打内核补丁之前就完成。驱动本身不依赖RT补丁,但RT内核依赖驱动源和头文件。如果驱动已经注册到DKMS,每次换内核时它会自动尝试编译,少很多手动操作的环节。
6. RT内核与显卡驱动共存的生产级建议
6.1 实时任务与GPU驱动各占各的CPU核
RT内核和NVIDIA驱动能共存,但不意味着它们应该互相干扰。NVIDIA驱动的中断、DMA、内存分配会引入不可忽略的延迟,实时任务如果和GPU中断撞在同一个CPU核上,cyclictest的max值会明显变差。
我实际用的方案是在GRUB里隔离出专用实时核:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"这段参数的意思是:CPU 2、3号核心从Linux调度器中隔离出来,不跑普通进程;nohz_full让这两个核减少周期性时钟中断;rcu_nocbs让RCU回调不在这些核上执行。然后在应用层把EtherCAT主站线程用sched_setaffinity绑到2、3号核上,NVIDIA驱动中断和桌面进程就留在0、1号核上跑。这样一来,cyclictest的抖动可以稳定在10微秒以内,GPU并行训练也不影响实时链路。
6.2 内核多引导与回滚策略
生产环境最怕的不是出问题,而是出问题后回不去。装了RT内核后,旧内核的GRUB菜单项依然保留,这是天然的回滚通道。但需要注意:如果在新内核里系统起不来,GRUB菜单还是能进的,选择旧的generic内核启动后,可以卸载RT内核,也可以继续排查。
我建议保留至少一个已确认能用的旧内核,别为了省磁盘空间把旧内核删干净。工控设备上,一个可启动的备用内核就是救命的。另外,装RT内核后如果做过update-grub,小心有些云镜像或定制系统会自动清理旧内核,要在/etc/apt/apt.conf.d/里检查有没有开自动清理,开了的话改成保留旧内核。
6.3 一套我验证过的“重来一次”安装顺序
最后一次总结我实测过的全过程顺序,跟着走最省事:
- 在原来正常工作的Ubuntu系统上,先装好NVIDIA驱动,确认
nvidia-smi正常,并确认dkms status里能看到对应模块。 - 安装编译依赖,下载6.6.119源码和RT补丁。
- 解压源码、打补丁,确认没有
.rej文件。 - 复制
/boot/config-$(uname -r)为.config,执行scripts/config --enable PREEMPT_RT,再执行make olddefconfig。 - 编译并生成deb包:
make -j$(nproc) bindeb-pkg。 - 安装
linux-image-6.6.119-rt*.deb和linux-headers-6.6.119-rt*.deb。 sudo update-grub,重启,手动选择RT内核。- 验证
uname -r和/sys/kernel/realtime,再跑cyclictest确认实时性。 - 如果
nvidia-smi报错,按第五章的链路排查;如果正常,下一步配置isolcpus和nohz_full做实时核隔离。
我实际把机器完全按这个顺序重做了一遍,全程没再出现模块签名错误和DKMS构建失败的问题。RT内核下NVIDIA驱动的确能跑,但别指望它像generic内核下那样“百毒不侵”,偶尔升级驱动或内核后还会冒出新问题。做好隔离、留好回滚通道、别删旧内核,这三件事比任何配置技巧都管用。