第四章:实时性内核配置(上)
启用PREEMPT_RT、配置HZ频率、配置CPU隔离
好,咱们进入正题。这一章讲的是实时性内核配置的上半部分。说白了,就是让Linux内核从「尽力而为」变成「说到做到」。我当年第一次调EtherCAT主站时,没做任何内核优化,结果周期抖动直接飙到几百微秒——那叫一个惨。后来老老实实把PREEMPT_RT打开,抖动立马降到个位数。
嗯,咱们一个一个来。
4.1 启用PREEMPT_RT
PREEMPT_RT是什么?它让内核变得「随时可打断」。普通内核里,一个任务如果进了内核态,它可能霸占CPU很久。PREEMPT_RT把这个时间压缩到极致。
我个人习惯,在编译内核时直接选上:
Kernel Features --->
Preemption Model (Fully Preemptible Kernel (Real-Time)) --->
(X) Fully Preemptible Kernel (Real-Time)
选完之后,你可以在启动日志里确认:
dmesg | grep -i "preempt"
如果看到类似 Preemptible kernel (with RT) 的字样,那就对了。
重要:PREEMPT_RT不是万能的。它主要解决的是内核态抢占延迟,不是用户态调度延迟。如果你用户态调度乱成一锅粥,光靠PREEMPT_RT救不了你。
我在项目中遇到过一件事:有个同事开了PREEMPT_RT,但周期抖动还是很大。查了半天,发现是某个驱动里用了自旋锁,锁的持有时间太长。PREEMPT_RT能打断大部分内核路径,但自旋锁区域它动不了。所以,开了RT之后,还得检查驱动代码。
小技巧:用 cyclictest 测试实时性。跑个几分钟,看最大延迟。如果超过100微秒,说明还有优化空间。
4.2 配置HZ频率:1000Hz vs 100Hz
HZ频率,说白了就是内核时钟中断的频率。100Hz意味着每10ms一次中断,1000Hz就是每1ms一次。
你想想看,EtherCAT的周期通常是1ms甚至更短。如果内核时钟中断是100Hz,那调度器只能每10ms检查一次。这中间的空档,实时任务可能被耽误很久。
我个人建议,EtherCAT场景直接上1000Hz:
Kernel Features --->
Timer frequency (1000 Hz) --->
(X) 1000 Hz
为什么不用100Hz?我举个例子。假设你的EtherCAT周期是1ms,100Hz的时钟中断意味着调度器每10ms才「醒」一次。这10ms里,你的实时任务可能被普通任务堵住,完全没法响应。1000Hz下,调度器每1ms醒一次,实时任务最多等1ms就能被调度。
| 配置 | 时钟中断间隔 | 适用场景 |
|---|---|---|
| 100 Hz | 10 ms | 桌面、服务器 |
| 250 Hz | 4 ms | 通用嵌入式 |
| 1000 Hz | 1 ms | EtherCAT、工业实时 |
注意:1000Hz会带来额外的CPU开销。因为时钟中断处理程序每1ms跑一次,CPU占用会高一点。但在现代多核处理器上,这点开销可以忽略不计。别为了省这点性能,牺牲了实时性。
我曾经在一个项目里试过250Hz,想着省点CPU。结果EtherCAT周期抖动直接超了200微秒。改回1000Hz后,抖动降到20微秒以内。嗯,从此我再也不纠结这个了。
4.3 配置CPU隔离(isolcpus)
CPU隔离,就是把某些CPU核心从内核的通用调度器中「摘」出来。这些核心只运行你指定的任务,不会被其他乱七八糟的进程打扰。
配置方法很简单,在启动参数里加:
isolcpus=1,2
这表示CPU1和CPU2被隔离。内核不会把普通进程调度到这两个核心上。你可以手动把EtherCAT主站进程绑定到隔离核心上:
taskset -c 1 ./ethercat_master
为什么需要隔离?你想想看,如果EtherCAT主站和桌面环境、日志服务、网络栈抢同一个核心,那实时性肯定完蛋。隔离之后,这些核心只为你服务。
关键点:隔离不是万能的。中断处理程序仍然可能跑到隔离核心上。你需要配合 irqbalance 或者手动设置中断亲和性,把网卡中断也绑到非隔离核心上。
我习惯的做法是:
- 用
isolcpus=2,3隔离两个核心 - 把EtherCAT主站绑到CPU2
- 把网卡中断绑到CPU0或CPU1
- 用
htop确认没有其他进程跑到CPU2上
我曾经踩过一个坑:隔离了核心,但忘了处理中断。结果网卡中断频繁打到隔离核心上,EtherCAT周期抖动反而更大了。后来把中断亲和性设好,问题才解决。
提示:可以用 cat /proc/irq/xxx/smp_affinity 查看中断亲和性。把网卡中断的亲和性设成非隔离核心的CPU掩码。
小结
这一章咱们聊了三件事:
- PREEMPT_RT让内核变得可抢占,减少内核态延迟
- 1000Hz时钟中断让调度器更灵敏,适合1ms周期的EtherCAT
- CPU隔离把专用核心留给实时任务,避免干扰
这三步做完,你的EtherCAT实时性基本就有了保障。下一章咱们继续聊剩下的配置——中断线程化、RCU、cgroup等等。嗯,到时候见。