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

相关推荐
拂拉氏1 小时前
【知识讲解】 Linux文件系统底层认识
linux·文件系统
IT大白鼠1 小时前
彭大帅的AI运维助手——自然语言管理 Linux 集群与网络设备——第 1 篇 · 骨架:说人话,跑命令:自然语言 SSH 运维的骨架
linux·运维·人工智能
牢姐与蒯1 小时前
Linux信号(一).信号产生
linux·运维·服务器·ubuntu
雯宝7 小时前
mnt_init 函数
linux
律宏阔13 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔13 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴13 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千14 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei3714 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化