Ubuntu18上基于udev实现U盘热插拔+自动化处理业务功能
编者按在嵌入式linux系统中,往往可以基于mdev来实现U盘/SD卡的热插拔、自动挂/卸载功能。在mdev.conf配置文件中,可以对捕捉到的插、拔事件指定执行的命令/脚本,具体配置方法此处不再赘述。这也就意味着我们可以捕获U盘等外设的插、拔事件并进一步作出相应的特殊处理。在Ubuntu系统中,本身已经具备U盘热插拔功能,但却不能方便的对U盘的插、拔事件作出特殊的、特定功能的后续处理。一种方法是
编者按
在嵌入式linux系统中,往往可以基于mdev来实现U盘/SD卡的热插拔功能。在mdev.conf配置文件中,可以对捕捉到的插、拔事件指定执行的命令/脚本,具体配置方法此处不再赘述。这也就意味着我们可以捕获U盘等外设的插、拔事件并进一步作出相应的特殊处理。
在Ubuntu系统中,本身已经具备U盘热插拔功能,但却不能很方便地对U盘的插、拔事件作出特殊的后续处理。一种方法是,定时巡查/media目录来检测是否有U盘插、拔事件发生,但这往往是傻瓜且低效的。而Udev实在是一种不错的选择。不过在Ubuntu14升级到Ubuntu18后,udev热插拔功能却无法正常使用。
本文,仅基于实际项目经验对基于Udev实现设备热插拔检测稍作探讨及记录。对Udev和mdev的工作机制、配置文件的细节信息不作赘述。
1.捕捉U盘事件
首先需要编写规则文件捕捉U盘插、拔事件,本配置仅捕捉处理sdbX、sdcX设备。具体匹配规则可见udev介绍。
1.在/etc/udev/rules.d目录下新建规则文件1-persistent-USB.rules
2.在规则文件中加入以下规则,以实现捕捉U盘插入、拔出事件并执行 handle脚本
ACTION=="add|change", SUBSYSTEM=="block", KERNEL=="sd[bc][0-9]", MODE:="0777", SYMLINK+="Myusb%n" , RUN+="/etc/udev/hotplug/usb/usb_handle"
SUBSYSTEM=="block", KERNEL=="sd[bc][0-9]", ACTION=="remove", RUN+="/etc/udev/hotplug/usb/usb_handle"
2.编写U盘事件处理脚本
执行脚本如下所示 ,主要分为3个步骤,mount 挂载、执行升级等自定义特殊操作、再次挂载到用户空间(U14:mount、U18:systemd-mount),这里需要特别注意的是,mount命令在U16以上系统中,无法挂载到用户空间,即挂载成功后仅在脚本内可见,脚本退出后,对其他命名空间不可见。通过df等命令均无法观察到挂载信息,程序自然也就无法操作,对于Ubuntu16实测不可使用,在U16上没有systemd-mount命令,需编写system [Automount]配置项文件来完成。
#!/bin/bash
UFail()
{
return 1
}
Insert_to_tmp()
{
meg="The device does not exist or the mount point has been mounted by another device."
if [ -d /sys/block/*/${name} ] ; then
chk=`df | grep "$mount_dir" -q;echo $?`
if [ "$chk" == "0" ]
then
exit 1
fi
if [ ! -d $mount_dir ]
then
/bin/mkdir -p $mount_dir
fi
if [ "$mode" = "0" ]
then
msg=`$Mount -o noatime -o nodiratime -o umask=000 -t auto $DEVNAME $mount_dir 2>&1`
else
msg=`$Mount --no-block --automount=yes --collect $DEVNAME $mount_dir 2>&1`
fi
fi
msg=`echo "$msg" | xargs `
if [ -x /sh/hotplug.sh ] ; then
/sh/hotplug.sh $mount_dir $name "$msg"
fi
}
Insert()
{
if [ $# -le 0 ]
then
return 1
fi
InsertMode=$1
meg="The device does not exist or the mount point has been mounted by another device."
if [ -d /sys/block/*/${name} ] ; then
chk=`df | grep "$mount_dir" -q;echo $?`
if [ "$chk" == "0" ]
then
exit 1
fi
if [ ! -d $mount_dir ]
then
/bin/mkdir -p $mount_dir
fi
if [ "$InsertMode" = "0" ]
then
msg="insert to namespace."
if [ "$mode" = "0" ]
then
msg+=`$Mountold -o noatime -o nodiratime -o umask=000 -t auto $DEVNAME $mount_dir 2>&1`
else
msg+=`$Mount --no-block --automount=yes --collect $DEVNAME $mount_dir 2>&1`
fi
else
msg="insert to udev namespace."
msg+=`$Mountold -o noatime -o nodiratime -o umask=000 -t auto $DEVNAME $mount_dir 2>&1`
fi
fi
msg=`echo "$msg" | xargs `
if [ -x /sh/hotplug.sh ] ; then
/sh/hotplug.sh $mount_dir $name "$msg"
fi
}
Remove()
{
if [ $# -le 0 ]
then
return 1
fi
InsertMode=$1
mountinfo=`/bin/cat /etc/mtab | /bin/grep "$DEVNAME" -w`
if [ -z "$mountinfo" ]
then
meg="All hardpoints of the device have been unmounted."
if [ -x /sh/hotplug.sh ] ; then
/sh/hotplug.sh $mountdir $name "$msg"
fi
exit 1
fi
IFSOLD=$IFS #默认的IFS的数值-->也是环境变量!
IFS=$'\n' #自定义的IFS数值
for i in $mountinfo
do
mountdir=`echo $i | /usr/bin/awk '{print $2}'`
sync
if [ "$InsertMode" = "0" ]
then
msg="remove from namespace."
if [ "$mode" = "0" ]
then
msg+=`$Umountold -f -l $mountdir 2>&1`
else
msg+=`$Umount -u $mountdir 2>&1`
fi
else
msg="remove from udev namespace."
msg+=`$Umountold -f -l $mountdir 2>&1`
fi
msg=`echo "$msg" | xargs`
if [ -x /sh/hotplug.sh ] ; then
/sh/hotplug.sh $mountdir $name "$msg"
fi
done
IFS=$IFSOLD
}
Do_special()
{
#此处自行实现特殊处理,自动检测到升级标志自动进行升级、更新等自定义操作
#do_Install || do_Restore
}
prefix_path=$(cd $(dirname $0);pwd)/
name="`basename "$DEVNAME"`"
mount_dir="/udisk"
Mountold=/bin/mount
Umountold=/bin/umount
mode=0
#/lib/systemd/systemd-udevd./lib/systemd/systemd-udevd:
#systemd by default runs systemd-udevd.service with a separate "mount namespace",which means that mounts will not be visible to the rest of the system.
#Even if you change the service parameters to fix this (commenting out the PrivateMounts and MountFlags lines), there is another problem which is that processes started from Udev are killed after a few seconds.
#In case of FUSE filesystems, such as exFAT (which is common to find on mp3 players or shareable thumb drives) and NTFS, "mount" starts a user-space process to handle the filesystem internals;
#when this is killed you will get Transport endpoint not connected errors if you try to access the filesystem.
#There are some options that work:
# 1)Start a custom systemd service from the Udev rule; the systemd service can invoke a script which can start any number of long-running processes (like FUSE).
#A concise example which automatically mounts USB disks under /media is udev-media-automount. It was found to be fast and reliable. A variant of the same idea is explained in this blog post.
# 2)Use systemd-mount instead of mount in your Udev rule. This is recommended by systemd developers.
if type "/usr/bin/systemd-mount" &>/dev/null
then
Mount=/usr/bin/systemd-mount
Umount=/usr/bin/systemd-mount
mode=1
fi
if [ $ACTION = "add" ]
then
#Mount the device into the current namespace to perform some special operations
Insert 1
Do_special
Remove 1
#Mount the device into the namespace
Insert 0
elif [ $ACTION = "remove" ]
then
Remove 0
fi
3.测试前准备
1.修改udev为调试模式,方便在/var/log/syslog中定位日志
2.使规则生效,后期修改规则文件时必须执行udev重启生效,单纯修改usb_handle执行脚本无需重启。
service udev restart
3.借助工具测试规则是否可以匹配
使用udevadm test 模拟测试设备插入事件,查看打印信息确认配置无误。
udevadm test -a add /sys/class/block/sdc1
下图可知规则已被找到
下图可知执行脚本可以被执行
其中/sys/class/block/sdc1是具体的设备,根据实际情况修改即可
3.测试
插入U盘、拔出U盘,可通过df 命令、syslog日志观察到设备是否成功挂载。
下面是/sh/hotplug.sh打印的信息,该脚本需要自行编写
4.后记
Udev挂在设备时,在U16版本前,可以使用mount命令直接挂载,umount卸载,这与嵌入式中mdev是一样的,U16版本之后,则必须使用systemd-mount进行挂载、卸载。否则会因为命名空间的限制而无法被其他空间使用,仅在udev命名空间可见。
挂载命名空间 | 隔离挂载点 | 隔离文件目录 | 进程运行时可以将挂载点与系统分离,使用这个功能时,我们可以达到 chroot 的功能,而在安全性方面比 chroot 更高 |
更多推荐
所有评论(0)