症状:风扇 "启动转两秒就停",循环反复
这台 ThinkPad E490(Arch Linux 局域网 TTY 服务器)用自写的曲线脚本接管风扇已经一个多月了(方案见前一篇:thinkfan 缺位,自写曲线脚本接管)。空载安静、负载自动吹,本来挺好。直到某天开始,风扇变成了 "启动转 2 秒就停",每十几秒来一次,哒哒哒,很烦人。
症状描述:
-
风扇突然启动,全速转 2-5 秒
-
然后立刻停转
-
十几秒后再次启动,循环反复
-
机器看起来并没有持续高负载
排查:先抓数据,不要猜
改策略之前先看证据。风扇控制的日志和当前状态都可以直接读:
journalctl -t thinkpad-fan -n 30 --no-pager
cat /proc/acpi/ibm/fan
日志立刻暴露了问题:
12:21:52 level 7 (max 79000 mC)
12:21:57 level 0 (max 48000 mC)
12:22:12 level 7 (max 79000 mC)
12:22:17 level 0 (max 47000 mC)
12:22:32 level 3 (max 69000 mC)
12:22:37 level 0 (max 48000 mC)
温度在 47-49°C 和 73-79°C 之间每 15 秒左右大幅摆动一次,档位跟着疯狂切换。真相是:这台 TTY 服务器上有周期性脉冲负载(容器里的定时任务之类),每次爆发把温度瞬间顶到 79°C,5 秒内又回落到 48°C。
而脚本的行为是:温度一低于 55°C 就写 level 0 停转。于是负载一走,风扇刚转起来就被掐停;下一波负载又来,又启动。风扇的切换频率 = 负载的脉冲频率。
为什么单纯调阈值没用
第一反应是调阈值:把停转线从 55°C 调到 50°C 甚至 45°C。但仔细想就知道没用 ------ 问题不在阈值高低,在于温度摆幅巨大(47↔79°C),任何一条阈值线都会被来回穿越:
-
阈值调低 → 负载间隙照样跌破停转线,风扇照旧频繁启停
-
阈值调高 → 轻负载不启动风扇了,但重脉冲一来照样转 2 秒就停
负载脉冲多快,纯温度阈值就切换多快。风扇控制缺的不是更合适的温度点,而是时间维度。
方案:两道稳定机制
给曲线加两道机制,全部收在脚本顶层的常量里:
-
最短运转时间 MIN_RUN=180:风扇一旦响应负载转起来,至少连续转满 3 分钟才允许降档或停转。脉冲间隔 15 秒远小于 180 秒,所以只要负载还在持续爆发,风扇就一直运转;负载真正平息后,也先把余热吹透再停
-
降档迟滞 DOWN_GAP=8000:降档需要温度比对应升档阈值低 8°C 才动作。比如 7 档要等到温度跌破 67°C 才允许退到 4 档,防止在临界点来回横跳
-
停转线 STOP_TEMP=52000:档 1 回档 0 要求温度低于 52°C,而升档是 55°C,天然留了 3°C 迟滞 ------ 只有真空闲才彻底停转
升档曲线完全不动:55/60/65/70/75°C → 档 1/2/3/4/7。过热优先原则不变:负载一来温度穿过阈值,立刻按原曲线升档。
实现:完整脚本
/usr/local/sbin/thinkpad-fan.sh 全部内容(可直接抄):
#!/bin/bash
# ThinkPad E490 fan curve controller
# v2: hysteresis + min-run-time (kills the 2s on/off cycling)
# Requires thinkpad_acpi fan_control=1 (baked into initramfs since 2026-08-03)
#
# Strategy:
# - UP curve (unchanged): 55/60/65/70/75 mC -> level 1/2/3/4/7
# - MIN_RUN: once the fan spins up, it stays at least 180s before any down-step
# - HYSTERESIS: down-step needs temp 8C below the up threshold
# - STOP: level 1 -> 0 only below 52C (vs 55C spin-up)
FAN=/proc/acpi/ibm/fan
TEMP_GLOB=/sys/devices/platform/coretemp.0/hwmon/hwmon*/temp*_input
SLEEP=5
MIN_RUN=180 # seconds: fan keeps running at least this long after spin-up
DOWN_GAP=8000 # hysteresis: down-step requires temp 8C below the up threshold
STOP_TEMP=52000 # level 1 -> 0 below this (52C), spin-up was at 55C
level_up() {
if [ "$1" -ge 75000 ]; then echo 7
elif [ "$1" -ge 70000 ]; then echo 4
elif [ "$1" -ge 65000 ]; then echo 3
elif [ "$1" -ge 60000 ]; then echo 2
elif [ "$1" -ge 55000 ]; then echo 1
else echo 0
fi
}
level_down() {
if [ "$1" -ge 67000 ]; then echo 4
elif [ "$1" -ge 62000 ]; then echo 3
elif [ "$1" -ge 57000 ]; then echo 2
elif [ "$1" -ge "$STOP_TEMP" ]; then echo 1
else echo 0
fi
}
last_level=""
level_since=0
self_healed=0
while true; do
max=0
for t in $TEMP_GLOB; do
v=$(cat "$t" 2>/dev/null) || continue
[ -n "$v" ] && [ "$v" -gt "$max" ] && max=$v
done
if [ "$max" -eq 0 ]; then
sleep "$SLEEP"
continue
fi
now=$(date +%s)
[ -z "$last_level" ] && last_level=0 && level_since=$now
if [ "$last_level" -eq 0 ]; then
level=$(level_up "$max")
else
up=$(level_up "$max")
if [ "$up" -gt "$last_level" ]; then
level=$up
elif [ "$now" -ge $((level_since + MIN_RUN)) ]; then
level=$(level_down "$max")
else
level=$last_level
fi
fi
if [ "$level" != "$last_level" ]; then
if echo "level $level" > "$FAN" 2>/dev/null; then
last_level="$level"
level_since=$now
logger -t thinkpad-fan "level $level (max ${max} mC)"
elif [ "$self_healed" -eq 0 ]; then
# fan_control not enabled: try reloading the module once
modprobe -r thinkpad_acpi 2>/dev/null
modprobe thinkpad_acpi fan_control=1 2>/dev/null
self_healed=1
continue
fi
fi
sleep "$SLEEP"
done
关键逻辑只有一段:升档永远立即执行,降档必须同时满足 "过了最短运转时间" 和 "温度跌破迟滞线"。档位为 0 时没有 "保持" 可言,直接用升档曲线决定。
重启服务生效:
sudo systemctl restart thinkpad-fan
journalctl -t thinkpad-fan -f
实测:一个周期从 5 次启停变成 1 次
改完立刻观察档位时间线。旧版(12:24:48-12:25:33,60 秒内):
level 7 -> level 0 -> level 4 -> level 0 -> level 4 -> level 0
5-15 秒一次启停,风扇反复 "全速转 2 秒 - 停 - 全速转 2 秒"。
新版(12:25:48 服务重启后):
12:25:48 level 3 (max 65000 mC)
12:26:33 level 4 (max 72000 mC)
12:26:53 level 7 (max 78000 mC)
12:29:54 level 0 (max 40000 mC)
负载爆发照样立刻响应(3 → 4 → 7 全速),但 12:26:53 升到 7 档后连续运转了 181 秒------ 期间温度多次回落到 40-44°C,档位纹丝不动。直到最短运转时间结束、温度稳定在 40°C(低于 52°C 停转线),才干净地停转。整个周期只有一次启停。
参数怎么调
三个常量就是全部的旋钮:
-
MIN_RUN:觉得启停还是偏频繁就加大到 240/300 秒;嫌风扇转太久就减小。原则是大于负载脉冲间隔
-
DOWN_GAP:迟滞带宽度,8°C 适合大幅摆动的负载;温度波动小的机器可以收窄
-
STOP_TEMP:停转线。想更爱停就抬高(注意别抬进空闲温度区间,否则又抖动);想更稳就压低,代价是风扇会保持低速档更久
改完:
sudo systemctl restart thinkpad-fan
小结
一句话概括这次的修法:阈值管 "什么时候转",时间管 "转多久",迟滞管 "别在临界点来回横跳"。
-
纯温度阈值解决不了脉冲式负载下的频繁启停 ------ 负载摆幅远大于阈值迟滞时,曲线形同虚设
-
最短运转时间是治 "转 2 秒就停" 的主力:把风扇切换频率从 "负载脉冲频率" 拉低到 "负载平息频率"
-
升档保持即时、降档才讲条件,过热永远优先
这套思路不限于 ThinkPad:任何 CPU 温度被周期性任务打成脉冲的机器(定时编译、容器批处理、监控采集),只要风扇由脚本控制,都能照抄这三个常量。