工控存储外设:U 盘/SD 卡自动挂载、插拔检测、文件拷贝备份
工控现场最常见的需求之一:插上U盘,自动把数据拷走,拔掉U盘,数据不损坏。听起来简单,坑却不少。
一、Linux USB / SD 卡设备识别机制
当外部存储设备插入时,Linux 内核会自动识别并分配设备节点:
| 设备类型 | 设备节点 | 说明 |
|---|---|---|
| U盘 | /dev/sda1,/dev/sdb1 | SCSI子系统分配,按字母递增 |
| SD卡 | /dev/mmcblk0p1 | MMC子系统分配,pN表示第N分区 |
| CD-ROM | /dev/sr0 | 较少见,工控中偶尔用于离线升级 |
关键区分:U盘是/dev/sd*,SD卡是/dev/mmcblk*,写脚本时千万别搞混。
查看当前已识别的块设备:
cat/proc/partitions# 或者lsblk二、udev 规则配置
2.1 什么是 udev
udev是 Linux 的设备管理器,负责在设备插入/拔出时执行预定义规则。工控场景下,我们靠它实现自动挂载和权限设置。
2.2 udev 规则语法
规则文件放在/etc/udev/rules.d/目录下,文件名以数字开头控制优先级:
# /etc/udev/rules.d/99-usb-storage.rules # U盘插入时触发 ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z][0-9]", \ RUN+="/etc/udev/scripts/mount-usb.sh %k" # U盘拔出时触发 ACTION=="remove", SUBSYSTEM=="block", KERNEL=="sd[a-z][0-9]", \ RUN+="/etc/udev/scripts/umount-usb.sh %k" # SD卡插入时触发 ACTION=="add", SUBSYSTEM=="block", KERNEL=="mmcblk[0-9]p[0-9]", \ RUN+="/etc/udev/scripts/mount-usb.sh %k" # SD卡拔出时触发 ACTION=="remove", SUBSYSTEM=="block", KERNEL=="mmcblk[0-9]p[0-9]", \ RUN+="/etc/udev/scripts/umount-usb.sh %k"规则字段解读:
ACTION:add(插入)或remove(拔出)SUBSYSTEM:设备子系统,存储设备是blockKERNEL:内核设备名匹配,支持通配RUN+:匹配时执行的脚本,%k传入设备名(如sda1)
2.3 自定义设备名
如果想让特定的U盘固定挂载名(比如"数据盘"),可以通过设备的唯一标识匹配:
# 查看U盘的唯一标识 udevadm info --query=all --name=/dev/sda1 | grep ID_SERIAL # 按序列号绑定固定名称 ACTION=="add", SUBSYSTEM=="block", \ ENV{ID_SERIAL}=="USB_Flash_Drive_123456", \ SYMLINK+="data_usb"这样该U盘插入后会出现/dev/data_usb软链接,换一个U盘就不行——工控现场防插错U盘的神技。
三、mdev 配置(精简 Buildroot 系统)
如果工控板用的是 Buildroot 精简系统,可能没有udev,而是用busybox mdev。配置方式不同:
# /etc/mdev.conf# U盘分区自动挂载sd[a-z][0-9]0:0 0666 */etc/udev/scripts/mount-usb.sh$MDEVmmcblk[0-9]p[0-9]0:0 0666 */etc/udev/scripts/mount-usb.sh$MDEV# 拔出时卸载sd[a-z][0-9]0:0 0666 *$/etc/udev/scripts/umount-usb.sh$MDEV同时需要确保/etc/init.d/中有启动mdev -s的脚本,并在内核中开启热插拔支持:
echo/sbin/mdev>/proc/sys/kernel/hotplug mdev-s四、udevadm monitor 监控插拔事件
调试 udev 规则时,最实用的命令就是udevadm monitor:
# 监控所有uevent事件udevadm monitor# 只看block子系统udevadm monitor --subsystem-match=block# 查看设备详细属性(用于写规则)udevadm info-a-n/dev/sda1插拔U盘时,终端会实时输出事件流,方便你确认KERNEL和SUBSYSTEM字段是否正确。
五、自动挂载脚本实现
5.1 挂载脚本
#!/bin/sh# /etc/udev/scripts/mount-usb.sh# 用法: mount-usb.sh <device> 例如: mount-usb.sh sda1DEV=$1MOUNT_DIR="/mnt/usb/$DEV"# 等待设备节点就绪(内核可能还没创建完)i=0while[!-e"/dev/$DEV"]&&[$i-lt20];dosleep0.1i=$((i+1))doneif[!-e"/dev/$DEV"];thenlogger"mount-usb: /dev/$DEVnot found, timeout"exit1fi# 创建挂载点mkdir-p"$MOUNT_DIR"# 尝试挂载(支持vfat和ext4等常见格式)mount-tvfat"/dev/$DEV""$MOUNT_DIR"2>/dev/null||\mount-texfat"/dev/$DEV""$MOUNT_DIR"2>/dev/null||\mount-text4"/dev/$DEV""$MOUNT_DIR"2>/dev/null||\mount"/dev/$DEV""$MOUNT_DIR"2>/dev/nullif[$?-eq0];thenlogger"mount-usb: /dev/$DEVmounted to$MOUNT_DIR"# 触发数据同步脚本(可选)/etc/udev/scripts/sync-data.sh"$MOUNT_DIR"&elselogger"mount-usb: failed to mount /dev/$DEV"rmdir"$MOUNT_DIR"2>/dev/nullfi5.2 卸载脚本
#!/bin/sh# /etc/udev/scripts/umount-usb.sh# 用法: umount-usb.sh <device>DEV=$1MOUNT_DIR="/mnt/usb/$DEV"# 检查是否还在挂载状态ifmountpoint-q"$MOUNT_DIR";then# 先sync刷新缓冲,防止数据损坏syncumount"$MOUNT_DIR"logger"umount-usb: /dev/$DEVunmounted from$MOUNT_DIR"fi# 清理挂载点目录rmdir"$MOUNT_DIR"2>/dev/null logger"umount-usb: cleanup$MOUNT_DIR"关键细节:卸载前一定要sync!Linux 文件系统有写缓存,直接拔U盘可能丢数据。
六、文件拷贝备份脚本
工控场景典型需求:U盘插入后自动把设备上的数据目录拷贝过去,并校验完整性。
#!/bin/sh# /etc/udev/scripts/sync-data.sh# 用法: sync-data.sh <mount_dir>MOUNT_DIR=$1DATA_DIR="/var/log/device_data"# 本地数据目录BACKUP_LOG="/var/log/backup.log"# 等待挂载稳定sleep2logger"sync-data: starting backup to$MOUNT_DIR"# 生成本次备份的目录名(带时间戳)BACKUP_NAME="backup_$(date+%Y%m%d_%H%M%S)"TARGET="$MOUNT_DIR/$BACKUP_NAME"# 执行拷贝mkdir-p"$TARGET"cp-a"$DATA_DIR"/*"$TARGET"/2>/dev/nullif[$?-ne0];thenlogger"sync-data: ERROR copy failed"exit1fi# MD5校验 - 生成本次备份的文件清单和校验值cd"$TARGET"find.-typef-execmd5sum{}\;>checksum.md5# 验证校验md5sum-cchecksum.md5>/dev/null2>&1if[$?-eq0];thenlogger"sync-data: backup OK, files at$TARGET"# 写入备份日志echo"$(date'+%Y-%m-%d %H:%M:%S')BACKUP OK ->$TARGET">>"$BACKUP_LOG"elselogger"sync-data: WARNING checksum verification failed"echo"$(date'+%Y-%m-%d %H:%M:%S')BACKUP CHECKSUM FAIL ->$TARGET">>"$BACKUP_LOG"fi# 刷新缓冲,确保数据落盘sync七、热插拔安全性
工控现场拔U盘不讲究,所以驱动和脚本层面要做好保护:
| 措施 | 说明 |
|---|---|
卸载前sync | 刷新文件系统缓冲到物理设备 |
使用umount而非直接拔 | 通知文件系统释放资源 |
mount加sync选项 | mount -o sync实时写入,牺牲性能换安全 |
| 只读挂载选项 | mount -o ro读取场景下防止误写 |
| 日志记录 | logger记录每次挂载/卸载事件,便于追溯 |
挂载时加上sync,uid=0,gid=0,umask=000参数对 vfat 格式很实用:
mount-tvfat-osync,umask=000"/dev/$DEV""$MOUNT_DIR"umask=000让所有用户可读写,避免工控程序因权限问题写不进U盘。
八、多分区 U 盘处理
一个U盘可能有多个分区(如sda1、sda2),udev 规则中KERNEL=="sd[a-z][0-9]"已经能匹配所有分区,但脚本需要处理每个分区独立挂载:
# 挂载点按分区号区分,不会互相覆盖MOUNT_DIR="/mnt/usb/$DEV"# sda1 -> /mnt/usb/sda1# sda2 -> /mnt/usb/sda2九、工控存储常见问题汇总
| 问题 | 原因 | 解决方案 |
|---|---|---|
| U盘识别但无法挂载 | 文件系统不支持 | 编译内核时开启 vfat/exfat/ntfs 支持 |
| 挂载后权限不足 | 默认只有root可写 | mount -o umask=000或修改 udev 规则赋权 |
| 拔U盘后数据丢失 | 缓冲未刷新 | 脚本中加sync,或挂载时用-o sync |
| 设备节点名不固定 | 按插入顺序分配字母 | 用ENV{ID_SERIAL}匹配固定符号链接 |
| 频繁插拔导致挂载点残留 | umount 失败 | 脚本中fuser -k杀死占用进程后再卸载 |
| SD卡分区识别不到 | 没有分区表 | 直接挂/dev/mmcblk0而非mmcblk0p1 |
存储外设自动挂载看似简单,但要在工控环境中做到无人值守、数据不丢、即插即用,每一个环节的细节都不能含糊。sync该加的加,校验该做的做,脚本该有的容错一个都不能少。