Linux stop_machine 停机机制与 OOM Killer 并发场景下的 soft lockup 诊断
1. Linux stop_machine机制与OOM Killer的死亡之舞
第一次在Kubernetes节点日志里看到"soft lockup"报错时,我正喝着咖啡调试一个内存泄漏问题。屏幕上突然刷出的红色警告让我差点把咖啡喷出来——几十个CPU核心的迁移线程同时卡死,而OOM Killer正在疯狂收割进程。这种高负载容器环境下stop_machine与OOM Killer的并发场景,就像两个醉汉在内存危机的独木桥上狭路相逢。
stop_machine本质上是个"世界暂停"按钮。当内核需要执行热补丁、CPU热插拔或NUMA平衡等操作时,它会冻结所有CPU的执行流,确保没有代码会干扰关键操作。想象下教室里的场景:老师(stop_machine)喊"全体安静"时,所有学生(CPU)必须停止说话(执行代码),直到老师说完指令才能继续活动。
而OOM Killer则是内存压力下的清道夫。当cgroup内存超限时,它会选择性地终止进程来释放内存。在容器化环境中,我们经常看到这样的日志:
Memory cgroup out of memory: Killed process 28155 (mysqld)2. 问题复现与诊断方法论
2.1 典型症状识别
在内存紧张的K8s节点上,你会同时看到两种症状:
- 频繁的OOM Kill事件
- 多CPU核心的soft lockup告警
# OOM日志示例 May 16 15:19:25 node-2 kernel: Memory cgroup out of memory # soft lockup日志示例 May 16 15:20:02 node-2 kernel: watchdog: BUG: soft lockup - CPU#85 stuck for 22s! [migration/85:537]2.2 诊断工具包
我常用的诊断组合拳:
# 1. 检查memory cgroup配置 cat /sys/fs/cgroup/memory/kubepods/memory.limit_in_bytes # 2. 监控内存压力 watch -n 1 'cat /proc/meminfo | grep -E "MemFree|Cached"' # 3. 追踪stop_machine调用 perf probe --add stop_machine perf stat -e probe:stop_machine -a sleep 103. 深度技术原理剖析
3.1 stop_machine的状态机困境
stop_machine的核心是个五状态机:
enum multi_stop_state { MULTI_STOP_NONE, MULTI_STOP_PREPARE, // 准备阶段 MULTI_STOP_DISABLE_IRQ, // 禁用中断 MULTI_STOP_RUN, // 执行回调 MULTI_STOP_EXIT // 退出 };问题出在状态转换的同步机制上。所有CPU必须通过这个原子计数器同步:
static void ack_state(struct multi_stop_data *msdata) { if (atomic_dec_and_test(&msdata->thread_ack)) set_state(msdata, msdata->state + 1); }3.2 OOM Killer的致命遍历
当OOM Killer开始工作时,它会遍历memcg的所有进程进行评估。关键路径如下:
oom_kill_process -> dump_header -> mem_cgroup_print_oom_info -> dump_tasks这个过程中会持有RCU读锁,导致迁移线程在rcu_momentary_dyntick_idle()中卡住。就像交通堵塞时,救护车(OOM Killer)被堵在了清理现场的路上。
3.3 Watchdog机制失效点
Linux的soft lockup检测依赖两个关键变量:
watchdog_touch_ts:最后活跃时间戳hrtimer_interrupts:定时器中断计数
正常流程中,迁移线程应该定期"抚摸"看门狗:
static void touch_nmi_watchdog(void) { arch_touch_nmi_watchdog(); touch_softlockup_watchdog(); // 重置时间戳 }但在我们的场景中,这个抚摸动作被延迟了,因为:
- OOM Killer持有锁导致迁移线程无法运行
- stop_machine只在特定状态(
curstate > MULTI_STOP_PREPARE)才调用touch操作
4. 解决方案与实战建议
4.1 临时缓解措施
对于线上紧急情况,可以:
# 1. 调整watchdog阈值(单位:秒) echo 30 > /proc/sys/kernel/watchdog_thresh # 2. 限制memcg的OOM killer激进程度 echo 1 > /sys/fs/cgroup/memory/memory.oom_control4.2 内核参数优化
在/etc/sysctl.conf中添加:
# 降低NUMA平衡频率 kernel.numa_balancing = 0 # 调整OOM killer行为 vm.panic_on_oom = 0 vm.oom_kill_allocating_task = 14.3 代码级修复方案
最根本的解决是修改stop_machine的状态机逻辑。在我的测试环境中,这个补丁有效:
// 修改前 else if (curstate > MULTI_STOP_PREPARE) { touch_nmi_watchdog(); } // 修改后 else if (curstate >= MULTI_STOP_PREPARE) { touch_nmi_watchdog(); }5. 预防体系构建
5.1 监控指标设计
建议监控这些关键指标:
- memory cgroup使用率
- stop_machine调用频率
- soft lockup发生次数
Prometheus配置示例:
- name: kernel_softlockup rules: - alert: SoftlockupDetected expr: increase(node_softlockups_total[5m]) > 0 labels: severity: critical5.2 压力测试方案
使用这个简易测试脚本模拟内存压力:
#!/bin/bash # 创建内存压力 stress-ng --vm 4 --vm-bytes 80% --vm-method all -t 60s & # 模拟stop_machine调用 while true; do echo 1 > /proc/sys/kernel/numa_balancing sleep 1 done6. 总结与经验分享
排查这类问题就像在解一个多维度的拼图。我花了三天时间才理清stop_machine、OOM Killer和watchdog三者之间的微妙关系。关键突破点是发现OOM Killer的dump_header路径会长时间持有RCU锁,而stop_machine的迁移线程正好依赖RCU。
对于运维高负载K8s集群的建议:
- 给系统预留足够的内存余量
- 避免在内存紧张的节点执行NUMA平衡操作
- 定期检查memory cgroup的配置合理性
记得那次凌晨三点,当我终于看到测试环境不再报soft lockup时,那种成就感比喝十杯咖啡都提神。这就是系统级调试的魅力——你永远在和操作系统最精妙的设计博弈。
