RK3576开发板外设管理:Udev实现U盘自动挂载的标准化方案

原创 作者 Forlinx 2026-03-05 14:44:00 RK3576开发板

我是燕南无声,一个在嵌入式领域摸爬滚打了12年的工程师。平时喜欢琢磨底层系统机制,也爱把实际项目中踩过的坑、填过的土记录下来。今天要分享的是基于飞凌RK3576开发板(OK3576-C)Ubuntu24.04环境,深入解析Udev设备事件监听机制与Systemd服务协同逻辑,提供U盘自动挂载标准化实现方案。从Udev配置、规则编写,到Systemd服务创建、挂载/卸载脚本开发,全流程拆解,解决路径乱码、脚本执行失效等行业常见问题,为RK3576开发板嵌入式项目提供可复用的外设管理模板。

项目背景与需求

本次项目是基于RK3576开发板(型号:OK3576-C)开发自助终端设备,客户核心需求是 系统启动后插入U盘可自动挂载到固定目录,且目录名无乱码。Ubuntu 24.04桌面版自带的文件管理器(如nautilus)虽支持U盘自动挂载,但挂载点路径会包含UUID或中文卷标,在终端操作时极易出现乱码,直接干扰后续自动化脚本的执行。

RK3576开发板

由于终端环境对路径的 固定性和可预测性要求极高,因此决定抛弃文件管理器的默认挂载机制,改用udev接管U盘的插拔事件,自定义挂载/卸载脚本,实现标准化的U盘挂载管理。

实现思路与核心逻辑

首先明确核心实现思路:udev是Linux内核设备管理器的用户空间守护进程,核心负责管理/dev目录下的设备节点,并能在设备插入/移除时触发自定义动作。我们的核心目标是—— U盘插入时自动执行挂载脚本,U盘拔出时自动执行卸载脚本

需要注意的是,Ubuntu 24.04这类高版本系统中,直接在udev规则里调用shell脚本大概率会失效:一方面udev事件触发的运行环境极度受限,另一方面脚本执行为异步模式,易出现竞争条件导致执行失败。Linux官方推荐的最优方案是 通过systemd服务包装脚本,在udev规则中通过TAG+="systemd"和ENV{SYSTEMD_WANTS}触发服务,让脚本在systemd管理的干净环境中运行,从根本上规避权限、路径等问题。

基于此,梳理出本次改造需要修改/新增的核心文件,所有操作均基于飞凌RK3576开发板的Ubuntu 24.04环境:

  • /etc/udev/udev.conf:确认udev基础配置,指向正确的规则目录;
  • /etc/udev/rules.d/95-usb-mount.rules:编写udev核心规则,捕获U盘分区插拔事件,触发对应systemd服务;
  • /etc/systemd/system/mount.service/umount.service:创建两个systemd服务单元,分别承载U盘挂载、卸载逻辑;
  • /etc/systemd/system/mount.sh/umount.sh:编写实际的挂载、卸载脚本,由上述systemd服务调用。

分步实操实现方案

以下为分步实现步骤,所有配置命令、脚本代码均经过实际环境验证,直接复制即可使用,仅在格式上做缩进和注释优化,未改动任何核心参数。

1

确认udev基础配置

首先检查并确认/etc/udev/udev.conf配置文件,确保udev的规则目录、设备根目录指向正确,无需新增配置,仅启用必要项即可:

# /etc/udev/udev.conf
# 参考udev.conf(5)获取详细配置说明
# udevd会在initrd中启动,修改此文件后建议重建initrd使配置生效
#udev_log=info
#children_max=
#exec_delay=
#event_timeout=180
#timeout_signal=SIGKILL
#resolve_names=early
# 设备根目录
udev_root="/dev/"
# udev规则文件存放目录
udev_rules="/etc/udev/rules.d/"
# 日志级别设为错误,减少冗余日志
udev_log="err"

核心关注udev_rules参数,确保其指向/etc/udev/rules.d/,保持默认配置即可,无需额外修改。

2

编写udevU盘插拔检测规则

在/etc/udev/rules.d/目录下创建95-usb-mount.rules文件(95为规则优先级,数值越大优先级越低),编写U盘插拔的触发规则,核心实现 插入创目录+启挂载服务,拔出启卸载服务

# /etc/udev/rules.d/95-usb-mount.rules
# 为U盘主设备(sd[a-z])创建符号链接usb%m,%m为内核次要设备号,保证设备标识唯一
KERNEL=="sd[a-z]", NAME="%k", SYMLINK+="usb%m", OPTIONS="last_rule"
# U盘分区(sd[a-z][0-9])插入事件:创建符号链接
ACTION=="add", KERNEL=="sd[a-z][0-9]", SYMLINK+="usb%m", NAME="%k"
# U盘分区插入事件:自动创建挂载点目录,路径为/mnt/分区名(如/mnt/sda1)
ACTION=="add", KERNEL=="sd[a-z][0-9]", RUN+="/bin/mkdir -p /mnt/%k"
# U盘分区插入事件:核心触发挂载服务,仅匹配USB块设备,避免识别SATA等本地设备
ACTION=="add",KERNEL=="sd[a-z][0-9]",SUBSYSTEM=="block",ENV{ID_BUS}=="usb",TAG+="systemd", PROGRAM="/bin/systemd-escape -p %k", ENV{SYSTEMD_WANTS}+="mount.service"
# U盘分区移除事件:创建符号链接(保持与插入规则一致性,可省略)
ACTION=="remove", KERNEL=="sd[a-z][0-9]", SYMLINK+="usb%m", NAME="%k"
# U盘分区移除事件:触发卸载服务,仅匹配USB块设备
ACTION=="remove", KERNEL=="sd[a-z][0-9]", SUBSYSTEM=="block", ENV{ID_BUS}=="usb", PROGRAM="/usr/bin/systemctl start umount.service", TAG+="systemd"

规则关键说明:ENV{ID_BUS}=="usb"精准匹配USB设备,彻底排除本地SATA、NVMe等存储设备;RUN+="/bin/mkdir -p /mnt/%k"自动创建挂载点;TAG+="systemd"告诉udev通过systemd管理后续触发的服务。

3

创建systemd挂载/卸载服务单元

由于高版本Ubuntu无法直接在udev规则中执行脚本,因此创建两个systemd服务单元mount.service和umount.service,分别调用挂载、卸载脚本,服务文件存放于/etc/systemd/system/目录。

挂载服务:mount.service

# /etc/systemd/system/mount.service
[Unit]
# 服务描述
Description=mountservice
# 服务启动时机:在网络服务后启动(挂载无需网络,仅为通用习惯,可省略)
After=network.target
[Service]
# 服务类型:forking表示脚本会以后台进程模式运行
Type=forking
# 执行用户/用户组:必须为root,挂载/卸载操作需要最高权限
User=root
Group=root
# 工作目录:挂载点根目录
WorkingDirectory=/mnt
# 服务执行命令:调用挂载脚本,--log-target=journal让日志写入systemd日志,可通过journalctl查看
ExecStart=/etc/systemd/system/mount.sh start --log-target=journal
[Install]
# 服务安装目标:多用户模式下启用
WantedBy=multi-user.target

卸载服务:umount.service

# /etc/systemd/system/umount.service
[Unit]
# 服务描述
Description=umountservice
# 服务启动时机:在网络服务后启动
After=network.target
[Service]
# 服务类型:forking表示脚本会以后台进程模式运行
Type=forking
# 执行用户/用户组:root最高权限
User=root
Group=root
# 工作目录:挂载点根目录
WorkingDirectory=/mnt
# 服务执行命令:调用卸载脚本
ExecStart=/etc/systemd/system/umount.sh start --log-target=journal
[Install]
# 服务安装目标:多用户模式下启用
WantedBy=multi-user.target

执行以下命令为两个服务文件添加可执行权限:

chmod 777 /etc/systemd/system/mount.service
chmod 777 /etc/systemd/system/umount.service
4

编写挂载/卸载核心脚本

创建与systemd服务对应的挂载脚本mount.sh和卸载脚本umount.sh,存放于/etc/systemd/system/目录,脚本为实际执行U盘挂载、卸载的核心逻辑。

挂载脚本:mount.sh

#!/bin/bash
# /etc/systemd/system/mount.sh
# U盘自动挂载脚本,挂载路径/mnt/设备名(如/dev/sda1 → /mnt/sda1)
# 初始变量,后续会被遍历结果覆盖
var2="/mnt/sda"
# 遍历所有/dev目录下的USB分区设备
for V in $(ls /dev/sd[a-z][0-9])
do
  # 打印当前设备名(调试用)
  echo $V
  # 赋值当前设备路径给变量
  var2=$V
  # 截取设备名(如从/dev/sda1中截取sda1)
  echo ${var2:5:4}
  # 执行挂载:将设备挂载到/mnt/设备名目录
  /bin/mount $V /mnt/${var2:5:4}
done

脚本关键说明:挂载点由udev规则提前创建,脚本直接执行挂载即可;使用/bin/mount绝对路径执行,避免udev/systemd环境中PATH变量缺失导致的命令找不到问题。

卸载脚本:umount.sh

#!/bin/sh
# /etc/systemd/system/umount.sh
# U盘自动卸载脚本,检测设备不存在则卸载并删除挂载点
# 定义需监控的USB设备节点
usb_device_1="/dev/sda1"
usb_device_2="/dev/sdb1"
usb_device_3="/dev/sdc1"
usb_device_4="/dev/sdd1"
# 定义设备对应的挂载点目录
mount_dir_1="/mnt/sda1"
mount_dir_2="/mnt/sdb1"
mount_dir_3="/mnt/sdc1"
mount_dir_4="/mnt/sdd1"
# 延时1秒,等待设备彻底移除,避免竞争条件导致卸载失败
sleep 1
# 检测设备1是否存在,不存在则卸载并删除挂载点
if [ ! -e "$usb_device_1" ];then
        umount $usb_device_1 > /dev/null 2>&1
        rm -rf $mount_dir_1 > /dev/null 2>&1
fi
# 检测设备2是否存在,不存在则卸载并删除挂载点
if [ ! -e "$usb_device_2" ];then
        umount $usb_device_2 > /dev/null 2>&1
        rm -rf $mount_dir_2 > /dev/null 2>&1
fi
# 检测设备3是否存在,不存在则卸载并删除挂载点
if [ ! -e "$usb_device_3" ];then
        umount $usb_device_3 > /dev/null 2>&1
        rm -rf $mount_dir_3 > /dev/null 2>&1
fi
# 检测设备4是否存在,不存在则卸载并删除挂载点
if [ ! -e "$usb_device_4" ];then
        umount $usb_device_4 > /dev/null 2>&1
        rm -rf $mount_dir_4 > /dev/null 2>&1
fi

脚本关键说明:sleep 1为核心延时操作,等待内核完成设备移除的底层操作;> /dev/null 2>&1重定向所有输出和错误到空设备,保持系统日志干净。

执行以下命令为两个脚本添加可执行权限:

chmod 777 /etc/systemd/system/mount.sh
chmod 777 /etc/systemd/system/umount.sh
5

重载配置并测试验证

完成所有配置和脚本编写后,无需重启系统,手动重载udev规则和systemd配置即可生效:

# 重载udev规则,让新编写的U盘插拔规则生效
udevadm control --reload-rules
# 重载systemd配置,让新创建的挂载/卸载服务生效
systemctl daemon-reload

测试步骤

  1. 插入U盘(单分区/多分区均可),执行ls /mnt/,可看到自动创建的挂载点目录(如sda1),执行df -h可验证U盘已成功挂载;
  2. 拔出U盘,执行ls /mnt/,对应的挂载点目录已被自动删除,执行df -h可验证U盘已成功卸载;
  3. 若需查看服务运行日志,可执行journalctl -u mount.service或journalctl -u umount.service。

验证总结

在飞凌RK3576开发板的Ubuntu 24.04桌面版环境中,经过多次实际插拔测试,该方案可实现 U盘插入自动挂载到/mnt/设备名固定目录,拔出自动卸载并删除挂载点,挂载点路径无任何UUID或中文卷标,彻底解决了默认挂载的乱码问题,完全满足自助终端设备的自动化脚本操作需求。

执行以下调试命令,可验证挂载/卸载服务是否已正常启用:

# 查看挂载服务启用状态
systemctl is-enabled mount.service
# 查看卸载服务启用状态
systemctl is-enabled umount.service

正常情况下,两个命令的输出均为enabled,表示服务已成功启用并随系统开机自启。

经验反思

本次基于udev+systemd实现U盘自动挂载,踩过了高版本Ubuntu的环境坑,也积累了嵌入式Linux外设管理的实用经验,核心总结三点:

规避udev直接调用脚本的坑 Ubuntu 20.04及以上版本中,udev的RUN指令运行环境受限,且受SELinux/AppArmor权限管控。通过systemd服务包装脚本是官方推荐的稳健方案。
卸载脚本的局限性需知晓 本次方案中的卸载脚本采用硬编码设备节点方式,仅支持sda1~sdd1四个设备。若需同时插入多个U盘或U盘有多个分区,可优化为动态遍历设备的方式。
固定路径是解决乱码的核心 默认挂载的乱码根源是路径包含UUID/中文卷标,而udev规则中通过%k获取设备名,挂载点固定为/mnt/%k,完全脱离卷标和UUID的影响。

对于飞凌嵌入式RK3576开发板,Ubuntu 24.04桌面版的整体运行表现稳定,结合udev的设备事件监听和systemd的服务管理,可灵活定制USB、串口、网卡等各类外设的自动处理逻辑。本次U盘自动挂载的改造方案,也为后续嵌入式项目中的外设管理提供了可直接复用的模板。

rk3576核心板

咨询立即获得专属报价

华北区负责人二维码

华北区负责人

华东区负责人二维码

华东区负责人

华南区负责人二维码

华南区负责人

中西区负责人二维码

中西区负责人

相关产品 >

  • FET3576-C核心板

    飞凌嵌入式RK3576核心板集成了强大的处理器和丰富的接口,提供出色的计算能力和扩展性。RK3576核心板以其卓越的性能、低功耗和稳定性,成为工业、AIoT、边缘计算、智能移动终端等领域的理想选择。无论是数据处理还是边缘计算,RK3576都能为项目提供强大的硬件支持。核心板推荐选择飞凌嵌入式瑞芯微系列RK3576J业级核心板、RK3576高性能核心板 了解详情
    FET3576-C核心板
  • OK3576-C开发板

    RK3576开发板CPU选用瑞芯微RK3576,采用核心板+底板分体式设计,采用4个100Pin板对板连接器的方式将处理器的功能引脚以最便利的方式全部引出,并针对不同的功能做了深度优化,方便用户二次开发的同时简化用户设计,为您的项目提供良好的评估及设计依据。RK3576是瑞芯微专为AIoT市场打造的一款高算力、高性能、低功耗的国产化应用处理器,集成了4个ARM Cortex-A72和4个 ARM Cortex-A53高性能核;内置6TOPS超强算力NPU;嵌入式3D GPU加之带有MMU的专用2D硬件引擎,最大限度提升显示性能;H.265超清硬解码,最高支持8K分辨率。 了解详情
    OK3576-C开发板

推荐阅读 换一批 换一批