Skip to content

OpenWrt 故障处理案例

案例一:网关重启后 Modbus 采集中断

故障现象

生产环境中,OpenWrt 网关每次重启后约 3 分钟内 Modbus 采集程序无法连接到 RS485 设备,3 分钟后自动恢复。

排查过程

bash
# 1. 查看串口设备状态
ls -la /dev/ttyS1
# 设备存在,权限正常

# 2. 查看采集程序日志
logread | grep modbus
# [ERROR] Failed to connect: Resource temporarily unavailable

# 3. 检查串口是否被占用
fuser /dev/ttyS1
# 无输出,未被占用

# 4. 手动测试串口
echo -e '\x01\x03\x00\x00\x00\x01\x84\x0A' > /dev/ttyS1
# 无响应

# 5. 检查串口参数
stty -F /dev/ttyS1 -a
# 发现波特率为 115200,而设备要求 9600

根本原因

procd 服务启动顺序问题:采集程序在 network 服务完全就绪前启动,导致串口初始化脚本(依赖网络时间同步)未执行完成。同时,串口参数在每次重启后被重置为默认值。

解决方案

bash
# 1. 创建串口初始化脚本
cat > /etc/init.d/serial-init <<'EOF'
#!/bin/sh /etc/rc.common
USE_PROCD=1
START=60  # 在 network(20) 之后,采集程序(95)之前

start_service() {
    # 配置 RS485 串口参数
    stty -F /dev/ttyS1 9600 cs8 -cstopb -parenb raw
    # 设置低延迟模式
    setserial /dev/ttyS1 low_latency
    logger -t serial-init "RS485 port initialized"
}
EOF
chmod +x /etc/init.d/serial-init
/etc/init.d/serial-init enable

# 2. 修改采集程序服务,添加依赖
# /etc/init.d/modbus-collector
# 在 start_service 中添加:
procd_set_param respawn 30 5 0  # 失败后 30 秒重试

案例二:MQTT 消息丢失,Broker 连接频繁断开

故障现象

网关上的 mosquitto broker 每隔 1-2 小时断开一次,重连后部分历史消息丢失。

排查过程

bash
# 查看 mosquitto 日志
logread | grep mosquitto | tail -50
# 1703123456: Socket error on client gateway-001, disconnecting.
# 1703123460: New connection from 192.168.1.50 on port 1883.

# 检查系统资源
free -m
# total: 128MB, used: 121MB, free: 7MB  ← 内存严重不足!

# 查看内存占用最高的进程
top -b -n 1 | head -20
# mosquitto 占用 45MB,node-red 占用 62MB

# 检查 OOM 日志
dmesg | grep -i "out of memory"
# [12345.678] Out of memory: Kill process 1234 (mosquitto) score 892

根本原因

设备内存仅 128MB,node-red 内存泄漏导致系统 OOM,内核强制杀死 mosquitto 进程。

解决方案

bash
# 1. 限制 node-red 内存使用
# /etc/init.d/node-red
procd_set_param command node --max-old-space-size=64 /usr/bin/node-red

# 2. 配置 mosquitto 持久化(防止消息丢失)
cat > /etc/mosquitto/mosquitto.conf <<'EOF'
persistence true
persistence_location /overlay/mosquitto/
persistence_file mosquitto.db
autosave_interval 60

# 限制内存使用
max_queued_messages 1000
max_inflight_messages 20
EOF

# 3. 添加内存监控,提前预警
cat > /usr/bin/mem-watchdog.sh <<'EOF'
#!/bin/sh
MEM_FREE=$(grep MemFree /proc/meminfo | awk '{print $2}')
if [ "$MEM_FREE" -lt 20480 ]; then  # 低于 20MB 告警
    logger -t mem-watchdog "WARNING: Low memory ${MEM_FREE}KB"
    mosquitto_pub -h localhost -t "alert/gateway/memory" \
      -m "{\"free_kb\":$MEM_FREE,\"level\":\"warning\"}"
fi
EOF
echo "*/5 * * * * /usr/bin/mem-watchdog.sh" >> /etc/crontabs/root

案例三:4G 模块掉线后无法自动恢复

故障现象

使用 EC21 4G 模块的网关,在信号弱区域偶发掉线后,即使信号恢复也无法自动重连,需要手动重启。

排查过程

bash
# 检查 modem 状态
mmcli -m 0
# Status: failed
# Reason: sim-missing  ← 误报,SIM 卡实际存在

# 检查 USB 设备
lsusb
# Bus 001 Device 003: ID 2c7c:0121 Quectel EC21

# 查看 ModemManager 日志
logread | grep ModemManager | tail -30
# ModemManager: <warn> couldn't load SIM: GDBus.Error: org.freedesktop.ModemManager1.Error.Core.Failed

# 检查 QMI 接口
ls /dev/cdc-wdm*
# /dev/cdc-wdm0  ← 存在

# 手动重置 modem
mmcli -m 0 --reset
# 重置后恢复正常

根本原因

ModemManager 在检测到信号丢失后进入错误状态,未实现自动状态机恢复。

解决方案

bash
# 创建 4G 连接看门狗
cat > /usr/bin/4g-watchdog.sh <<'EOF'
#!/bin/sh

INTERFACE="wwan0"
CHECK_HOST="8.8.8.8"
MAX_FAIL=3
FAIL_COUNT_FILE="/tmp/4g_fail_count"

# 读取失败计数
FAIL_COUNT=$(cat "$FAIL_COUNT_FILE" 2>/dev/null || echo 0)

# 检查网络连通性
if ping -c 2 -W 5 -I "$INTERFACE" "$CHECK_HOST" > /dev/null 2>&1; then
    echo 0 > "$FAIL_COUNT_FILE"
    exit 0
fi

FAIL_COUNT=$((FAIL_COUNT + 1))
echo "$FAIL_COUNT" > "$FAIL_COUNT_FILE"
logger -t 4g-watchdog "Ping failed, count: $FAIL_COUNT"

if [ "$FAIL_COUNT" -ge "$MAX_FAIL" ]; then
    logger -t 4g-watchdog "Resetting 4G modem..."
    
    # 方法1:通过 ModemManager 重置
    mmcli -m 0 --reset 2>/dev/null
    sleep 10
    
    # 方法2:如果 MM 重置失败,重启 USB 设备
    if ! ping -c 1 -W 5 -I "$INTERFACE" "$CHECK_HOST" > /dev/null 2>&1; then
        logger -t 4g-watchdog "MM reset failed, cycling USB power..."
        # 通过 GPIO 控制 USB 电源(硬件相关)
        echo 0 > /sys/class/gpio/gpio18/value
        sleep 3
        echo 1 > /sys/class/gpio/gpio18/value
        sleep 15
    fi
    
    echo 0 > "$FAIL_COUNT_FILE"
fi
EOF

chmod +x /usr/bin/4g-watchdog.sh
echo "*/2 * * * * /usr/bin/4g-watchdog.sh" >> /etc/crontabs/root
/etc/init.d/cron restart

案例四:升级固件后配置丢失

故障现象

执行 sysupgrade -c 升级后,自定义的 /etc/myapp.conf/usr/bin/custom-script.sh 丢失。

原因分析

sysupgrade -c 只保留 /lib/upgrade/keep.d/ 中列出的文件路径,自定义文件需要手动添加。

解决方案

bash
# 方法1:添加到 sysupgrade 保留列表
cat > /lib/upgrade/keep.d/myapp <<'EOF'
/etc/myapp.conf
/etc/myapp/
/usr/bin/custom-script.sh
/root/.ssh/authorized_keys
/etc/crontabs/root
EOF

# 方法2:使用 sysupgrade.conf
cat >> /etc/sysupgrade.conf <<'EOF'
/etc/myapp.conf
/usr/bin/custom-script.sh
EOF

# 验证保留列表
sysupgrade --list-backup | grep myapp

案例五:LuCI 无法访问,uhttpd 崩溃

故障现象

Web 管理界面突然无法访问,curl 返回 Connection refused。

排查过程

bash
# 检查 uhttpd 状态
/etc/init.d/uhttpd status
# inactive

# 查看错误日志
logread | grep uhttpd
# uhttpd: Failed to bind socket: Address already in use

# 查找占用 80 端口的进程
netstat -tlnp | grep :80
# tcp  0  0 0.0.0.0:80  0.0.0.0:*  LISTEN  1234/python3

# 发现是自定义 Python HTTP 服务占用了 80 端口

解决方案

bash
# 修改 uhttpd 端口或停止冲突服务
uci set uhttpd.main.listen_http='0.0.0.0:8080'
uci commit uhttpd
/etc/init.d/uhttpd restart

# 或者修改 Python 服务端口
# 修改对应服务配置文件,使用 8000 等非冲突端口

常用诊断命令速查

bash
# 系统状态
ubus call system board          # 硬件信息
cat /proc/cpuinfo               # CPU 信息
free -m                         # 内存使用
df -h                           # 磁盘使用
uptime                          # 运行时间和负载

# 网络诊断
ip addr show                    # 接口地址
ip route show                   # 路由表
ip neigh show                   # ARP 表
ss -tlnp                        # 监听端口
tcpdump -i eth1 -n port 502     # 抓取 Modbus 流量

# 日志查看
logread -f                      # 实时日志
logread | grep ERROR            # 过滤错误
dmesg | tail -50                # 内核日志

# 进程管理
ps aux                          # 所有进程
pgrep -a mosquitto              # 查找进程
kill -HUP $(pgrep mosquitto)    # 发送 HUP 信号

# UCI 调试
uci show                        # 显示所有配置
uci changes                     # 未提交的变更

褚成志的IoT笔记