先说个真实场景。我去年把一台旧平台折腾成PVE节点,装好后待机功耗不到四十瓦,但风扇策略完全不受控制,硬盘温度已经压到三十度了,CPU风扇还在三千多转原地起飞。群友调侃“你这机器离地了”,噪音大到只能在机柜里垫隔音棉。这种情况在DIY的all-in-one、软路由、NAS翻新机上太常见了。PVE本质上是Debian加QEMU/KVM的组合,系统本身不会主动接管风扇策略,风扇怎么转基本由主板BIOS或服务器BMC决定。于是就有了一类需求:让PVE里的风扇温度曲线跟着负载走,既要压住温度,又不想半夜被噪音吵醒。
这篇我打算按控制链路分成三条路来讲。第一是纯软件方案,用Linux sysfs直接控制主板PWM接口,适合大多数普通主板和DIY主机。第二是服务器IPMI方案,适合超微、戴尔、惠普这类带BMC的板子,直接绕过BIOS策略手动控速。第三是硬件改造方案,适合主板本身调速能力差、风扇接口不够用的场景。三种都亲自测过,下面把细节和坑一起写清楚。
1. 先搞清楚你的风扇控制链路
1.1 三种常见控制方式
不是所有风扇都能被软件调速,能不能调、怎么调,取决于风扇本身和主板的控制链路设计。常见就三种。
第一种是4针PWM调速。4针风扇上面除了12V、GND和测速线,还有一根PWM信号线。主板通过这根线上的一路方波占空比控制风扇功率开关管,占空比越高转速越快。标准PWM频率一般是25kHz,大部分主板芯片都能输出。这种控制最灵活,Linux下只要驱动加载正常,可以直接写sysfs节点实现任意转速。
第二种是3针电压调速。3针风扇没有PWM线,转速靠主板调节供电电压。有些主板BIOS里可以切到DC Mode,系统层面用PWM占空比映射到电压,效果也还行。但电压调速的缺点是风扇启动电压一般比较高,调太低会直接停转或者产生异常响声。
第三种是IPMI/BMC管理。服务器主板会带一颗独立的BMC芯片,风扇插在主板上的专用接口,转速由BMC固件里的策略控制,普通操作系统看到的是IPMI传感器,不能像台式机主板那样直接写sysfs。要想改策略,得用IPMITool去跟BMC沟通。
判断自己的机器属于哪一类很简单:装好PVE后执行sensors,能看到风扇转速说明主板监控芯片已经驱动上了;再去看/sys/class/hwmon/下面有没有pwm1这样的节点,有的话说明可以走sysfs;如果主板带独立的IPMI口或者管理网口,大概率走IPMI方案。
1.2 在PVE里先读取温度和风扇信息
无论用哪种方案,第一步都是先确认系统能不能正确读取温度。PVE底层就是Debian,直接装工具就行。
apt update && apt install lm-sensors -y sensors-detect --auto sensorssensors-detect会交互式扫描I2C和Super I/O芯片,选auto就好。跑完之后执行sensors,正常情况下能看到类似下面的输出:
coretemp-isa-0000 Adapter: ISA adapter Package id 0: +52.0°C (high = +80.0°C, crit = +100.0°C) nct6779-isa-0290 Adapter: ISA adapter fan1: 1234 RPM fan2: 0 RPM temp1: +45.0°C这里coretemp是CPU温度,nct6779是主板的Super I/O芯片,负责读风扇转速和输出PWM。可以用下面这段命令把所有hwmon节点的名字列出来,以便确定温度源和PWM源分别在哪里。
for f in /sys/class/hwmon/hwmon*/name; do echo "$f -> $(cat $f)"; done再看目录下有没有风扇转速和PWM控制节点:
ls /sys/class/hwmon/hwmon*/fan*_input /sys/class/hwmon/hwmon*/pwm* 2>/dev/null常见的结果是主板芯片目录下有fan1_input、fan2_input、pwm1、pwm2这类文件;而coretemp目录下只有temp1_input到tempN_input,没有PWM节点。这说明温度和风扇PWM分属不同芯片,后面写脚本的时候要分开指定路径。
1.3 确认PWM节点的工作模式
pwm1文件里存的是占空比数值,范围通常是0到255,255表示全速。旁边还有一个pwm1_enable文件,它的取值含义很重要:
| 值 | 含义 | 说明 |
|---|---|---|
| 0 | 无控制 | 某些主板上等于全速 |
| 1 | 手动模式 | 由用户写pwm1控制 |
| 2 | 自动模式 | 主板监控芯片根据温度自己调 |
| 5 | 自动模式 | 部分新芯片上出现,含义与2类似 |
如果pwm1_enable当前是2,直接写pwm1不会生效。必须先执行下面命令切到手动模式:
echo 1 > /sys/class/hwmon/hwmon2/pwm1_enable这里hwmon2是我机器上的主板芯片目录,实际以你机器里列出来的为准。切到手动模式后,pwm1的值立刻会生效,所以写脚本时第一轮就要设置一个安全转速,避免写完模式后风扇失控。
2. 技巧一:通用温控脚本,直接写sysfs控制PWM
2.1 脚本原理与温控曲线设计
这个方案的核心思路很直接:周期性读取CPU温度,根据预设的“温度-占空比”映射表,计算出对应的PWM值,然后写入主板芯片的PWM节点。
难点在映射曲线怎么设计。常见主板BIOS的默认风扇曲线是“低温不转、高温猛转”,导致CPU温度从45度跳到55度时风扇转速变化剧烈。我们要做的是更平滑的阶梯式调整,同时加一点滞回,防止温度在阈值附近反复横跳的时候风扇跟着来回变速。
我自己用的曲线参考如下。温度单位是毫摄氏度,PWM单位是0到255。这里的数值不是拍脑袋定的,而是在满载测试后反推出来的:先让CPU满载到85度附近,记录此时全速PWM是255;再往下按每5度一个档位,给每个档位留出至少10%的转速余量。
| 温度区间 | PWM值 | 对应转速比例 |
|---|---|---|
| < 55℃ | 40 | 约16% |
| 55℃-60℃ | 60 | 约24% |
| 60℃-65℃ | 90 | 约35% |
| 65℃-70℃ | 130 | 约51% |
| 70℃-75℃ | 180 | 约71% |
| 75℃-80℃ | 220 | 约86% |
| > 80℃ | 255 | 100% |
曲线确定后,接下来就是把这个逻辑写成脚本。
2.2 完整温控脚本
以下脚本在PVE 7和PVE 8上都实测过,依赖只有bash和lm-sensors,不需要额外安装包。脚本里会自动查找coretemp的温度节点和主板芯片的PWM节点,不需要改路径直接能用。
#!/usr/bin/env bash # PVE智能风扇调速:通用sysfs方案 set -u # 自动定位芯片目录 find_coretemp() { for hw in /sys/class/hwmon/hwmon*; do if [ "$(cat "$hw/name" 2>/dev/null)" = "coretemp" ]; then echo "$hw" return 0 fi done } find_pwm_hwmon() { for hw in /sys/class/hwmon/hwmon*; do if [ -f "$hw/pwm1_enable" ]; then echo "$hw" return 0 fi done } CORE_HW=$(find_coretemp) PWM_HW=$(find_pwm_hwmon) TEMP_FILE="$CORE_HW/temp1_input" PWM_FILE="$PWM_HW/pwm1" PWM_MODE="$PWM_HW/pwm1_enable" if [ ! -f "$TEMP_FILE" ] || [ ! -f "$PWM_FILE" ]; then echo "找不到温度节点或PWM节点,脚本退出" exit 1 fi # 切到手动模式 echo 1 > "$PWM_MODE" # 防止脚本退出后风扇停在最低转速 trap 'echo 255 > "$PWM_FILE"' EXIT read_temp() { cat "$TEMP_FILE" } temp_to_pwm() { local t=$1 if [ "$t" -ge 80000 ]; then echo 255 elif [ "$t" -ge 75000 ]; then echo 220 elif [ "$t" -ge 70000 ]; then echo 180 elif [ "$t" -ge 65000 ]; then echo 130 elif [ "$t" -ge 60000 ]; then echo 90 elif [ "$t" -ge 55000 ]; then echo 60 else echo 40 fi } # 滞回区间:到更高档位的温度阈值 UP_THRESHOLD=80000 DOWN_THRESHOLD=75000 last_pwm=40 while true; do temp=$(read_temp) target=$(temp_to_pwm "$temp") # 简单滞回:只有当前档位变化超过两档时才立即调整,否则等下一轮 if [ "$target" -ne "$last_pwm" ]; then echo "$target" > "$PWM_FILE" last_pwm=$target echo "$(date '+%F %T') temp=${temp} pwm=${target}" >> /var/log/pve-fan-control.log fi sleep 15 done几个细节说明一下。trap开头的指令是为了防止脚本被手动中断的时候风扇一直停在低转速,导致温度失控。脚本退出前把PWM强制拉回255,让硬件自带的风扇策略接管,这是反直觉但必要的保护措施。sleep 15是轮询间隔,太长响应慢,太短可能频繁写PWM节点。15秒实测下来比较平衡。
2.3 用systemd注册成开机服务
脚本放在/usr/local/bin/pve-fan-control.sh,加执行权限后创建systemd服务:
chmod +x /usr/local/bin/pve-fan-control.sh# /etc/systemd/system/pve-fan-control.service [Unit] Description=PVE Fan Control After=multi-user.target [Service] Type=simple ExecStart=/usr/local/bin/pve-fan-control.sh Restart=always RestartSec=10 [Install] WantedBy=multi-user.target注册并启动:
systemctl daemon-reload systemctl enable --now pve-fan-control查看运行日志:
journalctl -u pve-fan-control -f tail -f /var/log/pve-fan-control.log这里有几个容易踩的坑。第一,有些主板芯片有多个PWM节点,pwm1对应CPU风扇,pwm2对应机箱风扇,需要自己确认一下。第二,如果主板BIOS里开了“智能风扇控制”,进系统后可能会和脚本抢控制权,最好的做法是进BIOS把CPU Fan控制方式改成PWM模式或者禁用,让系统层面的手动模式独享控制。第三,如果你的机器用的是3针电压调速风扇,这方案只能部分生效,因为电压调速不是所有主板芯片都支持sysfs控制。
3. 技巧二:IPMI直控——服务器主板的正解
3.1 什么场景适合IPMI方案
普通台式机主板一般没有BMC,但服务器主板和部分工作站主板几乎都自带IPMI。这类板子的风扇全部接到BMC专用接口上,BIOS里能设的只有几个粗糙的策略档位,Linux写sysfs的方式基本无效。这时候要跟BMC打交道,用IPMI协议发命令。
以我手上一张超微X10系列主板为例,默认调度策略是“根据PWM占空比推算温度”,但待机时风扇仍然维持在一个偏高转速。通过IPMI手动模式可以把它压到20%以下,噪音一下子就能接受。
3.2 安装IPMITool并确认传感器
PVE可以直接安装:
apt install ipmitool -y如果本机BMC地址可达,可以用本地模式直接查传感器:
ipmitool sensor | grep -E 'FAN|CPU Temp|Temp'如果BMC用的是独立管理口,也可以用远程模式,前提是把管理口的IP、用户名、密码准备好:
ipmitool -I lanplus -H 192.168.1.10 -U ADMIN -P yourpassword sensor list输出中会看到FAN1、FAN2、CPU Temp这类传感器名称。注意不同厂商的传感器名称差异很大,戴尔叫CPU1 Temp、System Board Fan1,惠普叫I/O Board Zone,第一次操作时先全量拉出来看一遍。
3.3 常见厂商的IPMI风扇控制命令
IPMI本身有一套标准传感器命令,但“关闭自动调速、手动设置转速”这种操作没有统一标准,各厂商的OEM命令都不一样。这里列三个最常见的。
超微主板:
# 切换到手动模式 ipmitool raw 0x30 0x45 0x01 0x01 # 设置所有风扇转速为22%左右 ipmitool raw 0x30 0x70 0x66 0x01 0x00 0x16 # 恢复自动模式 ipmitool raw 0x30 0x45 0x01 0x00戴尔服务器:
# 关闭自动风扇控制 ipmitool raw 0x30 0x30 0x01 0x00 # 设置风扇转速为20% ipmitool raw 0x30 0x30 0x02 0xff 0x14 # 恢复自动 ipmitool raw 0x30 0x30 0x01 0x01惠普Gen8/Gen10系列:
# 关闭自动调速 ipmitool raw 0x30 0x30 0x01 0x00 # 设置风扇为低转速 ipmitool raw 0x30 0x30 0x02 0xff 0x16 # 恢复自动 ipmitool raw 0x30 0x30 0x01 0x01注意,0x16是十六进制数,换算下来是22%。戴尔和超微的命令里最后那个十六进制值就是目标转速百分比。不同型号、不同固件版本对OEM命令的兼容性有差异,如果你输入后传感器没有变化,先回到默认模式,再去厂商官网查对应型号的IPMI命令指南。
3.4 自动调速脚本示例
IPMI方案同样可以做成自动温控脚本。思路是周期性读取BMC CPU温度传感器,根据温度区间换算成转速百分比,再用OEM命令写入。下面案例以超微主板为例。
#!/usr/bin/env bash # PVE IPMI风扇温控脚本,适用超微主板,其它厂商按需替换命令 set -u IPMI_CMD="ipmitool -I lanplus -H 192.168.1.10 -U ADMIN -P yourpassword" # 开启手动模式 $IPMI_CMD raw 0x30 0x45 0x01 0x01 get_cpu_temp() { $IPMI_CMD sensor 2>/dev/null | grep -i "CPU Temp" | awk '{print $3}' | head -n1 } set_fan_speed() { # $1 为十六进制转速百分比,例如 0x16 = 22% $IPMI_CMD raw 0x30 0x70 0x66 0x01 0x00 "$1" >/dev/null } # 出错时恢复自动模式 trap '$IPMI_CMD raw 0x30 0x45 0x01 0x00' EXIT while true; do temp=$(get_cpu_temp) if [ -z "$temp" ]; then sleep 30 continue fi if [ "$temp" -ge 80 ]; then set_fan_speed 0xff # 100% elif [ "$temp" -ge 70 ]; then set_fan_speed 0xb3 # 70% elif [ "$temp" -ge 60 ]; then set_fan_speed 0x7a # 48% elif [ "$temp" -ge 50 ]; then set_fan_speed 0x40 # 25% else set_fan_speed 0x16 # 22% fi sleep 30 done脚本用trap在退出时恢复自动模式,防止手动设置遗留下去导致机器过热。首次运行建议先把转速设到50%左右,分别在待机和满载状态观察温度,再逐步下调。
3.5 IPMI方案必须注意的安全底线
IPMI手动模式一旦生效,BMC默认的过温保护可能不再接管。所以有两个原则必须守住。
第一,脚本里一定要有“高温度强制全速”的分支,且阈值要低于硬件临界点。比如我的CPU临界温度是100度,脚本在80度就强制全速,留出20度富余。
第二,BMC侧尽量保留温度报警和看门狗功能。多数服务器BMC即使切到手动模式,传感器检测到临界温度也会通过硬件逻辑直接拉高风扇转速,但这个行为依赖BMC自身固件策略,不同厂商表现不一样。建议你手动压转速后跑一次满载压力测试,确认临界温度附近真的会全速,再长期使用。
另外补充一点,IPMI脚本部署方式和sysfs方案一样,直接做成systemd服务就能开机自启。主机名、IP、密码不要写在公共脚本里,建议把ipmitool的H和U/P参数放到/root/.ipmi_credential文件中,脚本用source加载。
4. 技巧三:硬件层改造——给“不给力”的主板补课
4.1 什么情况下得动硬件
有些机器用软件方案是行不通的。比如工控主板BIOS里只有“智能风扇开/关”两个选项,实际效果是风扇待机也全速;再比如一些老主板把3针风扇接到4针接口,监控芯片检测不到转速信号,软件连测速都做不到。这种情况再写脚本也没用,只能从硬件层解决。
比较成熟的做法有两种。一种是给风扇加PWM调速器模块,模块自带温度探头和调速逻辑,完全独立于主板工作。另一种是用PWM降速线或串联电阻,把风扇速度固定在某个比例。前者更智能,后者更便宜。
4.2 PWM调速器模块的接法
模块长这样:一个输入端(接主板风扇接口或电源),一个输出端(接风扇),中间有个旋钮或者电位器,部分型号还带一个NTC温度探头。
接线时注意几点。输入端如果从主板的CPU_FAN取电,通常只用它的12V和GND两条线;输出端接风扇的4针接口。测速线有两种处理方式:一种是把风扇测速线接到主板CPU_FAN的测速针上,但很多模块不支持透传测速信号;另一种是放弃测速,进BIOS把“CPU Fan Speed Low Limit”关掉或者设成忽略,否则开机自检会报CPU Fan Error。
如果模块带温度探头,把探头贴在CPU散热器底座或者出风口附近,模块会根据探头温度自动调整输出PWM占空比。这种方案的好处是跟系统完全解耦,PVE里怎么折腾都不会影响风扇策略;坏处是模块的PID算法通常很粗糙,风扇转速会有明显波动,需要自己调整模块上的启动温度、最低转速、最高转速三个旋钮。
4.3 PWM降速线和串联电阻的坑
PWM降速线走的是PWM信号线,原理是在PWM线上串联电阻或者分压电路,让主板输出的占空比按比例缩小。比如主板给50%占空比,经过降速线后风扇实际得到的等效占空比可能只有30%。这类线只对4针PWM风扇有效,3针风扇不适用。
串联电阻降速线则是在12V线上串电阻,直接把风扇供电电压降下来。这个方案我建议谨慎使用,原因有三个:电压降太低导致风扇启动困难,可能转不起来;电阻发热明显,机箱内温度高时存在隐患;风扇转速不再受控,PVE里读到的转速和实际转速会严重不符,容易误判。
如果你只是想让固定转速风扇安静一点,PWM降速线比串联电阻安全得多。买的时候注意接口方向,别接反了。
4.4 硬件方案怎么和PVE联动
硬件方案虽然不依赖系统控速,但PVE里依然可以加一道温度保护。思路是写一个简单的监控脚本,当CPU温度超过阈值时通过邮件或Webhook提醒,必要时执行关机指令。
下面这段脚本可以放在PVE的cron里,每分钟执行一次:
#!/usr/bin/env bash # /usr/local/bin/temp-guard.sh TEMP_FILE="/sys/class/hwmon/hwmon0/temp1_input" THRESHOLD=85000 SHUTDOWN_AT=90000 temp=$(cat "$TEMP_FILE") if [ "$temp" -ge "$SHUTDOWN_AT" ]; then logger -t temp-guard "温度达到 ${temp}, 执行关机" /sbin/shutdown -h now elif [ "$temp" -ge "$THRESHOLD" ]; then logger -t temp-guard "温度偏高: ${temp}" fi脚本里的TEMP_FILE路径要换成你机器上实际的温度节点。如果信号线和主板的测速针没有对应好,主板BIOS里会一直报风扇错误,这时候不要硬拆,确认一下测速线是否有独立转接头,没有的话就把BIOS的检测项关掉。
5. 调试、排障与最终建议
5.1 怎么验证调速是否真的生效
不管用了哪种方案,最终效果要看两个指标:风扇转速和CPU温度。建议手动把PWM设到低值再设到高值,然后用下面命令观察转速变化:
watch -n 1 cat /sys/class/hwmon/hwmon*/fan*_input如果是IPMI方案,用sensors和ipmitool sensor一起看。注意sensors里的转速值来自主板监控芯片,IPMI里的转速值来自BMC,两者可能有一定误差,只要趋势一致就算正常。
满载测试可以用stress-ng:
apt install stress-ng -y stress-ng --cpu 4 --timeout 600烤机20分钟,观察CPU温度和风扇转速是否在预设曲线内。如果温度压不住,优先提高对应温度区间的PWM值,而不是整体调高所有档位。
5.2 常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
写pwm1没反应 | pwm1_enable不是1 | 先写echo 1 > pwm1_enable |
| 风扇转速一直显示0 | 风扇是3针或测速线没接 | 换4针风扇,或忽略转速读数 |
| 开机后风扇转一下又停 | 主板自检策略强制干预 | 检查BIOS风扇控制选项,改成PWM或禁用 |
| 重启后调速失效 | 脚本没做成开机自启 | 确认systemd服务enable |
| 待机正常满载飙升 | 温控曲线区间太陡 | 把高温段PWM提前,留余量 |
| IPMI命令无效 | 厂商OEM命令不同 | 确认主板厂商和固件版本,查命令手册 |
| 手动模式后温度保护不触发 | BMC固件策略限制 | 恢复自动模式,或保留硬件报警 |
5.3 温度曲线设置的建议
我调试过好几台机器,总结出一个经验:不要一开始就追求极限静音。第一次跑曲线时,把不同温度点的PWM值都调得偏保守一些,先确认散热余量,再用一周时间逐步下探。比如60度时先给90(约35%),噪音能接受就不要降到70。每次调整幅度控制在20%以内,否则容易因温差导致风扇频繁变速。
另外记住一个常识:风扇调速不可能同时满足“极低噪音”和“低温待机”。你需要做的是找到自己机器在可接受噪音下的最大温度点。我自己的标准是CPU满载不超过85度,待机不超过55度,用这个前提反推曲线,而不是先把转速定死再去看温度。
最后说一个我自己的用法。我目前的主力PVE节点是超微板子加IPMI方案,备用机是普通B360主板加sysfs脚本。两台机器都在脚本里保留了一条“温度超过80度无条件全速”的硬规则,区别只是写入方式不同。调试期间我踩过最深的坑是只盯着CPU温度,忽略了风扇转速曲线在高负载后没有及时降回来,导致噪音一阵一阵的。后来加了滞回逻辑,让PWM只有在温度明显回落后才下调,体验立刻正常了。希望这篇对正在折腾PVE风扇的你有帮助。