Home

在 NAS 上用 Linux 接管机箱风扇:基于硬盘温度的智能温控实战(ASRock B250M-HDV + nct6791)

我有一台自组 NAS,配置大致是:

  • 主板:ASRock B250M-HDV
  • 硬盘:目前 4 块 3.6TB 机械盘(后续计划扩展到 10 盘位)
  • 系统:Linux(带 systemd,可用 smartctlsensors 等工具)

关注点

  • NAS 的主要热源在 硬盘,CPU 负载较低且不易过热。
  • 希望在 操作系统层面 精细控制机箱风扇(硬盘风道风扇),而不是每次进 BIOS 调整。
  • 目标是:
    • 风扇转速 跟随硬盘最高温度 自动变化;
    • 保持 尽量安静 的前提下,确保硬盘温度在合理范围;
    • 支持硬盘扩容(10 盘位),脚本不绑定具体盘数量;
    • 有日志、有轮转,行为可观测,不是黑盒;
    • 开机自启,挂了能自动重启;
    • 不妨碍硬盘休眠(5 分钟无访问自动进入 standby)。

整体设计思路

设计原则

  • 控制对象:机箱中负责硬盘风道的风扇(通过主板风扇控制芯片 nct6791 的 pwm4/pwm5 控制)。
  • 温度指标
    • 主指标:所有机械盘中的最高温度(SMART 属性 194 Temperature_Celsius)。
    • 次指标:CPU 包温 只用于高温兜底。
  • 控制策略
    • 6 分钟(360 秒) 读取一次硬盘温度(配合硬盘 5 分钟休眠,确保盘有机会睡着);
    • 使用 smartctl -n standby -A不唤醒已休眠的硬盘
    • 根据最高盘温映射到几档 PWM 值(偏静音的分级策略);
    • CPU 温度超阈值时强制提高风扇转速;
    • 防止风扇频繁大幅度抖动,档位设计相对粗但稳定。
  • 其他要求
    • 脚本输出 中文日志
    • 日志文件 单个最大 10MB,轮转时最多保留 10 个历史文件
    • 使用 systemd 实现 开机自启 + 异常自动重启

实战过程与关键步骤

确认主板与风扇控制芯片

查看主板信息:

sudo dmidecode -t baseboard

输出示例(关键部分):

Manufacturer: ASRock
Product Name: B250M-HDV

说明确认为 ASRock B250M-HDV 主板。

寻找硬件监控(hwmon)设备

先看系统中有哪些 hwmon 设备:

ls /sys/class/hwmon
for h in /sys/class/hwmon/hwmon*; do
  echo "=== $h ==="
  cat "$h/name" 2>/dev/null || true
  ls "$h"
  echo
done

初始只看到类似:

  • hwmon0: nvme(NVMe 硬盘温度)
  • hwmon1: coretemp(CPU 温度)

**没有任何 fanX_input 或 **pwmX,说明风扇控制芯片还没被驱动识别。

加载 nct6791 风扇控制芯片驱动

检查已加载模块:

lsmod | egrep "nct|it8|w836|hwmon" || echo "no related modules"

若尚未加载,尝试:

modprobe nct6775 2>/dev/null || echo "nct6775 not available"
modprobe it87    2>/dev/null || echo "it87 not available"

在这块主板上,实际加载成功的是 nct6791 软硬件组合,加载后再次查看:

ls /sys/class/hwmon
for h in /sys/class/hwmon/hwmon*; do
  echo "=== $h ==="
  cat "$h/name" 2>/dev/null || true
  ls "$h"
  echo
done

这时多出一项:

=== /sys/class/hwmon/hwmon2 ===
nct6791
...
fan1_input fan2_input ... fan6_input
pwm1 pwm1_enable ...
pwm4 pwm4_enable ...
pwm5 pwm5_enable ...
...

说明主板风扇/电压/温度的监控和控制接口已经暴露出来。

为了让驱动开机自动加载,可以写入 /etc/modules

echo "nct6791" | sudo tee -a /etc/modules

(确认不重复即可。)

验证哪一路 PWM 控制哪只风扇

进入对应 hwmon 目录(具体编号以实际为准,这里是 hwmon2):

cd /sys/class/hwmon/hwmon2

查看当前 PWM 和风扇转速:

for x in pwm1 pwm2 pwm3 pwm4 pwm5 pwm6; do
  printf "%s: " "$x"; cat "$x" 2>/dev/null || echo "(不存在)";
  printf "%s_enable: " "$x"; cat "${x}_enable" 2>/dev/null || echo "(不存在)";
  echo;
done

for f in fan1_input fan2_input fan3_input fan4_input fan5_input fan6_input; do
  printf "%s: " "$f"; cat "$f" 2>/dev/null || echo "(不存在)";
done

示例输出(关键段):

pwm4: 76
pwm4_enable: 5

pwm5: 76
pwm5_enable: 5

fan4_input: 675
fan5_input: 683

此时 pwm4/pwm5 处于主板自动模式(enable=5),fan4/fan5 转速约 680 RPM。

实验验证:将 pwm4/pwm5 切换为手动并提高占空比(只往上调,避免停转):

cd /sys/class/hwmon/hwmon2

echo 1   > pwm4_enable
echo 1   > pwm5_enable
echo 160 > pwm4
echo 160 > pwm5

sleep 5

cat pwm4; cat pwm4_enable
cat pwm5; cat pwm5_enable
cat fan4_input; cat fan5_input

结果类似:

pwm4: 160
pwm4_enable: 1

pwm5: 160
pwm5_enable: 1

fan4_input: 1215
fan5_input: 1215

说明:

  • pwm4fan4_input
  • pwm5fan5_input
  • 且两路风扇就是硬盘风道对应的风扇(转速明显上升)

五、读取硬盘温度(SMART)

列出磁盘:

lsblk -o NAME,TYPE,SIZE,MODEL

确认机械盘设备名为 /dev/sda/dev/sdb/dev/sdc/dev/sdd 等。

使用 smartctl 读取 SMART 信息:

smartctl -A /dev/sda | egrep '^194|^190|Temperature'

示例温度信息:

190 Airflow_Temperature_Cel ...
194 Temperature_Celsius     ... 42 (0 17 0 0 0)

整机硬盘温度概览命令:

for d in /dev/sd?; do
  smartctl -A "$d" 2>/dev/null | awk "/^194 Temperature_Celsius/ {print \"$d:\", \$10\"°C\"}";
done

我们定义 控制指标 为:所有 /dev/sd? 中温度最高的那一块盘

六、硬盘休眠与监控频率的配合

为了让硬盘能正常休眠(用户设置为 5 分钟无访问自动进入 standby):

  • 监控间隔必须大于休眠时间:设置 INTERVAL=360 秒(6 分钟),确保硬盘有完整的 5 分钟空档可以进入休眠。
  • **使用 **smartctl -n standby -A:如果盘已经进入 standby,该命令不会唤醒它,只是拿不到温度(视为 0)。

这样既能根据硬盘温度调风扇,又不会妨碍硬盘休眠。


核心温控脚本:按硬盘最高温调节 pwm4/pwm5(带中文日志 + 日志轮转)

脚本路径建议:/usr/local/sbin/nas-hdd-fanctl.sh
内容如下:

#!/bin/bash

LOG_FILE="/var/log/nas-hdd-fanctl.log"
MAX_SIZE=$((10 * 1024 * 1024))   # 10MB
MAX_FILES=10

log_msg() {
  # 日志滚动:大于 10MB 时,nas-hdd-fanctl.log -> .1 -> .2 ...,最多 10 个
  if [ -f "$LOG_FILE" ]; then
    size=$(stat -c%s "$LOG_FILE" 2>/dev/null || echo 0)
    if [ "$size" -ge "$MAX_SIZE" ]; then
      # 从高编号往低编号滚动
      for ((i=MAX_FILES-1; i>=1; i--)); do
        if [ -f "${LOG_FILE}.${i}" ]; then
          if [ "$i" -eq "$MAX_FILES-1" ]; then
            rm -f "${LOG_FILE}.${i}"
          else
            mv "${LOG_FILE}.${i}" "${LOG_FILE}.$((i+1))"
          fi
        fi
      done
      mv "$LOG_FILE" "${LOG_FILE}.1"
    fi
  fi

  # 追加写中文日志:时间戳 + 文本
  echo "$(date '+%Y-%m-%d %H:%M:%S') $*" >> "$LOG_FILE"
}

# 动态找到 nct6791 的 hwmon 目录
HWMON_DIR=""
for h in /sys/class/hwmon/hwmon*; do
  if [ -f "$h/name" ] && grep -q "^nct6791$" "$h/name"; then
    HWMON_DIR="$h"
    break
  fi
done

if [ -z "$HWMON_DIR" ]; then
  log_msg "[错误] 未找到 nct6791 的 hwmon 设备,脚本退出"
  sleep 60
  exit 1
fi

log_msg "[信息] 使用 hwmon 目录: $HWMON_DIR"

PWM4="$HWMON_DIR/pwm4"
PWM5="$HWMON_DIR/pwm5"
PWM4_EN="$HWMON_DIR/pwm4_enable"
PWM5_EN="$HWMON_DIR/pwm5_enable"

# 确认 pwm4/pwm5 存在
if [ ! -f "$PWM4" ] || [ ! -f "$PWM5" ]; then
  log_msg "[错误] 在 $HWMON_DIR 下未找到 pwm4/pwm5,脚本退出"
  sleep 60
  exit 1
fi

# 切换 pwm4/pwm5 到手动模式
[ -f "$PWM4_EN" ] && echo 1 > "$PWM4_EN"
[ -f "$PWM5_EN" ] && echo 1 > "$PWM5_EN"
log_msg "[信息] 已将 pwm4/pwm5 设置为手动模式"

# 初始较安静又不算太低的转速
SPEED=110
echo "$SPEED" > "$PWM4"
echo "$SPEED" > "$PWM5"
log_msg "[信息] 初始风扇转速设为 $SPEED"

# 读取所有 /dev/sd? 的最高硬盘温度(SMART 194 Temperature_Celsius)
# 使用 -n standby:如果磁盘已休眠则不唤醒,返回空温度
get_max_hdd_temp() {
  local max=0
  local found=0
  for d in /dev/sd?; do
    [ -b "$d" ] || continue
    t=$(smartctl -n standby -A "$d" 2>/dev/null | awk '/^194 Temperature_Celsius/ {print $10; exit}')
    if echo "$t" | grep -q '^[0-9][0-9]*$'; then
      found=1
      [ "$t" -gt "$max" ] && max="$t"
    fi
  done
  if [ "$found" -eq 0 ]; then
    echo 0
  else
    echo "$max"
  fi
}

# 获取 CPU 包温(Package id 0),整数 °C;失败则输出空
get_cpu_temp() {
  sensors 2>/dev/null | awk '
    /Package id 0:/ {
      gsub(/[^0-9.]/,"",$4);
      printf "%d", $4;
      exit
    }'
}

# 检测间隔(秒)——与硬盘休眠 5 分钟配合使用:这里用 360 秒(6 分钟)
INTERVAL=360
log_msg "[信息] 开始温控循环,检测间隔 ${INTERVAL} 秒(使用 -n standby 保护硬盘休眠)"

while true; do
  max_hdd=$(get_max_hdd_temp)
  cpu_t=$(get_cpu_temp)

  # 以"硬盘最高温"为主的风扇档位策略(偏静音)
  if [ "$max_hdd" -lt 32 ]; then
    SPEED=90       # 很凉,尽量安静
  elif [ "$max_hdd" -lt 37 ]; then
    SPEED=110      # 正常温度,轻微风
  elif [ "$max_hdd" -lt 42 ]; then
    SPEED=150      # 稍热,适度提高
  else
    SPEED=200      # 偏热,显著加强风量
  fi

  # CPU 高温兜底:CPU >= 70°C 强制提速
  if echo "$cpu_t" | grep -q '^[0-9][0-9]*$' && [ "$cpu_t" -ge 70 ]; then
    SPEED=220
  fi

  # 保险:限制在 [80,230]
  if [ "$SPEED" -lt 80 ]; then
    SPEED=80
  elif [ "$SPEED" -gt 230 ]; then
    SPEED=230
  fi

  # 应用到硬盘风道两个风扇
  echo "$SPEED" > "$PWM4"
  echo "$SPEED" > "$PWM5"

  log_msg "[循环] 硬盘最高温=${max_hdd}°C CPU温度=${cpu_t:-NA}°C 设定转速=${SPEED}"

  sleep "$INTERVAL"
done

注意事项:

  • 控制对象仅为 pwm4/pwm5 对应的风扇(硬盘风道),CPU 风扇依然由主板 BIOS / SmartFan 负责;
  • /dev/sd? 会动态适配将来扩展到 10 盘位甚至更多;
  • 日志文件路径为 /var/log/nas-hdd-fanctl.log,单个文件 10MB 自动轮转,最多保留 10 个历史文件。

使用 systemd 配置开机自启

1. 赋予脚本执行权限

chmod +x /usr/local/sbin/nas-hdd-fanctl.sh

2. 创建 systemd 服务单元

文件路径:/etc/systemd/system/nas-hdd-fan.service

内容:

[Unit]
Description=NAS HDD temperature based fan control (pwm4/pwm5)
After=multi-user.target
Wants=multi-user.target

[Service]
Type=simple
ExecStart=/usr/local/sbin/nas-hdd-fanctl.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

3. 重新加载并启用服务

systemctl daemon-reload
systemctl enable --now nas-hdd-fan.service

查看状态:

systemctl status nas-hdd-fan.service

查看日志:

tail -f /var/log/nas-hdd-fanctl.log

实际效果与可调参数

关注指标

  • 硬盘温度:通过 SMART 194 属性查看长期稳定区间(建议长期控制在 40–45°C 内)。
  • 风扇转速:通过 sensors 查看 fan4fan5 的 RPM。
  • 硬盘休眠状态:通过 hdparm -C /dev/sdX 查看盘是否处于 standby。

可调参数

  • 检测间隔INTERVAL=360(可按需求改为 300/600 秒等,注意要大于硬盘休眠时间)。
  • 档位温度阈值32/37/42°C 可根据实际盘温和噪音感受微调。
  • PWM 档位90/110/150/200/220 可根据风扇特性和噪音容忍度微调。
  • 日志轮转大小MAX_SIZE=$((10 * 1024 * 1024))(单文件 10MB,可按需调整)。

辅助监控脚本

为了方便实时观察温度和转速,可以使用 /root/check-nas-therm.sh(持续刷新版):

bash /root/check-nas-therm.sh

默认每 10 秒刷新一次,也可以自定义间隔:

INTERVAL=5 bash /root/check-nas-therm.sh

总结

这套方案主要解决了以下几个问题:

  • 利用 nct6791 芯片,在 Linux 系统层面直接接管风扇控制,不再依赖 BIOS,支持脚本化/自动化。
  • 硬盘最高温度 为核心指标,结合 CPU 高温兜底,实现更符合 NAS 场景的温控策略。
  • 通过 中文日志 + 日志轮转(单文件 10MB,最多 10 个)保证脚本行为可观测、可审计,便于日后优化与排障。
  • 使用 systemd 实现 开机自启 + 异常自动重启,真正做到”设置一次,长期托管”。
  • 使用 smartctl -n standby -A** + 合理的监控间隔(6 分钟)**,确保硬盘能正常进入 5 分钟休眠,不会被监控脚本频繁唤醒。

如果你也用类似的主板/NAS 方案,可以在此基础上根据自己的硬盘温度/噪音偏好/休眠需求,微调几个关键参数,就能获得一套非常贴合自己使用习惯的自动温控系统。

Linux AI