使用udev规则创建USB设备挂载点映射
0x00 为何要设置USB设备别名
随着我们开发的机器人具备的功能越来越复杂,那么需要外接的USB设备会逐渐多起来,例如:外接两个arduino板,外接两个USB摄像头,外接一个激光雷达,外接一个USB接口的IMU模块等等,那么如此多的USB设备在linux中的挂载点就会多起来,而且慢慢的也会变得较为混乱,导致我们无法分清楚哪一个设备挂载点对应哪一个设备,而且即使我们在本次开机中分清楚了,那么在下次linux系统开机后,USB设备的挂载点也会随着系统挂载设备顺序的不同而导致设备挂载点发生变化。如下图所示,当外接了很多USB设备时会导致无法分清楚挂载点与哪一个设备对应:
从上图无法得知设备挂载点/dev/ttyACM0和/dev/ttyACM1哪一个挂载点对应哪一个arduino设备,ttyUSB0和ttyUSB1对应哪一个设备也不确定,因此,我们就需要一种方法来保证每次开机后唯一的设备挂载点对应一个确定的设备,这样我们的程序就可以正确操控相应的USB设备了。
0x01 什么是udev?
udev 是 Linux 内核的设备管理器。总的来说,它取代了 devfs 和 hotplug,负责管理 /dev 中的设备节点。同时,udev 也处理所有用户空间发生的硬件添加、删除事件,以及某些特定设备所需的固件加载。与传统的顺序加载相比,udev 通过并行加载内核模块提供了潜在的性能优势。异步加载模块的方式也有一个天生的缺点:无法保证每次加载模块的顺序,如果机器具有多个块设备,那么它们的设备节点可能随机变化。
udev 规则以管理员身份编写并保存在 /etc/udev/rules.d/ 目录,其文件名必须以 .rules 结尾,各种软件包提供的规则文件位于 /lib/udev/rules.d/。如果 /usr/lib 和 /etc 这两个目录中有同名文件,则 /etc 中的文件优先,关于udev更为详细的介绍,大家可以参考以下链接:udev介绍
0x02 编写udev规则
在简单了解了什么是udev后,我们就可以来编写属于自己USB设备的udev规则了,这样我们就可以为自己的USB设备挂载点来重命名,由于完整的介绍编写udev规则太过于复杂,我们这里来通过大家经常用到的rplidar_ros里面的rplidar的udev规则来做举例说明,如果已经下载过rplidar_ros代码的就不用重新下载,如果没有下载的可以通过以下命令来下载:
git clone https://github.com/robopeak/rplidar_ros.git
当下载完该代码后,我们查看目录结构可以发现一个scripts的目录,里面就放着rplidar相关的udev规则,具体操作如下图所示:
我们先来看rplidar.rules的内容,然后来分析其编写规则:
在规则文件里,除了以“#”开头的行(注释),所有的非空行都被视为一条规则,但是一条规则不能扩展到多行。规则都是由多个 键值对(key-value pairs)组成,并由逗号隔开,键值对可以分为 条件匹配键值对( 以下简称“匹配键 ”) 和 赋值键值对( 以下简称“赋值键 ”),一条规则可以有多条匹配键和多条赋值键。匹配键是匹配一个设备属性的所有条件,当一个设备的属性匹配了该规则里所有的匹配键,就认为这条规则生效,然后按照赋值键的内容,执行该规则的赋值。
在rplidar.rules中的KERNEL,ATTRS{idVendor}和ATTRS{idProduct}是匹配键,MODE和SYMLINK是赋值键,这条规则的意思是:如果有一个设备的内核设备名称是ttyUSB*(*代表任意数字),VID是10c4,PID是ea60的话,那么就在/dev目录下增加一个符号链接设备并命名为rplidar,同时为其赋予0777的权限。
通过这条简单的规则,大家应该对 udev 规则有直观的了解。但可能会产生疑惑,为什么 KERNEL,ATTRS{idVendor}和ATTRS{idProduct} 是匹配键,而 MODE和SYMLINK是赋值键呢?这由中间的操作符 (operator) 决定。
仅当操作符是“==”或者“!=”时,其为匹配键;若为其他操作符时,都是赋值键,下面是 udev 规则的所有操作符:
“==”:比较键、值,若等于,则该条件满足;
“!=”: 比较键、值,若不等于,则该条件满足;
“=”: 对一个键赋值;
“+=”:为一个表示多个条目的键赋值。
“:=”:对一个键赋值,并拒绝之后所有对该键的改动。目的是防止后面的规则文件对该键赋值。
0x03 使udev规则生效
当我们编写好属于自己的udev规则后,接下来就需要将其复制到指定位置,然后使其生效了,这里我们可以参考这两个脚本如何编写的,首先看create_udev_rules.sh:
#!/bin/bash echo "remap the device serial port(ttyUSBX) to rplidar" echo "rplidar usb connection as /dev/rplidar , check it using the command : ls -l /dev|grep ttyUSB" echo "start copy rplidar.rules to /etc/udev/rules.d/" echo "`rospack find rplidar_ros`/scripts/rplidar.rules" sudo cp `rospack find rplidar_ros`/scripts/rplidar.rules /etc/udev/rules.d echo " " echo "Restarting udev" echo "" sudo service udev reload sudo service udev restart echo "finish "
接下来看删除规则的脚本内容,主要就是将复制到rules.d目录下的rplidar.rules规则文件删除,然后重启udev服务:
#!/bin/bash echo "delete remap the device serial port(ttyUSBX) to rplidar" echo "sudo rm /etc/udev/rules.d/rplidar.rules" sudo rm /etc/udev/rules.d/rplidar.rules echo " " echo "Restarting udev" echo "" sudo service udev reload sudo service udev restart echo "finish delete"
然后我们先来执行create_udev_rules.sh脚本使规则生效,由于该脚本默认情况下需要将rplidar_ros代码放在ROS的工作空间源码目录下,使用catkin_make编译后,然后source devel/setup.bash,然后才能执行该脚本,我在这里将脚本简单修改,这样就没必要在ROS工作空间的源码目录下执行了,我修改后的脚本如下:
#!/bin/bash echo "remap the device serial port(ttyUSBX) to rplidar" echo "rplidar usb connection as /dev/rplidar , check it using the command : ls -l /dev|grep ttyUSB" echo "start copy rplidar.rules to /etc/udev/rules.d/" sudo cp ./rplidar.rules /etc/udev/rules.d echo " " echo "Restarting udev" echo "" sudo service udev reload sudo service udev restart echo "finish "
当我们不想再使用rplidar的udev规则时就可以使用delete_udev_rules.sh脚本将规则文件删除,这样就不会再有相应的rplidar映射了:
如果不想在每次创建udev规则和删除规则时,重新的插拔USB设备,那么可以将脚本文件做如下修改就可以了,首先来看create_udev_rules.sh如何修改:
#!/bin/bash echo "remap the device serial port(ttyUSBX) to rplidar" echo "rplidar usb connection as /dev/rplidar , check it using the command : ls -l /dev|grep ttyUSB" echo "start copy rplidar.rules to /etc/udev/rules.d/" sudo cp ./rplidar.rules /etc/udev/rules.d echo " " echo "Restarting udev" echo "" sudo udevadm control --reload-rules sudo service udev restart sudo udevadm trigger echo "finish "
同理可得,修改后的delete_udev_rules.sh脚本内容如下:
#!/bin/bash echo "delete remap the device serial port(ttyUSBX) to rplidar" echo "sudo rm /etc/udev/rules.d/rplidar.rules" sudo rm /etc/udev/rules.d/rplidar.rules echo " " echo "Restarting udev" echo "" sudo udevadm control --reload-rules sudo service udev restart sudo udevadm trigger echo "finish delete"
0x04 当PID/VID相同时如何编写udev规则
当主控板上外接的USB设备越來越多时,难免遇到两个或多个USB设备的VID,PID相同,这样的话再通过上述那个简单的rules文件就不行了,我们就需要增加新的匹配键来做区分不同的USB设备:
此时我们就需要在rplidar.rules文件中新增KERNELS匹配键,这样才能与IMU模块的usb转接板区分开,获取KERNELS的方式如下,我们先把其他USB设备拔掉,只留下RPlidar A2设备,然后执行以下命令:
udevadm info --attribute-walk --name=/dev/ttyUSB1 | grep KERNELS
然后就可以根据该KERNELS属性来修改rplidar.rules内容了,这样我们就可以使用该udev规则文件来创建映射了,新rplidar.rules内容如下:
# set the udev rule , make the device_port be fixed by rplidar # KERNELS=="1-2.1", KERNEL=="ttyUSB*", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE:="0777", SYMLINK+="rplidar"
同理可得,我们可以创建IMU模块的imu.rules文件,内容如下:
# set the udev rule , make the device_port be fixed by rplidar # KERNELS=="1-2.4", KERNEL=="ttyUSB*", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE:="0777", SYMLINK+="imu"
同时我们还可以创建和imu模块规则文件配套的脚本文件,具体操作如下图所示:
那执行两个生效的脚本来查看效果:
0x05 注意事项
1.使用udev规则来创建设备挂载点新的映射时,需要注意不能随意更换USB设备插的USB口了,因为每次更换USB口,那么相应的KERNELS就换了,切记!我们只要不更换USB设备插的USB口,那么无论开机时设备挂载顺序如何,即使rplidar a2的挂载点这次开机是ttyUSB0,下次开机变成ttyUSB1也不要紧,因为/dev/rplidar总能正确的创建相应的映射到rplidar的挂载点上。
2.在执行上面的各种命令时需要在ubuntu的终端里执行,不可以在windows下使用类似xShell的终端模拟软件来运行。