myfreax

Linux Udev 规则与设备事件

Udev 是为计算机提供设备事件的 Linux 子系统。简单来说,这意味着它是检测计算机何时插入,删除设备的程序

9 min read
By myfreax
Linux Udev 规则与设备事件
Linux Udev 规则管理设备事件

Udev 是为计算机提供设备事件的 Linux 子系统。简单来说,这意味着它是检测计算机何时插入,删除设备的程序,例如网卡、外部硬盘驱动器(包括 USB 驱动器,U盘)、鼠标、键盘、操纵杆和游戏手柄、DVD-ROM 驱动器等在。

这使得它成为一个潜在有用的程序,并且它的接口暴露程度非常高,普通用户可以手动编写脚本来执行诸如在插入某个硬盘驱动器时执行某些任务之类的事情。

在本教程我们将说明如何在 Linux 创建由 udev 事件触发的 udev 脚本,例如插入指定的 USB 驱动器。

一旦您了解如何使用 udev 的过程,您就可以使用它来执行各种操作,例如在连接游戏手柄时加载特定驱动程序,或者在连接备份驱动器时执行自动备份。

Udev 事件脚本

使用 udev 的最佳方式是分成各个阶段。不要预先编写整个脚本,而是从简单地确认 udev 触发自定义事件开始。

由于编写脚本目标各有不同,但有些时候您可能无法保证您会亲眼看到脚本执行的结果,因此你需要确保您的脚本已成功被触发。

日志文件通常位于 /var 目录,但这是 root 用户的领域。为了进行测试,请使用 /tmp 目录,因此普通用户也可以访问,并且在重新启动后会被删除。

打开您最喜欢的文本编辑器并创建 udev.sh 脚本,在本教程中,我们将使用 vim 编辑器创建脚本 udev.sh :

vim ~/trigger.sh
#!/usr/bin/bash

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

并将其放置在 /usr/local/bin 或者是PATH环境变量指定的某个目录。当然,你还需要使运行 chmod 命令修改脚本使 udev.sh 可执行。

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
2023年 07月 31日 星期一 15:24:08 CST

现在,我们已经成功创建脚本,下一步是让 udev 事件触发脚本。

唯一的设备标识

为了让您的脚本由设备事件触发,udev 必须知道在什么条件下应该触发脚本的执行。在现实生活中,您可以通过 U 盘的颜色、制造商来识别您刚刚将其插入计算机的 U 盘。然而,您的计算机需要一套不同的标准。

udev 子系统是通过序列号、制造商、甚至供应商 ID 和产品 ID 号来识别设备。由于这是 udev 脚本生命周期的早期阶段,因此应尽可能广泛的捕获 Udev 事件。换句话说,您首先需要捕获几乎所有有效的 udev 事件属性来触发您的脚本。

您可以使用 udevadm Monitor 命令实时访问 udev 子系统,这样你就可以查看当您插入不同设备时的 udev 事件信息。

sudo udevadm monitor

udevadm Monitor 监视器的功能是在终端打印接收到的事件:

  • UDEV:Rule(规则) 处理后发送的 udev 事件
  • KERNEL:内核 uevent

udevadm monitor 命令提供许多有用的信息,但您可以使用命令 udevadm info 以更友好的格式查看。

假设您知道 USB 驱动器当前的设备路径。例如 Linux 为 USB 驱动器分配的设备路径是 /dev/sdb ,然后就可以运行以下命令以更友好的格式查看设备事件信息:

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

这会返回很多信息。基本上你只需要关注第一块的信息内容。您需要做的是从 udev 报告中挑选出该设备最独特的部分,然后告诉 udev 在检测到这些独特属性时触发您的脚本。

udevadm 信息进程报告设备信息,然后沿着父设备链向上走。对于找到的每个设备,它都会使用键值格式打印所有可能的属性。您可以根据设备的属性以及单个父设备的属性编写规则进行匹配。

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 规则必须包含来自父设备的一个属性。

父属性是从最基本的级别描述设备的事物,例如它是已插入物理端口的事物,或者是具有大小的事物,或者是可移动设备。

由于 KERNEL 标签 sdb 可能会根据插入的 USB 驱动器数量而发生变化,因此这不是 udev 规则的最佳父属性。

但是,它适用于概念验证,因此您可以使用它。更好的候选者是 SUBSYSTEM 属性,它标识这是一个“块”系统设备(这就是 lsblk 命令列出设备的原因)。

继续使用你喜欢文本编辑器在 /etc/udev/rules.d 目录创建名为 80-local.rules 的文件并填写以下规则内容到文件:

vim /etc/udev/rules.d/80-local.rules
SUBSYSTEM=="block", ACTION=="add", RUN+="/usr/local/bin/trigger.sh"
80-local.rules

保存文件,拔出测试的 USB 驱动器,然后重新启动。

理论上,您可以运行命令 sudo udevadm control --reload 重新载入所有 udev 规则,但在游戏的这个阶段,最好消除所有变量。

Udev 足够复杂,如果您不想整晚躺在床上思考该规则是否由于语法错误而不起作用,或者您是否应该重新启动。因此,无论您的 POSIX 骄傲的告诉您什么,请重新启动。

当您的系统重启后,切换到文本控制台(使用 Ctl+Alt+F3 或类似命令)然后插入 USB 驱动器。

如果您正在运行最新的内核,那么当您插入驱动器时,您可能会在控制台中看到非常多的信息输出。

如果您看到诸如无法执行 /usr/local/bin/trigger.sh 之类的错误消息,则您可能忘记使脚本可执行。

现在,你可以运行 cat 命令查看 /tmp/udev.log 内容:

cat /tmp/udev.log 
2023年 07月 31日 星期一 15:24:08 CST
/tmp/udev.log

如果您从 /tmp/udev.log 看到最近的日期和时间,则说明 udev 已成功触发您自定义的脚本。

将 udev 规则细化

这条规则的问题在于它非常通用。插入鼠标、USB 驱动器或其他人的 USB 驱动器都会触发您的脚本,但不是我们的目标。现在是时候开始关注您想要触发脚本的 USB驱动器。

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

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 表示供应商 ID(idVendor) 和产品 ID(idProduct)。

您也可以在命令 udevadm info -a -n /dev/sdb | grep vendor 的输出中看到这些数字。 但lsusb 命令的输出更容易看懂。

您现在可以将这些属性添加到您的 udev 规则中。

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

你仍然应该重新启动你的计算机,只是为了确保你从 udev 得到新的响应,它应该和以前一样工作。

只是现在如果你插入由不同公司制造的 USB 驱动器或鼠标或打印机,脚本将不会被触发,因为我们指定供应商 idVendor 属性。

不断添加新的属性,直到以你指定的 USB 驱动器才能触发脚本为止你可以继续运行命令 udevadm info -a -n /dev/sdb,查找更多产品属性的信息。

保持您的理智,请确保一次仅添加一个属性。建议不要把一堆属性放入 udev 规则中,否则你会不知道自己的错误在那里。一个一个地测试属性是确保 udev 能够成功识别您的设备的最安全方法。

Udev 规则的安全

udev 规则在插入设备时自动执行某些操作会带来的安全问题。这里请记住两件事。

  1. 一旦你让 udev 规则工作,就需要关注它们,只有在你真正想要的时候才会触发脚本。盲目地执行一个将数据复制到计算机或从计算机数据复制的 USB 驱动器脚本是一个坏主意,你需要提防有人碰巧携带相同品牌的 USB 驱动器将其插入您的计算机。
  2. 不要编写 udev 规则和脚本然后忘记它们。你需要知道哪些计算机上有自定义的 udev 规则,计算机的“社交性”越高,而 udev 规则可能会导致数据最终出现在其他人的设备上,或者其他人的数据或恶意软件出现在你的计算机上。

换句话说,就像 GNU 系统提供的如此多的功能一样,您的工作就是要注意如何运用这些功能。如果你滥用它或不尊重它,它很可能会出现可怕的错误。

结论

Udev 是一个非常灵活的系统,使您能够以其他系统很少的功能,敢于为用户提供的方式定义规则和功能。你需要学习它并使用它,享受 POSIX 带来的力量。