收起左侧

usb外接硬盘柜休眠方法(指示灯熄灭,风扇停转),铁威马d8 hybrid实测有效

1
回复
429
查看
[ 复制链接 ]

3

主题

5

回帖

0

牛值

江湖小虾

2026-5-23 14:38:49 显示全部楼层 阅读模式

USB硬盘柜休眠方案(铁威马d8 hybrid混合硬盘柜实测有效,其他自测。适用于硬盘柜内部是 USB转SCSI转ATA 的桥接链路)

环境

  • 依赖:Python3(系统自带)、cron(系统自带)、systemd(系统自带)、hdparm(系统自带)
  • 无需额外安装任何软件包

停转策略

模式 方式 效果 原因
normal(首次停转) Python SCSI STOP 停硬盘+风扇+灯 无盘在工作,安全停整柜
post_stop(静默唤醒后) hdparm -y 只停硬盘 避免 SCSI STOP 再次触发唤醒
关机 Python SCSI STOP 停硬盘+风扇+灯 所有盘都要停

文件清单

文件 用途
/usr/local/bin/scsi-stop.py 发送 SCSI STOP 命令
/usr/local/bin/hdd-auto-spindown.sh cron 定时检测,自动停转
/usr/local/bin/hdd-powersave.sh 关机停转钩子
/etc/systemd/system/hdd-powersave.service systemd 关机服务
/var/lib/hdd-spindown/ IO 状态记录目录

安装步骤

第一步:确认硬盘布局(sda、sdb、sdc这些,飞牛内硬盘信息能看到)x

下文出现的sda、sdb或者 /dev/sda、/dev/sdb按实际填写

第二步:停掉飞牛硬盘电源管理服务(可选,避免意外唤醒,如无可不选择)

systemctl stop trim_diskpowerd
systemctl disable trim_diskpowerd

第三步:创建 Python SCSI STOP 脚本

cat > /usr/local/bin/scsi-stop.py << 'PYEOF'
#!/usr/bin/env python3
import sys, os, ctypes, fcntl

SG_IO = 0x2285

class SgIoHdr(ctypes.Structure):
    _fields_ = [
        ("interface_id",    ctypes.c_int),
        ("dxfer_direction", ctypes.c_int),
        ("cmd_len",         ctypes.c_ubyte),
        ("mx_sb_len",       ctypes.c_ubyte),
        ("iovec_count",     ctypes.c_ushort),
        ("dxfer_len",       ctypes.c_uint),
        ("dxferp",          ctypes.c_void_p),
        ("cmdp",            ctypes.c_void_p),
        ("sbp",             ctypes.c_void_p),
        ("timeout",         ctypes.c_uint),
        ("flags",           ctypes.c_uint),
        ("pack_id",         ctypes.c_int),
        ("usr_ptr",         ctypes.c_void_p),
        ("status",          ctypes.c_ubyte),
        ("masked_status",   ctypes.c_ubyte),
        ("msg_status",      ctypes.c_ubyte),
        ("sb_len_wr",       ctypes.c_ubyte),
        ("host_status",     ctypes.c_ushort),
        ("driver_status",   ctypes.c_ushort),
        ("resid",           ctypes.c_int),
        ("duration",        ctypes.c_uint),
        ("info",            ctypes.c_uint),
    ]

def scsi_stop(dev):
    cdb = (ctypes.c_ubyte * 6)(0x1b, 0x00, 0x00, 0x00, 0x00, 0x00)
    sense = (ctypes.c_ubyte * 32)()
    hdr = SgIoHdr()
    hdr.interface_id = ord('S')
    hdr.dxfer_direction = -1
    hdr.cmd_len = 6
    hdr.mx_sb_len = 32
    hdr.dxfer_len = 0
    hdr.dxferp = 0
    hdr.cmdp = ctypes.addressof(cdb)
    hdr.sbp = ctypes.addressof(sense)
    hdr.timeout = 20000
    fd = os.open(dev, os.O_RDONLY)
    try:
        fcntl.ioctl(fd, SG_IO, hdr)
        print(f"OK: SCSI STOP sent to {dev}")
    except OSError as e:
        print(f"FAIL: {dev}: {e}", file=sys.stderr)
    finally:
        os.close(fd)

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} /dev/sdX [/dev/sdY ...]")
        sys.exit(1)
    for dev in sys.argv[1:]:
        scsi_stop(dev)
PYEOF
chmod +x /usr/local/bin/scsi-stop.py

第四步:创建 cron 定时停转脚本

cat > /usr/local/bin/hdd-auto-spindown.sh << 'EOF'
#!/bin/bash
STATE_DIR="/var/lib/hdd-spindown"
mkdir -p "$STATE_DIR"

PATH=/usr/sbin:/usr/bin:/sbin:/bin

# 正常模式:5分钟无IO后停转(SCSI STOP,停硬盘+风扇+灯)
NORMAL_IDLE=5
# 停转后模式:1分钟无IO后再次停转(hdparm -y,只停硬盘)
POST_STOP_IDLE=1

for dev in /dev/sd[a-z]; do
    [ -b "$dev" ] || continue
    base=$(basename "$dev")

    read_ops=$(awk '{print $1}' /sys/block/$base/stat 2>/dev/null)
    write_ops=$(awk '{print $5}' /sys/block/$base/stat 2>/dev/null)
    current_io="${read_ops:-0}_${write_ops:-0}"

    state_file="$STATE_DIR/$base"
    count_file="$STATE_DIR/${base}_count"
    mode_file="$STATE_DIR/${base}_mode"

    mode=$(cat "$mode_file" 2>/dev/null || echo "normal")
    if [ "$mode" = "post_stop" ]; then
        max_idle=$POST_STOP_IDLE
    else
        max_idle=$NORMAL_IDLE
    fi

    if [ -f "$state_file" ]; then
        last_io=$(cat "$state_file")
        if [ "$current_io" = "$last_io" ]; then
            mnt=$(lsblk -n -o MOUNTPOINT "/dev/${base}" 2>/dev/null | head -1)
            if [ -n "$mnt" ]; then
                open_count=$(lsof +D "$mnt" 2>/dev/null | wc -l)
                if [ "$open_count" -gt 0 ]; then
                    echo "0" > "$count_file"
                    if [ "$mode" = "post_stop" ]; then
                        echo "normal" > "$mode_file"
                        logger -t hdd-spindown "$dev has active users, switch to normal mode"
                    fi
                    echo "$current_io" > "$state_file"
                    continue
                fi
            fi

            count=$(cat "$count_file" 2>/dev/null || echo 0)
            count=$((count + 1))
            echo "$count" > "$count_file"
            if [ "$count" -ge "$max_idle" ]; then
                if [ "$mode" = "post_stop" ]; then
                    hdparm -y "$dev" > /dev/null 2>&1
                    logger -t hdd-spindown "$dev stopped by hdparm -y (post_stop)"
                else
                    python3 /usr/local/bin/scsi-stop.py "$dev"
                    logger -t hdd-spindown "$dev stopped by SCSI STOP (normal)"
                    echo "post_stop" > "$mode_file"
                fi
                echo "0" > "$count_file"
            fi
        else
            echo "0" > "$count_file"
            if [ "$mode" = "post_stop" ]; then
                echo "normal" > "$mode_file"
                logger -t hdd-spindown "$dev active, switch to normal mode"
            fi
        fi
    fi

    echo "$current_io" > "$state_file"
done
EOF
chmod +x /usr/local/bin/hdd-auto-spindown.sh

第五步:设置 cron 定时任务

(crontab -l 2>/dev/null | grep -v "hdd-auto-spindown.sh"; echo "* * * * * /usr/local/bin/hdd-auto-spindown.sh") | crontab -

确认:

crontab -l | grep -v "^#"

第六步:创建关机自动停转钩子

cat > /usr/local/bin/hdd-powersave.sh << 'EOF'
#!/bin/bash
for dev in /dev/sd[a-z]; do
    [ -b "$dev" ] || continue
    python3 /usr/local/bin/scsi-stop.py "$dev"
done
EOF
chmod +x /usr/local/bin/hdd-powersave.sh
cat > /etc/systemd/system/hdd-powersave.service << 'EOF'
[Unit]
Description=Spin down USB HDDs before shutdown
DefaultDependencies=no
Before=shutdown.target reboot.target halt.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/hdd-powersave.sh
TimeoutStartSec=10

[Install]
WantedBy=halt.target reboot.target shutdown.target
EOF
systemctl daemon-reload
systemctl enable hdd-powersave.service

第七步:验证

手动测试停转:

python3 /usr/local/bin/scsi-stop.py /dev/sdb

观察:硬盘停转 + 风扇停 + 指示灯灭 + 不会立即唤醒。如果硬盘重新启动,再执行hdparm -y 硬盘。

手动执行 cron 脚本:

/usr/local/bin/hdd-auto-spindown.sh

查看日志:

journalctl -t hdd-spindown --no-pager -n 20

确认 cron:

crontab -l | grep -v "^#"

确认关机钩子:

systemctl is-enabled hdd-powersave.service

第八步:BIOS 开启 ErP/EuP(可选但推荐)

关机后切断 USB 5V 待机供电,防止硬盘柜被重新唤醒。


工作逻辑

两种模式

模式 触发条件 等待时间 停转方式 效果
normal 正常运行/有IO后 5分钟 Python SCSI STOP 停硬盘+风扇+灯
post_stop 首次停转成功后 1分钟 hdparm -y 只停硬盘

为什么两种方式

SCSI STOP 会停风扇和指示灯,效果最好
但有概率在静默唤醒场景下再次触发唤醒

hdparm -y 只停硬盘,不停风扇和灯
不会触发再次唤醒,适合频繁的静默唤醒兜底

首次停转用 SCSI STOP → 彻底停止
静默唤醒后用 hdparm -y → 安全兜底,不触发再次唤醒

三重保护

检测到IO变化     → 重置计数,不停转
检测到有进程使用  → 重置计数,不停转
无IO + 无进程使用 → 计数+1 → 达到阈值 → 停转

完整流程

开机
  → 状态:normal
  → cron 每分钟检查一次

运行中(normal 模式)
  → IO有变化 → 重置计数
  → IO无变化 + 有进程使用挂载点 → 重置计数
  → IO无变化 + 无进程使用 → 计数+1
  → 连续5次无IO(5分钟)→ Python SCSI STOP
  → 硬盘停转 + 风扇停 + 灯灭
  → 切换到 post_stop 模式

被静默唤醒(post_stop 模式)
  → 风扇不转,灯不亮,只有硬盘空转
  → IO无变化 + 无进程使用 → 计数+1
  → 连续1次无IO(1分钟内)→ hdparm -y
  → 只停硬盘,不触发再次唤醒

有IO访问
  → 硬盘自动唤醒
  → IO计数变化 → 重置计数 → 切换回 normal 模式
  → 等待5分钟无IO后再用 SCSI STOP 停转

关机
  → systemd 钩子 → Python SCSI STOP → 所有盘停转
  → (若开ErP)USB口断 电 → 硬盘柜彻底断 电

开机
  → USB恢复供电 → 硬盘柜通电 → 硬盘自动上电
  → cron 重新开始监控

手动操作

手动停转单块盘(SCSI STOP,停硬盘+风扇+灯)

python3 /usr/local/bin/scsi-stop.py /dev/sdb

手动停转多块盘

python3 /usr/local/bin/scsi-stop.py /dev/sda /dev/sdb /dev/sdc /dev/sdd

手动停转所有 sd 盘

for dev in /dev/sd[a-z]; do [ -b "$dev" ] && python3 /usr/local/bin/scsi-stop.py "$dev"; done

手动停转单块盘(hdparm -y,只停硬盘)

hdparm -y /dev/sdb

手动执行一次自动停转脚本(模拟 cron)

/usr/local/bin/hdd-auto-spindown.sh

手动执行关机停转脚本

/usr/local/bin/hdd-powersave.sh

查看状态

查看硬盘模式、IO状态和计数

for dev in /dev/sd[a-z]; do [ -b "$dev" ] || continue; base=$(basename "$dev"); io=$(cat /var/lib/hdd-spindown/$base 2>/dev/null || echo "未记录"); count=$(cat /var/lib/hdd-spindown/${base}_count 2>/dev/null || echo "0"); mode=$(cat /var/lib/hdd-spindown/${base}_mode 2>/dev/null || echo "normal"); echo "$base: IO=$io count=$count mode=$mode"; done

输出示例:

sda: IO=9778_1162 count=2 mode=normal
sdb: IO=552_0 count=0 mode=post_stop

查看 cron 日志

journalctl -t hdd-spindown --no-pager -n 20

查看实时日志

journalctl -t hdd-spindown -f

Ctrl + C 退出。

查看 cron 任务

crontab -l | grep -v "^#"

查看关机钩子状态

systemctl is-enabled hdd-powersave.service

查看 IO 状态记录

ls -la /var/lib/hdd-spindown/

查看有没有进程在访问硬盘

lsof /dev/sdb

修改参数

修改正常模式空闲时间

编辑 /usr/local/bin/hdd-auto-spindown.sh,修改 NORMAL_IDLE

NORMAL_IDLE=5   → 5分钟无IO后停转
NORMAL_IDLE=10  → 10分钟无IO后停转
NORMAL_IDLE=30  → 30分钟无IO后停转

修改停转后模式等待时间

编辑 /usr/local/bin/hdd-auto-spindown.sh,修改 POST_STOP_IDLE

POST_STOP_IDLE=1  → 静默唤醒后1分钟内再次停转
POST_STOP_IDLE=2  → 静默唤醒后2分钟内再次停转

常见问题

硬盘不自动停转

(可选)自带的 trim_diskpowerd 服务可能会定期用 smartctl 检查硬盘,导致IO计数被重置。需要停掉:

systemctl stop trim_diskpowerd
systemctl disable trim_diskpowerd

为5分钟休眠后,风扇停了,硬盘又重新启动

等待1分钟后触发hdparm -y,观察是否停掉不会唤醒

为什么每分钟执行,停转后被静默唤醒

硬盘柜桥接芯片可能会在无IO的情况下唤醒硬盘(风扇不转、灯不亮)。post_stop 模式会在1分钟内用 hdparm -y 再次停转。

关机后硬盘柜重新被唤醒

主机USB口有5V待机供电,硬盘柜检测到USB有电会重新初始化。需要在BIOS开启ErP/EuP。


卸载步骤

crontab -l 2>/dev/null | grep -v "hdd-auto-spindown.sh" | crontab -
systemctl stop hdd-powersave.service
systemctl disable hdd-powersave.service
systemctl daemon-reload
rm -f /usr/local/bin/scsi-stop.py
rm -f /usr/local/bin/hdd-auto-spindown.sh
rm -f /usr/local/bin/hdd-powersave.sh
rm -f /etc/systemd/system/hdd-powersave.service
rm -rf /var/lib/hdd-spindown

如果需要恢复飞牛硬盘电源管理:

systemctl enable trim_diskpowerd
systemctl start trim_diskpowerd

技术原理

USB硬盘柜桥接链路

主机 → USB → 桥接芯片(SCSI层) → 硬盘(ATA层)
  • hdparm -y(ATA STANDBY):只到达硬盘层,桥接芯片不知道设备要停止,风扇继续转
  • sg_start --stop(SCSI STOP UNIT):到达桥接芯片层,但打开设备文件时触发桥接芯片唤醒
  • Python SG_IO ioctl:直接通过内核 ioctl 发送 SCSI STOP,不触发设备文件打开事件

为什么两种方式混用

SCSI STOP 效果最好(停硬盘+风扇+灯)
但有概率在静默唤醒场景下再次触发唤醒

hdparm -y 只停硬盘,不影响桥接芯片整体状态
不会触发再次唤醒,适合频繁的静默唤醒兜底

两种方式不在同一次执行中混用,避免桥接芯片冲突

双模式监控逻辑

正常模式(NORMAL_IDLE=5)
  首次停转需要5分钟无IO,避免频繁启停
  使用 SCSI STOP 彻底停止

停转后模式(POST_STOP_IDLE=1)
  被静默唤醒后1分钟内再次停转,快速响应
  使用 hdparm -y 安全兜底

有IO活动
  立即重置为正常模式,避免打扰用户使用
收藏
送赞
分享

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

0

主题

2

回帖

0

牛值

江湖小虾

2026-5-28 21:19:55 显示全部楼层

铁威**论坛没找到的解决方案在这里找到了smile

我这里有两个硬盘柜,雷电的是可以跟随飞牛设置的休眠逻辑走,铁威**不行,这种情况是否需要全都用手动脚本来设置硬盘的启停休眠吗

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则