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 命令提供了很多有用的信息,但是如果您知道您的拇指驱动器当前位于您的 /dev 树中的哪个位置,您可以使用 udevadm info 命令以更漂亮的格式查看它。如果不是,请拔下并重新插入您的拇指驱动器,然后立即发出以下命令

$ 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"

第一行检测我的 U 盘,使用之前已经讨论过的属性,然后在设备树中为 U 盘分配一个符号链接。它分配的符号链接是 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 的 U 盘,并且我插入了另一个也名为 DISK 的 U 盘,则第二个 U 盘将被标记为 DISK_,这将破坏我的脚本)。它将 safety1(驱动器的第一个分区)挂载到我首选的挂载点 /mnt/hd

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

Udev 是你的 dev

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

本文基于 Slackermedia Handbook 的内容,该手册根据 GNU 自由文档许可证 1.3 授权。

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

6 条评论

echo $date > /tmp/udev.log

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

/bin/date > /tmp/udev.log

Cheerz!

感谢您的提醒,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。创建一个规则,使得当插入一个时,它被分配到您选择的某个标准位置。类似...

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本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.