Udev 简介:用于管理设备事件的 Linux 子系统

创建一个脚本,当插入特定设备时,触发您的计算机执行特定操作。
335 位读者喜欢这篇文章。
A new presciption for open source health care

Opensource.com

Udev 是 Linux 子系统,为您的计算机提供设备事件。简单来说,这意味着它是检测您何时将设备插入计算机的代码,例如网卡、外部硬盘驱动器(包括 USB 闪存驱动器)、鼠标、键盘、操纵杆和游戏手柄、DVD-ROM 驱动器等等。这使其成为一个潜在有用的实用程序,并且它充分暴露,以至于标准用户可以手动编写脚本来执行某些操作,例如在插入特定硬盘驱动器时执行某些任务。

本文教您如何创建一个由某些 udev 事件(例如插入特定的闪存驱动器)触发的 udev 脚本。一旦您了解了使用 udev 的过程,您就可以使用它来完成各种事情,例如在连接游戏手柄时加载特定的驱动程序,或在连接备份驱动器时执行自动备份。

一个基本脚本

使用 udev 的最佳方法是以小块进行。不要预先编写整个脚本,而是从一些简单地确认 udev 触发某些自定义事件的内容开始。

根据您脚本的目标,您无法保证您会亲眼看到脚本的结果,因此请确保您的脚本记录它已成功触发。日志文件的常用位置在 /var 目录中,但这主要是 root 用户的领域。对于测试,请使用 /tmp,普通用户可以访问它,并且通常在重新启动时会被清除。

打开您最喜欢的文本编辑器并输入这个简单的脚本

#!/usr/bin/bash

/usr/bin/date >> /tmp/udev.log

将其放在 /usr/local/bin 或默认可执行路径中的某个位置。将其命名为 trigger.sh,当然,使用 chmod +x 使其可执行。

$ sudo mv trigger.sh /usr/local/bin
$ sudo chmod +x /usr/local/bin/trigger.sh

此脚本与 udev 无关。当它执行时,脚本会将时间戳放在 /tmp/udev.log 文件中。自己测试脚本

$ /usr/local/bin/trigger.sh
$ cat /tmp/udev.log
Tue Oct 31 01:05:28 NZDT 2035

下一步是让 udev 触发脚本。

唯一设备标识

为了使您的脚本由设备事件触发,udev 必须知道在什么条件下它应该调用脚本。在现实生活中,您可以通过颜色、制造商以及您刚刚将其插入计算机的事实来识别闪存驱动器。但是,您的计算机需要一组不同的标准。

Udev 通过序列号、制造商甚至供应商 ID 和产品 ID 号来识别设备。由于这在您的 udev 脚本的早期阶段,因此请尽可能广泛、非特定和包容。换句话说,您首先要捕获几乎所有有效的 udev 事件来触发您的脚本。

使用 udevadm monitor 命令,您可以实时访问 udev,并查看当您插入不同的设备时它看到了什么。成为 root 用户并尝试一下。

$ su
# udevadm monitor

监视器功能打印接收到的事件,用于

  • UDEV:udev 在规则处理后发出的事件
  • KERNEL:内核 uevent

udevadm monitor 运行时,插入闪存驱动器,并观看各种信息喷射到您的屏幕上。请注意,事件的类型是 ADD 事件。这是一个识别您想要的事件类型的好方法。

udevadm monitor 命令提供了很多有用的信息,但是您可以使用命令 udevadm info 以更漂亮的格式查看它,假设您知道您的闪存驱动器当前位于 /dev 树中的哪个位置。如果不知道,请拔下并重新插入您的闪存驱动器,然后立即发出此命令

$ su -c 'dmesg | tail | fgrep -i sd*'

例如,如果该命令返回 sdb: sdb1,您就知道内核已为您的闪存驱动器分配了 sdb 标签。

或者,您可以使用 lsblk 命令查看连接到您系统的所有驱动器,包括它们的大小和分区。

现在您已经确定了您的驱动器在文件系统中的位置,您可以使用此命令查看有关该设备的 udev 信息

# udevadm info -a -n /dev/sdb | less

这将返回大量信息。现在关注第一个信息块。

您的工作是挑出 udev 关于设备的报告中对于该设备最独特的部分,然后告诉 udev 在检测到这些独特属性时触发您的脚本。

udevadm info 进程报告设备(由设备路径指定),然后“向上遍历”父设备链。对于找到的每个设备,它使用键值格式打印所有可能的属性。您可以编写规则以根据设备的属性以及来自单个父设备的属性进行匹配。

looking at device '/devices/000:000/blah/blah//block/sdb':
  KERNEL=="sdb"
  SUBSYSTEM=="block"
  DRIVER==""
  ATTR{ro}=="0" 
  ATTR{size}=="125722368"
  ATTR{stat}==" 2765 1537 5393"
  ATTR{range}=="16"
  ATTR{discard\_alignment}=="0"
  ATTR{removable}=="1"
  ATTR{blah}=="blah"

udev 规则必须包含来自单个父设备的一个属性。

父属性是描述设备最基本级别的事物,例如它是一个已插入物理端口的东西它是一个具有大小的东西这是一个可移动设备

由于 sdb 的 KERNEL 标签可能会根据在您插入该闪存驱动器之前插入了多少其他驱动器而更改,因此这不是 udev 规则的最佳父属性。但是,它适用于概念验证,因此您可以使用它。更好的候选者是 SUBSYSTEM 属性,它标识这是一个“块”系统设备(这就是 lsblk 命令列出该设备的原因)。

/etc/udev/rules.d 中打开一个名为 80-local.rules 的文件,并输入以下代码

SUBSYSTEM=="block", ACTION=="add", RUN+="/usr/local/bin/trigger.sh"

保存文件,拔下您的测试闪存驱动器,然后重新启动。

等等,在 Linux 机器上重新启动

理论上,您只需发出 udevadm control --reload,它应该加载所有规则,但在游戏的这个阶段,最好消除所有变量。Udev 足够复杂,您不想整夜躺在床上想知道该规则是否由于语法错误而不起作用,或者您是否应该重新启动。因此,无论您的 POSIX 自豪感告诉您什么,都要重新启动。

当您的系统重新联机时,切换到文本控制台(使用 Ctl+Alt+F3 或类似组合键),然后插入您的闪存驱动器。如果您运行的是最新的内核,您可能会在插入驱动器时在控制台中看到大量输出。如果您看到错误消息,例如无法执行 /usr/local/bin/trigger.sh,您可能忘记使脚本可执行。否则,希望您看到的只是插入了一个设备,它获得了一些内核设备分配,等等。

现在,真相大白了

$ cat /tmp/udev.log
Tue Oct 31 01:35:28 NZDT 2035

如果您看到从 /tmp/udev.log 返回的非常近期的日期和时间,则 udev 已成功触发您的脚本。

将规则改进为有用的东西

此规则的问题在于它非常通用。插入鼠标、闪存驱动器或其他人的闪存驱动器都会不加区分地触发您的脚本。现在是开始关注您想要触发脚本的确切闪存驱动器的时候了。

一种方法是使用供应商 ID 和产品 ID。要获取这些数字,您可以使用 lsusb 命令。

$ lsusb
Bus 001 Device 002: ID 8087:0024 Slacker Corp. Hub
Bus 002 Device 002: ID 8087:0024 Slacker Corp. Hub 
Bus 003 Device 005: ID 03f0:3307 TyCoon Corp. 
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 hub
Bus 001 Device 003: ID 13d3:5165 SBo Networks

在本例中,TyCoon Corp. 之前的 03f0:3307 表示 idVendor 和 idProduct 属性。您也可以在 udevadm info -a -n /dev/sdb | grep vendor 的输出中看到这些数字,但我发现 lsusb 的输出更赏心悦目。

您现在可以将这些属性包含在您的规则中。

SUBSYSTEM=="block", ATTRS{idVendor}=="03f0", ACTION=="add", RUN+="/usr/local/bin/thumb.sh"

测试一下(是的,您仍然应该重新启动,只是为了确保您从 udev 获得新鲜的反应),它应该像以前一样工作,只是现在如果您插入,例如,由不同公司制造的闪存驱动器(因此具有不同的 idVendor)或鼠标或打印机,脚本将不会被触发。

继续添加新属性,以进一步专注于您想要触发脚本的一个唯一闪存驱动器。使用 udevadm info -a -n /dev/sdb,您可以找出供应商名称、有时是序列号或产品名称等信息。

为了您自己的理智,请务必一次只添加一个新属性。我犯过的大多数错误(以及我在网上看到的其他人犯过的错误)是将一堆属性扔到他们的 udev 规则中,并想知道为什么该规则不再起作用。逐个测试属性是确保 udev 可以成功识别您的设备的最安全方法。

安全

这引发了编写 udev 规则以在插入驱动器时自动执行某些操作的安全性问题。在我的机器上,我甚至没有打开自动挂载,但本文提出了脚本和规则,它们仅通过插入某些东西来执行命令。

这里有两件事要记住。

  1. 一旦您的 udev 规则正常工作,就集中精力使它们仅在您真正想要它们触发脚本时才触发。如果任何碰巧携带相同品牌闪存驱动器的人将其插入您的计算机,则执行盲目地将数据复制到您的计算机或从您的计算机复制数据的脚本是一个坏主意。
  2. 不要编写您的 udev 规则和脚本并忘记它们。我知道哪些计算机上有我的 udev 规则,并且这些计算机通常是我的个人计算机,而不是我带到会议或在工作办公室使用的计算机。“社交性”越强的计算机,就越不可能在其上设置 udev 规则,这可能会导致我的数据最终出现在别人的设备上,或者别人的数据或恶意软件出现在我的设备上。

换句话说,与 GNU 系统提供的许多强大功能一样,您有责任注意如何运用这种力量。如果您滥用它或未能尊重它,它很可能会变得非常糟糕。

现实世界中的 Udev

现在您可以确认您的脚本是由 udev 触发的,您可以将注意力转向脚本的功能。现在,它是无用的,除了记录它已被执行的事实之外什么也不做。

我使用 udev 来触发我的闪存驱动器的 自动备份。其想法是,我的活动文档的主副本位于我的闪存驱动器上(因为它随处可见,并且可能随时进行处理),并且每次我将驱动器插入该计算机时,这些主文档都会备份到我的计算机。换句话说,我的计算机是备份驱动器,而我的生产数据是移动的。源代码是可用的,因此请随意查看 attachup 的代码,以获取有关约束 udev 测试的更多示例。

由于这是我最常使用 udev 的用途,因此这是我将在此处使用的示例,但是 udev 可以获取很多其他东西,例如游戏手柄(这在未设置为在连接游戏手柄时加载 xboxdrv 模块的系统上很有用)以及相机和麦克风(在连接特定麦克风时设置输入很有用),因此请意识到它不仅仅适用于这一个示例。

我的备份系统的简单版本是一个两命令过程

SUBSYSTEM=="block", ATTRS{idVendor}=="03f0", ACTION=="add", SYMLINK+="safety%n"
SUBSYSTEM=="block", ATTRS{idVendor}=="03f0", ACTION=="add", RUN+="/usr/local/bin/trigger.sh"

第一行使用已经讨论过的属性检测我的闪存驱动器,然后在设备树中为闪存驱动器分配一个符号链接。它分配的符号链接是 safety%n。%n 是一个 udev 宏,它解析为内核给设备的任何数字,例如 sdb1、sdb2、sdb3 等等。因此,%n 将是 1 或 2 或 3。

这会在 dev 树中创建一个符号链接,因此它不会干扰插入设备的正常过程。这意味着,如果您使用喜欢自动挂载设备的桌面环境,您将不会为其造成问题。

第二行运行脚本。

我的备份脚本如下所示

#!/usr/bin/bash

mount /dev/safety1 /mnt/hd
sleep 2
rsync -az /mnt/hd/ /home/seth/backups/ && umount /dev/safety1

该脚本使用符号链接,这避免了 udev 将驱动器命名为意外名称的可能性(例如,如果我已经将一个名为 DISK 的闪存驱动器插入了我的计算机,并且我插入了另一个也称为 DISK 的闪存驱动器,则第二个驱动器将被标记为 DISK_,这将破坏我的脚本)。它将 safety1(驱动器的第一个分区)挂载到我首选的挂载点 /mnt/hd

安全挂载后,它使用 rsync 将驱动器备份到我的备份文件夹(我的实际脚本使用 rdiff-backup,您的脚本可以使用您喜欢的任何自动备份解决方案)。

Udev 是您的开发工具

Udev 是一个非常灵活的系统,使您能够以其他系统很少敢于向用户提供的方式定义规则和功能。学习并使用它,享受 POSIX 的强大功能。

本文基于 Slackermedia 手册 的内容构建,该手册已获得 GNU 自由文档许可证 1.3 许可。

标签
Seth Kenlon
Seth Kenlon 是一位 UNIX 极客、自由文化倡导者、独立多媒体艺术家和 D&D 爱好者。他曾在电影和计算行业工作,通常同时进行。

6 条评论

echo $date > /tmp/udev.log

在这里不起作用((Xubuntu 18.04 with bash),最好使用

/bin/date > /tmp/udev.log

干杯!

感谢您的提醒,Lars。您完全正确,我本意是写 $(date),但括号不知何故丢失了。

在编写过程中,该测试脚本从不仅仅返回日期的内容中削减下来。既然它所做的只是返回日期,那么 `echo` 是不必要的。

我已经更新了文章。

很棒的文章。
不过,我想知道的是 Udev 和 Systemd 的 .device 单元之间的当前关系。它们是 Udev 规则的替代品吗?
谢谢!

好问题,Osqui。Systemd .device 单元可以执行相同的任务,这是我的下一篇文章的主题(旨在作为本文的补充)。

但是,udev 仍然很重要,原因有两个:首先,并非所有 Linux 发行版都附带 systemd,其次,因为有些 Systemd 看不到的东西 udev 可以看到,因此您必须使用 udev 标签来提示 Systemd 执行操作。

鉴于这两个因素,我不清楚 Systemd 是否打算最终取代 udev,或者只是提供实现许多相同结果的另一种手段。我并不总是喜欢 udev(我仍然不相信它实际上能够在不重新启动的情况下重新加载其规则),但我不想完全失去它,因为我的 Slackware 系统没有运行 Systemd。

回复 作者: Osqui (未验证)

很棒的文章!

也许您可以阐明这个与 udev 相关的问题

我过去经常摆弄 arduinos 和其他 usb 编程板。其中一些需要拔下并重新插入才能进入“闪烁模式”。

但是当我重新插入它们时,udev 会为符号链接分配一个新名称,从 /dev/ttyUSB0 切换到 /dev/ttyUSB1(例如)。然后我的 IDE 找不到我需要重新配置的板。

我认为这与超时和旧链接的重用有关,但我不确定如何解决它。

有什么想法吗?

我不确定我是如何错过这条评论的,抱歉回复晚了。

我想尝试的是(不确定它是否会起作用,因为我实际上没有测试过)创建一个 udev 规则,该规则可以通过一些相当通用的属性来识别 arduinos。创建一个规则,以便在插入一个 arduino 时,将其分配到您选择的某个标准位置。类似...

ACTION=="add", YOUR-RULES-TO-DETECT-THE-ARDUINO RUN+="/usr/bin/mkdir /dev/arduino"
ACTION=="remove", YOUR-RULES-TO-DETECT-THE-ARDUINO RUN+="/usr/bin/rmdir /dev/arduino"

不确定它是否会起作用,但值得一试。

回复 作者: Alfonso E.M. (未验证)

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 获得许可。
© . All rights reserved.