摘要
一台运行 Arch Linux 的苹果硬件设备,在重任务后出现 CPU 温度接近高温上限的情况。排查发现,系统中已经加载 applesmc,并且存在 fan1_input、fan1_min、fan1_max 等 Apple SMC 风扇接口。这说明该设备的风扇控制并不适合优先使用通用 fancontrol,而更适合使用面向苹果硬件的 mbpfan。
最终处理方案是:
- 确认风扇由 Apple SMC 暴露;
- 避免同时启用
fancontrol和mbpfan; - 使用
mbpfan接管风扇控制; - 配置较保守但能及时介入的风扇曲线;
- 编写
fanwatch脚本,同时观察温度和风扇转速; - 保留完整回退方案。
实施后,风扇不再长期停留在最低转速附近,而是可以随温度上升自动提高转速,温度控制状态明显改善。
一、问题背景
设备在重任务后曾出现 CPU 温度接近高温上限的情况。虽然温度随后下降,但该现象说明系统默认风扇策略可能偏保守,在 Linux 下没有及时把风扇转速拉高。
初始观察到的状态大致如下:
CPU 温度:重任务后曾接近高温上限
风扇转速:接近最低转速
风扇接口:Apple SMC
风扇数量:单风扇
这种情况的风险在于:
重任务开始
CPU 温度迅速上升
风扇仍保持较低转速
CPU 接近温度墙
系统可能降频
性能下降,温度控制变差
因此,本次目标不是追求极限性能,而是让风扇更早介入,避免 CPU 在重任务下反复冲到高温边缘。
二、确认风扇控制接口
首先确认系统是否加载了苹果硬件相关模块:
lsmod | grep -E 'applesmc|coretemp'
如果输出中存在:
applesmc
coretemp
说明系统已经具备读取 Apple SMC 与 CPU 温度的基础条件。
然后确认风扇接口:
find /sys/devices/platform -path '*applesmc*' -type f \
\( -name 'fan*_input' -o -name 'fan*_min' -o -name 'fan*_max' -o -name 'fan*_output' -o -name 'fan*_manual' \) \
-print -exec cat {} \;
如果可以看到类似文件:
fan1_input
fan1_min
fan1_max
说明风扇是通过 Apple SMC 暴露出来的。
典型读数可能类似:
fan1_min 约 1200 RPM
fan1_max 约 2900 RPM
fan1_input 当前实际转速
这些数值应以本机实际读取结果为准。
三、为什么选择 mbpfan 而不是 fancontrol
fancontrol 和 mbpfan 都可以用于风扇控制,但适用对象不同。
| 工具 | 适用场景 |
|---|---|
fancontrol | 标准 hwmon / PWM 风扇控制 |
mbpfan | 苹果硬件,通过 Apple SMC 控制风扇 |
本机已经暴露出 applesmc 风扇接口,因此更适合使用 mbpfan。
同时要注意:
不要同时启用 fancontrol 和 mbpfan
原因是两套工具都可能试图控制风扇,容易造成冲突或不可预测行为。
四、实施前保存状态
建议先保存当前状态,方便后续对照和回退。调试记录不建议直接散落在用户主目录下,可以集中放到专门目录中。
ts="$(date +%Y%m%d-%H%M%S)"
debug_root="$HOME/workspace/system-debug/fan"
debug_dir="$debug_root/mbpfan.before.${ts}"
mkdir -p "$debug_dir"
{
echo "===== date ====="
date
echo
echo "===== kernel ====="
uname -a
echo
echo "===== modules ====="
lsmod | grep -E 'applesmc|coretemp' || true
echo
echo "===== sensors ====="
sensors
echo
echo "===== applesmc fan files ====="
find /sys/devices/platform -path '*applesmc*' -type f \
\( -name 'fan*_input' -o -name 'fan*_min' -o -name 'fan*_max' -o -name 'fan*_output' -o -name 'fan*_manual' \) \
-print -exec cat {} \;
echo
echo "===== services ====="
systemctl status fancontrol.service --no-pager || true
systemctl status mbpfan.service --no-pager || true
} | tee "$debug_dir/status.txt"
echo "Saved to: $debug_dir/status.txt"
这样诊断记录会保存到:
~/workspace/system-debug/fan/mbpfan.before.YYYYMMDD-HHMMSS/status.txt
五、禁用 fancontrol
如果系统中存在 fancontrol,应先停用,避免与 mbpfan 冲突:
sudo systemctl disable --now fancontrol.service 2>/dev/null || true
systemctl status fancontrol.service --no-pager || true
如果服务不存在,命令失败可以忽略。
六、确保模块开机加载
为了保证重启后仍能使用 Apple SMC 与 CPU 温度读取,可以写入模块加载配置:
sudo tee /etc/modules-load.d/apple-smc-fan.conf >/dev/null <<'EOF'
applesmc
coretemp
EOF
立即加载一次:
sudo modprobe coretemp
sudo modprobe applesmc
lsmod | grep -E 'applesmc|coretemp'
七、安装 mbpfan
如果系统使用 AUR helper,可以安装:
yay -S --needed mbpfan
如果普通包不可用,可以使用开发版:
yay -S --needed mbpfan-git
安装后确认命令存在:
command -v mbpfan
mbpfan -h
八、配置 mbpfan
先备份原配置:
ts="$(date +%Y%m%d-%H%M%S)"
if [ -f /etc/mbpfan.conf ]; then
sudo cp -a /etc/mbpfan.conf "/etc/mbpfan.conf.${ts}"
fi
写入单风扇配置:
sudo tee /etc/mbpfan.conf >/dev/null <<'EOF'
[general]
min_fan1_speed = 1200 max_fan1_speed = 2923 low_temp = 50 high_temp = 65 max_temp = 80 polling_interval = 1 EOF
配置说明:
| 参数 | 说明 |
|---|---|
min_fan1_speed | 风扇最低转速,应使用本机实际读取值 |
max_fan1_speed | 风扇最高转速,应使用本机实际读取值 |
low_temp | 达到该温度后开始更积极调速 |
high_temp | 达到该温度后明显提高风扇转速 |
max_temp | 达到该温度后尽量推高风扇 |
polling_interval | 温度检查间隔,单位为秒 |
本配置的思路是:
50°C 开始介入
65°C 明显提速
80°C 尽量推高风扇
每 1 秒检查一次
由于设备曾经出现高温边缘状态,初始配置不宜过于保守。后续如果噪音过大,可以再放缓曲线。
九、前台测试 mbpfan
正式启用 systemd 服务前,建议先前台运行测试:
sudo timeout 20s mbpfan -f -v
如果没有明显错误,再启用服务。
如果出现类似错误:
Could not set fan speed
Input/output error
则不应继续启用服务,应先检查 applesmc 接口、配置文件和权限状态。
十、启用 mbpfan 服务
sudo systemctl daemon-reload
sudo systemctl enable --now mbpfan.service
systemctl status mbpfan.service --no-pager
查看日志:
journalctl -u mbpfan.service -b --no-pager -n 80
目标状态:
active (running)
十一、验证风扇是否受控
可以直接观察风扇转速:
watch -n1 'for f in /sys/devices/platform/applesmc.*/fan*_input; do echo "$f: $(cat "$f") RPM"; done'
同时观察温度:
watch -n1 sensors
启用后,如果温度在 50°C 以上,风扇不再停留在最低转速,而是上升到更高转速,例如约 2000 RPM,说明控制已经生效。
判断标准:
fan1_input 高于最低转速
温度升高时风扇转速随之上升
mbpfan.service 保持 active
CPU 温度不再轻易接近高温上限
十二、启用后的状态记录
启用成功后,可以保存一份状态记录:
ts="$(date +%Y%m%d-%H%M%S)"
debug_root="$HOME/workspace/system-debug/fan"
debug_dir="$debug_root/mbpfan.after.${ts}"
mkdir -p "$debug_dir"
{
echo "===== date ====="
date
echo
echo "===== mbpfan status ====="
systemctl status mbpfan.service --no-pager
echo
echo "===== mbpfan config ====="
cat /etc/mbpfan.conf
echo
echo "===== sensors ====="
sensors
echo
echo "===== fan input ====="
for f in /sys/devices/platform/applesmc.*/fan*_input; do
echo "$f: $(cat "$f") RPM"
done
echo
echo "===== journal ====="
journalctl -u mbpfan.service -b --no-pager -n 80
} | tee "$debug_dir/status.txt"
echo "Saved to: $debug_dir/status.txt"
保存路径:
~/workspace/system-debug/fan/mbpfan.after.YYYYMMDD-HHMMSS/status.txt
十三、如果风扇噪音过大
如果初始配置过于积极,可以将曲线调得温和一些:
sudo tee /etc/mbpfan.conf >/dev/null <<'EOF'
[general]
min_fan1_speed = 1200 max_fan1_speed = 2923 low_temp = 55 high_temp = 70 max_temp = 85 polling_interval = 2 EOF sudo systemctl restart mbpfan.service
该配置的含义是:
55°C 后开始介入
70°C 后明显提速
85°C 后尽量推高风扇
每 2 秒检查一次
不过,如果设备此前已经出现接近高温上限的情况,不建议一开始就使用过于温和的配置。应先确保温度被压住,再逐步优化噪音。
十四、回退方案
如果 mbpfan 运行异常,可以立即停用:
sudo systemctl disable --now mbpfan.service
恢复旧配置:
ls -1 /etc/mbpfan.conf.*
sudo cp -a /etc/mbpfan.conf.YYYYMMDD-HHMMSS /etc/mbpfan.conf
如果需要完全取消本次风扇控制方案:
sudo systemctl disable --now mbpfan.service
sudo rm -f /etc/modules-load.d/apple-smc-fan.conf
然后重启:
sudo reboot
十五、温度与风扇监控脚本
为了避免每次手动组合 sensors 和 fan1_input,可以在 ~/bin 下放置一个监控脚本。
脚本名称:
fanwatch
功能:
- 显示
mbpfan.service状态; - 显示 Apple SMC 风扇转速;
- 显示风扇最低/最高转速;
- 显示最高若干个温度传感器;
- 支持三列或四列输出;
- 避免传感器数量过多导致屏幕被刷满。
安装脚本:
mkdir -p ~/bin
ts="$(date +%Y%m%d-%H%M%S)"
if [ -f ~/bin/fanwatch ]; then
cp -a ~/bin/fanwatch ~/bin/fanwatch."$ts"
fi
cat > ~/bin/fanwatch <<'EOF'
#!/usr/bin/env bash
set -u
interval="${1:-1}"
topn="${2:-16}"
cols="${3:-4}"
if ! command -v sensors >/dev/null 2>&1; then
echo "Error: sensors not found. Install lm_sensors first."
exit 1
fi
if ! awk -v x="$interval" 'BEGIN { exit !(x > 0) }'; then
echo "Usage: fanwatch [interval_seconds] [top_temperature_count] [columns]"
echo "Example: fanwatch"
echo "Example: fanwatch 1 16 4"
echo "Example: fanwatch 1 12 3"
exit 1
fi
if ! [[ "$topn" =~ ^[0-9]+$ ]] || [ "$topn" -lt 1 ]; then
echo "top_temperature_count must be a positive integer."
exit 1
fi
if ! [[ "$cols" =~ ^[0-9]+$ ]] || [ "$cols" -lt 1 ] || [ "$cols" -gt 6 ]; then
echo "columns must be an integer between 1 and 6."
exit 1
fi
trap 'tput cnorm 2>/dev/null || true; echo; exit 0' INT TERM
tput civis 2>/dev/null || true
while true; do
clear
echo "===== fanwatch ====="
echo "Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo "Refresh: ${interval}s"
echo "Temps: top ${topn}, ${cols} columns"
echo
echo "===== mbpfan ====="
if systemctl is-active --quiet mbpfan.service 2>/dev/null; then
echo "mbpfan.service: active"
else
echo "mbpfan.service: inactive or not found"
fi
echo
echo "===== fan ====="
found_fan=0
for f in /sys/devices/platform/applesmc.*/fan*_input; do
[ -e "$f" ] || continue
found_fan=1
base="${f%_input}"
fan_name="$(basename "$base")"
rpm="$(cat "$f" 2>/dev/null || echo '?')"
min_file="${base}_min"
max_file="${base}_max"
min_rpm="?"
max_rpm="?"
[ -r "$min_file" ] && min_rpm="$(cat "$min_file")"
[ -r "$max_file" ] && max_rpm="$(cat "$max_file")"
printf "%-8s %5s RPM min=%s max=%s\n" "$fan_name" "$rpm" "$min_rpm" "$max_rpm"
done
if [ "$found_fan" -eq 0 ]; then
echo "No applesmc fan input found."
fi
echo
echo "===== hottest temperatures ====="
mapfile -t temps < <(
sensors | awk '
/[+-][0-9]+(\.[0-9]+)?°C/ {
line = $0
if (match(line, /[+-][0-9]+(\.[0-9]+)?°C/)) {
temp = substr(line, RSTART, RLENGTH)
gsub(/\+/, "", temp)
gsub(/°C/, "", temp)
temp = temp + 0
label = line
sub(/^[[:space:]]*/, "", label)
sub(/:.*/, "", label)
if (temp > 0 && temp < 120) {
printf "%06.1f\t%s\n", temp, label
}
}
}
' | sort -nr | head -n "$topn" | awk -F'\t' '
{
printf "%-12s %5.1f°C\n", $2, $1
}
'
)
count="${#temps[@]}"
if [ "$count" -eq 0 ]; then
echo "No valid positive temperature readings found."
else
rows=$(( (count + cols - 1) / cols ))
width=24
for ((r = 0; r < rows; r++)); do
for ((c = 0; c < cols; c++)); do
idx=$((r + c * rows))
if [ "$idx" -lt "$count" ]; then
printf "%-${width}s" "${temps[$idx]}"
fi
done
echo
done
fi
echo
echo "Press Ctrl+C to stop."
sleep "$interval"
done
EOF
chmod +x ~/bin/fanwatch
使用方式:
fanwatch
每 1 秒刷新,显示最高 16 个温度,四列输出:
fanwatch 1 16 4
每 1 秒刷新,显示最高 12 个温度,三列输出:
fanwatch 1 12 3
每 2 秒刷新,显示最高 20 个温度,四列输出:
fanwatch 2 20 4
如果当前 shell 无法识别 ~/bin,可以临时执行:
export PATH="$HOME/bin:$PATH"
长期可以加入 shell 配置文件:
grep -q 'HOME/bin' ~/.bashrc || echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc
十六、最终状态判断
本次温度控制问题的核心不是硬件完全失效,而是 Linux 下默认风扇策略偏保守,导致重任务时风扇没有足够积极地提前介入。
处理后状态可以总结为:
Apple SMC 风扇接口可用
applesmc 与 coretemp 模块正常
fancontrol 未启用
mbpfan 已启用
风扇可以从最低转速随温度上升
温度控制状态明显改善
当前方案的长期原则是:
- 苹果硬件优先使用
mbpfan管理风扇。 - 不同时启用
fancontrol和mbpfan。 - 风扇曲线先保证安全,再逐步优化噪音。
- 如果温度仍频繁接近高温上限,应考虑清灰、换硅脂、检查散热器等硬件维护。
- 调试记录应集中保存到专门目录,避免散落在用户主目录根部。