ThinkPad E490 风扇 “转两秒就停“ 实录:迟滞曲线 + 最短运转时间

症状:风扇 "启动转两秒就停",循环反复

这台 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 温度被周期性任务打成脉冲的机器(定时编译、容器批处理、监控采集),只要风扇由脚本控制,都能照抄这三个常量。

相关推荐
Chenyu_3105 小时前
DPDK、TC 和 XDP:Linux 高性能网络到底在优化什么
linux·服务器·网络·缓存
陈让然6 小时前
WSL 桥接设置静态IP(快速)
linux·网络·ubuntu
乔旭伟6 小时前
Linux 服务器备份实战:一致性快照、日志归档与恢复演练
linux·运维·服务器
乔旭伟7 小时前
Linux 服务器安全加固实战:SSH、防火墙、补丁与入侵排查
linux·服务器·安全
刘胡子大叔8 小时前
备份包只有 1.5K?
linux·服务器·网络
无足鸟ICT8 小时前
【RHCA+】RPM包验证和数字证书
linux·运维·服务器
erzong2318 小时前
Linux操作系统:线程概念与控制
linux·算法
星恒随风8 小时前
Linux线程概念详解:从分页存储、页表、MMU与TLB,深入理解线程和进程的区别与联系
linux·运维·笔记·学习·状态模式
小范的技术工坊8 小时前
Linux应用开发(文件IO)
linux·stm32·单片机
小范的技术工坊8 小时前
Linux应用开发(文件属性)
linux·运维·服务器