第四章 内核配置调优:抢占模型、HZ与tickless、RCU机制、cgroup与CPU隔离
各位做座舱的朋友,咱们今天聊点硬核的。内核配置调优,说白了就是给系统「定规矩」——什么时候该干活,什么时候该休息,谁先谁后,谁跟谁不能抢资源。我这些年调过的MTK平台,十有八九的性能问题,最后都能追溯到内核配置上。
嗯,咱们一个一个来。
4.1 内核抢占模型选择
先问个问题:你的座舱里,一个高优先级的触控响应,能不能打断正在进行的后台导航渲染?
能,还是不能?这取决于抢占模型。
Linux内核有三种抢占模型:
- CONFIG_PREEMPT_NONE:服务器模式,几乎不抢占。适合批处理场景。
- CONFIG_PREEMPT_VOLUNTARY:桌面模式,在显式调度点可抢占。适合普通交互。
- CONFIG_PREEMPT:低延迟模式,内核态大部分代码可抢占。适合实时场景。
我个人习惯,座舱项目直接上 CONFIG_PREEMPT。为什么?
你想想看,座舱里同时跑着仪表、导航、语音、空调控制。如果触控响应被一个后台的I/O操作卡住200ms,用户会直接骂娘。我在一个项目中遇到过,仪表盘刷新偶尔卡顿,查了半天,发现是内核抢占模型配成了 CONFIG_PREEMPT_NONE。一个低优先级的日志写入任务,把高优先级的显示刷新给堵了。换成 CONFIG_PREEMPT 后,问题直接消失。
核心建议:座舱项目,除非你有极其特殊的批处理需求,否则直接选 CONFIG_PREEMPT。这是最低成本的实时性提升手段。
小提示:选 CONFIG_PREEMPT 后,注意检查是否有驱动在临界区里做了长时间的自旋锁持有。我曾经见过一个WiFi驱动,在自旋锁里做了ms级的等待,直接把抢占效果废了。
4.2 HZ值与tickless模式
接下来聊时钟。HZ值,就是内核每秒产生多少次时钟中断。默认一般是100或250。
HZ值越高,调度精度越高,但中断开销也越大。HZ值越低,系统越省电,但调度响应会变粗糙。
座舱场景怎么选?
| HZ值 | 适用场景 | 优缺点 |
|---|---|---|
| 100 | 服务器、省电场景 | 省电,但调度粒度10ms,太粗 |
| 250 | 桌面、通用场景 | 折中,调度粒度4ms |
| 1000 | 实时、低延迟场景 | 调度粒度1ms,但中断开销大 |
我个人建议座舱用 HZ=1000。别怕那点中断开销。MTK8678的CPU性能足够,1ms的调度粒度对触控、语音、动画同步来说,是刚需。
但光有高HZ还不够。你得配合 tickless 模式。
tickless,也叫动态时钟。它的意思是:当CPU空闲时,不再每秒产生1000次时钟中断,而是等到下一个真正需要调度的事件到来时才产生中断。
说白了,就是「忙时高精度,闲时低功耗」。我建议开启 CONFIG_NO_HZ_FULL,把tickless扩展到所有CPU。这样,你的座舱在播放音乐待机时,功耗能降不少。
注意:开启 CONFIG_NO_HZ_FULL 后,需要为每个CPU指定一个 nohz_full= 内核参数。比如 nohz_full=1-7,把CPU 1到7都设为tickless。CPU 0保留给内核管理任务。
4.3 RCU机制调优
RCU,Read-Copy-Update。这是Linux内核里一种非常优雅的同步机制。读操作几乎无锁,写操作通过延迟回收来保证一致性。
但RCU有个问题:它依赖时钟中断来触发回调。如果你的tickless配置不当,RCU回调可能会被延迟,导致内存无法及时回收。
我在项目中遇到过:一个座舱应用长时间运行后,内存越用越多,最后被OOM killer干掉。查了一圈,发现是RCU回调被tickless延迟了,大量待回收的内存块堆积在RCU队列里。
解决方案有两个:
- 方案一:开启
CONFIG_RCU_EXPERT,然后调整CONFIG_RCU_FANOUT和CONFIG_RCU_FANOUT_LEAF。对于MTK8678这种8核平台,我建议RCU_FANOUT=64,RCU_FANOUT_LEAF=16。这样能加速RCU回调的传播。 - 方案二:为RCU保留一个专用的CPU核,不让它进入tickless。通过
rcu_nocbs=内核参数指定。比如rcu_nocbs=1-7,把RCU回调绑定到CPU 0上。
经验之谈:我一般两个方案一起用。先调RCU参数,再配合 rcu_nocbs 隔离。这样既保证了RCU的及时性,又不会影响其他CPU的tickless省电效果。
4.4 cgroup与CPU隔离
最后,咱们聊聊资源隔离。座舱里跑着多个子系统——仪表、导航、语音、空调。它们之间不能互相干扰。
cgroup v2 是Linux内核提供的资源管控机制。我建议座舱项目直接上cgroup v2,别用v1了。v2的层级结构更清晰,而且支持CPU、内存、I/O的统一管理。
具体怎么做?
第一步,创建cgroup层级:
mkdir -p /sys/fs/cgroup/ivic
mkdir -p /sys/fs/cgroup/ivic/cluster
mkdir -p /sys/fs/cgroup/ivic/navi
mkdir -p /sys/fs/cgroup/ivic/voice
第二步,分配CPU份额:
echo 512 > /sys/fs/cgroup/ivic/cluster/cpu.weight
echo 256 > /sys/fs/cgroup/ivic/navi/cpu.weight
echo 128 > /sys/fs/cgroup/ivic/voice/cpu.weight
第三步,把进程移进去:
echo 1234 > /sys/fs/cgroup/ivic/cluster/cgroup.procs
但光有cgroup还不够。对于仪表这种硬实时任务,你需要 CPU隔离。
CPU隔离,就是把某些CPU核从内核的通用调度器中剥离出来,专门给特定任务使用。通过 isolcpus 内核参数实现。
比如:
isolcpus=1,2,3
这样CPU 1、2、3就不会被普通进程使用了。然后你可以通过 taskset 或cgroup的 cpuset 控制器,把仪表进程绑定到这些隔离核上。
避坑指南:我曾经把 isolcpus 配了,但忘了把内核线程也迁走。结果一个ksoftirqd线程在隔离核上跑,把仪表任务的实时性全毁了。记得用 cpuset 把内核线程也限制一下。
嗯,到这里,内核配置调优的核心内容就差不多了。总结一下:
- 抢占模型选
CONFIG_PREEMPT - HZ值用1000,配合tickless
- RCU参数调优,避免内存回收延迟
- cgroup v2做资源管控,isolcpus做CPU隔离
下一章,咱们聊中断线程化和优先级管理。那也是个容易踩坑的地方。