标签 udev 下的文章

使用 udev 管理你的 Linux 系统处理物理设备的方式。

 title=

Linux 能够出色地自动识别、加载、并公开接入的无数厂商的硬件设备。事实上,很多年以前,正是这个特性说服我,坚持让我的雇主将整个基础设施转换到 Linux。痛点在于 Redmond 的某家公司(LCTT 译注:指微软)不能在我们的 Compaq 台式机上加载集成网卡的驱动,而 Linux 可以轻松实现这一点。

从那以后的岁月里,Linux 的识别设备库随着该过程的复杂化而与日俱增,而 udev 就是解决这个问题的希望之星。udev 负责监听 Linux 内核发出的改变设备状态的事件。它可能是一个新 USB 设备被插入或拔出,也可能是一个无线鼠标因浸入洒出的咖啡中而脱机。

udev 负责处理所有的状态变更,比如指定访问设备使用的名称和权限。这些更改的记录可以通过 dmesg 获取。由于 dmesg 的输出通常有几千行,对结果进行过滤通常是聪明的选择。下面的例子说明了 Linux 如何识别我的 WiFi 接口。这个例子展示了我的无线设备使用的芯片组(ath9k)、启动过程早期阶段分配的原始名称(wlan0)、以及正在使用的又臭又长的永久名称(wlxec086b1ef0b3):

$ dmesg | grep wlan
[    5.396874] ath9k_htc 1-3:1.0 wlxec086b1ef0b3: renamed from wlan0

在这篇文章中,我会讨论为何有人想要使用这样的名称。在这个过程中,我会探索剖析 udev 的配置文件,然后展示如何更改 udev 的设置,包括编辑系统命名设备的方式。这篇文件基于我的新课程中《Linux 系统优化》的一个模块。

理解 udev 配置系统

使用 systemd 的机器上,udev 操作由 systemd-udevd 守护进程管理,你可以通过常规的 systemd 方式使用 systemctl status systemd-udevd 检查 udev 守护进程的状态。

严格来说,udev 的工作方式是试图将它收到的每个系统事件与 /lib/udev/rules.d//etc/udev/rules.d/ 目录下找到的规则集进行匹配。规则文件包括匹配键和分配键,可用的匹配键包括 actionnamesubsystem。这意味着如果探测到一个属于某个子系统的、带有特定名称的设备,就会给设备指定一个预设的配置。

接着,“分配”键值对被拿来应用想要的配置。例如,你可以给设备分配一个新名称、将其关联到文件系统中的一个符号链接、或者限制为只能由特定的所有者或组访问。这是从我的工作站摘出的一条规则:

$ cat /lib/udev/rules.d/73-usb-net-by-mac.rules
# Use MAC based names for network interfaces which are directly or indirectly
# on USB and have an universally administered (stable) MAC address (second bit
# is 0). Don't do this when ifnames is disabled via kernel command line or
# customizing/disabling 99-default.link (or previously 80-net-setup-link.rules).

IMPORT{cmdline}="net.ifnames"
ENV{net.ifnames}=="0", GOTO="usb_net_by_mac_end"

ACTION=="add", SUBSYSTEM=="net", SUBSYSTEMS=="usb", NAME=="", \
    ATTR{address}=="?[014589cd]:*", \
    TEST!="/etc/udev/rules.d/80-net-setup-link.rules", \
    TEST!="/etc/systemd/network/99-default.link", \
    IMPORT{builtin}="net_id", NAME="$env{ID_NET_NAME_MAC}"

add 动作告诉 udev,只要新插入的设备属于网络子系统,并且是一个 USB 设备,就执行操作。此外,如果我理解正确的话,只有设备的 MAC 地址由特定范围内的字符组成,并且 80-net-setup-link.rules99-default.link 文件存在时,规则才会生效。

假定所有的条件都满足,接口 ID 会改变以匹配设备的 MAC 地址。还记得之前的 dmesg 信息显示我的接口名称从 wlan0 改成了讨厌的 wlxec086b1ef0b3 吗?那都是这条规则的功劳。我怎么知道?因为 ec:08:6b:1e:f0:b3 是设备的 MAC 地址(不包括冒号)。

$ ifconfig -a
wlxec086b1ef0b3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.0.103  netmask 255.255.255.0  broadcast 192.168.0.255
        inet6 fe80::7484:3120:c6a3:e3d1  prefixlen 64  scopeid 0x20<link>
        ether ec:08:6b:1e:f0:b3  txqueuelen 1000  (Ethernet)
        RX packets 682098  bytes 714517869 (714.5 MB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 472448  bytes 201773965 (201.7 MB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Linux 默认包含这条 udev 规则,我不需要自己写。但是为什么费力进行这样的命名呢——尤其是看到这样的接口命名这么难使用后?仔细看一下包含在规则中的注释:

对直接或间接插入在 USB 上的网络接口使用基于 MAC 的名称,并且用一个普遍提供的(稳定的)MAC 地址(第二位是 0)。当 ifnames 通过内核命令行或 customizing/disabling 99-default.link(或之前的 80-net-setup-link.rules)被禁用时,不要这样做。

注意,这个规则专为基于 USB 的网络接口设计的。和 PCI 网络接口卡(NIC)不同,USB 设备很可能时不时地被移除或者替换,这意味着无法保证它们的 ID 不变。某一天 ID 可能是 wlan0,第二天却变成了 wlan3。为了避免迷惑应用程序,指定绝对 ID 给设备——就像分配给我的 USB 接口的 ID。

操作 udev 的设置

下一个示例中,我将从 VirtualBox 虚拟机里抓取以太网接口的 MAC 地址和当前接口 ID,然后用这些信息创建一个改变接口 ID 的 udev 新规则。为什么这么做?也许我打算从命令行操作设备,需要输入那么长的名称让人十分烦恼。下面是工作原理。

改变接口 ID 之前,我需要关闭 Netplan 当前的网络配置,促使 Linux 使用新的配置。下面是 /etc/netplan/ 目录下我的当前网络接口配置文件:

$ less /etc/netplan/50-cloud-init.yaml
# This file is generated from information provided by
# the datasource.  Changes to it will not persist across an instance.
# To disable cloud-init's network configuration capabilities, write a file
# /etc/cloud/cloud.cfg.d/99-disable-network-config.cfg with the following:
# network: {config: disabled}
network:
    ethernets:
        enp0s3:
            addresses: []
            dhcp4: true
    version: 2

50-cloud-init.yaml 文件包含一个非常基本的接口定义,但是注释中也包含一些禁用配置的重要信息。为此,我将移动到 /etc/cloud/cloud.cfg.d 目录,创建一个名为 /etc/cloud/cloud.cfg.d 的新文件,插入 network: {config: disabled} 字符串。

尽管我只在 Ubuntu 发行版上测试了这个方法,但它应该在任何一个带有 systemd 的 Linux(几乎所有的 Linux 发行版都有 systemd)上都可以工作。不管你使用哪个,都可以很好地了解编写 udev 配置文件并对其进行测试。

接下来,我需要收集一些系统信息。执行 ip 命令,显示我的以太网接口名为 enp0s3,MAC 地址是 08:00:27:1d:28:10

$ ip a
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 08:00:27:1d:28:10 brd ff:ff:ff:ff:ff:ff
    inet 192.168.0.115/24 brd 192.168.0.255 scope global dynamic enp0s3

现在,我要在 /etc/udev/rules.d 目录创建一个名为 peristent-net.rules 的新文件。我将给文件一个以较小的数字开头的名称,比如 10:

$ cat /etc/udev/rules.d/10-persistent-network.rules
ACTION=="add", SUBSYSTEM=="net",ATTR{address}=="08:00:27:1d:28:10",NAME="eth3"

数字越小,Linux 越早执行文件,我想要这个文件早点执行。文件被添加时,包含其中的代码就会分配名称 eth3 给网络设备——只要设备的地址能够匹配 08:00:27:1d:28:10,即我的接口的 MAC 地址 。

保存文件并重启计算机后,我的新接口名应该就会生效。我可能需要直接登录虚拟机,使用 dhclient 手动让 Linux 为这个新命名的网络请求一个 IP 地址。在执行下列命令前,可能无法打开 SSH 会话:

$ sudo dhclient eth3

大功告成。现在你能够促使 udev 控制计算机按照你想要的方式指向一个网卡,但更重要的是,你已经有了一些工具,可以弄清楚如何管理任何不听话的设备。


via: https://opensource.com/article/20/2/linux-systemd-udevd

作者:David Clinton 选题:lujun9972 译者:YungeG 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

创建这样一个脚本,当指定的设备插入时触发你的计算机去做一个指定动作。

udev 是一个为你的计算机提供设备事件的 Linux 子系统。通俗来讲就是,当你的计算机上插入了像网卡、外置硬盘(包括 U 盘)、鼠标、键盘、游戏操纵杆和手柄、DVD-ROM 驱动器等等设备时,代码能够检测到它们。这样就能写出很多可能非常有用的实用程序,而它已经很好了,普通用户就可以写出脚本去做一些事情,比如当某个硬盘驱动器插入时,执行某个任务。

这篇文章教你去如何写一个由一些 udev 事件触发的 udev 脚本,比如插入了一个 U 盘。当你理解了 udev 的工作原理,你就可以用它去做各种事情,比如当一个游戏手柄连接后加载一个指定的驱动程序,或者当你用于备份的驱动器连接后,自动执行备份工作。

一个初级的脚本

使用 udev 的最佳方式是从一个小的代码块开始。不要指望从一开始就写出完整的脚本,而是从最简单的确认 udev 触发了某些指定的事件开始。

对于你的脚本,依据你的目标,并不是在任何情况下都能保证你亲眼看到你的脚本运行结果的,因此需要在你的脚本日志中确认它成功触发了。而日志文件通常放在 /var 目录下,但那个目录通常是 root 用户的领地。对于测试目的,可以使用 /tmp,它可以被普通用户访问并且在重启动后就被清除了。

打开你喜欢的文本编辑器,然后输入下面的简单脚本:

#!/usr/bin/bash

echo $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 必须要知道在什么情况下调用该脚本。在现实中,你可以通过它的颜色、制造商、以及插入到你的计算机这一事实来识别一个 U 盘。而你的计算机,它需要一系列不同的标准。

udev 通过序列号、制造商、以及提供商 ID 和产品 ID 号来识别设备。由于现在你的 udev 脚本还处于它的生命周期的早期阶段,因此要尽可能地宽泛、非特定和包容。换句话说就是,你希望首先去捕获尽可能多的有效 udev 事件来触发你的脚本。

使用 udevadm monitor 命令你可以实时利用 udev,并且可以看到当你插入不同设备时发生了什么。用 root 权限试一试。

$ su
# udevadm monitor

该监视函数输出接收到的事件:

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

udevadm monitor 命令运行时,插入一个 U 盘,你将看到各种信息在你的屏幕上滚动而出。注意那一个 ADD 事件的事件类型。这是你所需要的识别事件类型的一个好方法。

udevadm monitor 命令提供了许多很好的信息,但是你可以使用 udevadm info 命令以更好看的格式来看到它,假如你知道你的 U 盘当前已经位于你的 /dev 树。如果不在这个树下,拔下它并重新插入,然后立即运行这个命令:

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

举例来说,如果那个命令返回 sdb: sdb1,说明内核已经给你的 U 盘分配了 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 规则必须包含来自单个父级设备的一个属性。

父级属性是描述一个设备的最基本的东西,比如它是插入到一个物理端口的东西、或是一个容量多大的东西、或这是一个可移除的设备。

由于 KERNEL 卷标 sdb 可能会由于分配给在它之前插入的其它驱动器而发生变化,因此卷标并不是一个 udev 规则的父级属性的好选择。但是,在做概念论证时你可以使用它。一个事件的最佳候选者是 SUBSYSTEM 属性,它表示那个设备是一个 “block” 系统设备(也就是为什么我们要使用 lsblk 命令来列出设备的原因)。

/etc/udev/rules.d 目录中打开一个名为 80-local.rules 的文件,然后输入如下代码:

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

保存文件,拔下你的测试 U 盘,然后重启动系统。

等等,重启动 Linux 机器?

理论上说,你只需要运行 udevadm control —reload 即可,它将重新加载所有规则,但是在我们实验的现阶段,最好要排除可能影响实验结果的所有因素。udev 是非常复杂的,为了不让你躺在床上整晚都在思考为什么这个规则不能正常工作,是因为语法错误吗?还是应该重启动一下。所以,不管 POSIX 自负地告诉你过什么,你都应该去重启动一下。

当你的系统重启动完毕之后,(使用 Ctl+Alt+F3 或类似快捷键)切换到一个文本控制台,并插入你的 U 盘。如果你运行了一个最新的内核,当你插入 U 盘后你或许可以看到一大堆输出。如果看到一个错误信息,比如 “Could not execute /usr/local/bin/trigger.sh”,或许是因为你忘了授予这个脚本可运行的权限。否则你将看到的是,一个设备插入,它得到内核设备分配的一些东西,等等。

现在,见证奇迹的时刻到了。

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

如果你在 /tmp/udev.log 中看到了最新的日期和时间,那么说明 udev 已经成功触发了你的脚本。

改进规则做一些有用的事情

现在的问题是使用的规则太通用了。插入一个鼠标、一个 U 盘、或某个人的 U 盘都将盲目地触发这个脚本。现在,我们开始专注于希望触发你的脚本的是确定的某个 U 盘。

实现上述目标的一种方式是使用提供商 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 就表示了提供商 ID 和产品 ID 的属性。你也可以通过 udevadm info -a -n /dev/sdb | grep vendor 的输出来查看这些数字,但是从 lsusb 的输出中可以很容易地一眼找到这些数字。

现在,可以在你的脚本中包含这些属性了。

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

测试它(是的,为了确保不会有来自 udev 的影响因素,我们仍然建议先重新启动一下),它应该会像前面一样工作,现在,如果你插入一个不同公司制造的 U 盘(因为它们的提供商 ID 不一样)、或插入一个鼠标、或插入一个打印机,这个脚本将不会被触发。

继续添加新属性来进一步专注于你希望去触发你的脚本的那个唯一的 U 盘。使用 udevadm info -a -n /dev/sdb 命令,你可以找出像提供商名字、序列号、或产品名这样的东西。

为了保证思路清晰,确保每次只添加一个新属性。我们(和在网上看到的其他人)在 udev 规则中所遇到的大多数错误都是因为一次添加了太多的属性,而奇怪为什么不能正常工作了。逐个测试属性是最安全的作法,这样可以确保 udev 能够成功识别到你的设备。

安全

编写 udev 规则当插入一个驱动器后自动去做一些事情,将带来安全方面的担忧。在我的机器上,我甚至都没有打开自动挂载功能,而基于本文的目的,当设备插入时,脚本和规则可以运行一些命令来做一些事情。

在这里需要记住两个事情。

  1. 聚焦于你的 udev 规则,当你真实地使用它们时,一旦让规则发挥作用将触发脚本。执行一个脚本去盲目地复制数据到你的计算上,或从你的计算机上复制出数据,是一个很糟糕的主意,因为有可能会遇到一个人拿着和你相同品牌的 U 盘插入到你的机器上的情况。
  2. 不要在写了 udev 规则和脚本后忘记了它们的存在。我知道哪个计算上有我的 udev 规则,这些机器一般是我的个人计算机,而不是那些我带着去开会或办公室工作的计算机。一台计算机的 “社交” 程度越高,它就越不能有 udev 规则存在于它上面,因为它将潜在地导致我的数据最终可能会出现在某个人的设备、或某个人的数据中、或在我的设备上出现恶意程序。

换句话说就是,随着一个 GNU 系统提供了一个这么强大的功能,你的任务是小心地如何使用它们的强大功能。如果你滥用它或不小心谨慎地使用它,最终将让你出问题,它非常可能会导致可怕的问题。

现实中的 Udev

现在,你可以确认你的脚本是由 udev 触发的,那么,可以将你的关注点转到脚本功能上了。到目前为止,这个脚本是没有用的,它除了记录脚本已经运行过了这一事实外,再没有做更多的事情。

我使用 udev 去触发我的 U 盘的 自动备份 。这个创意是,将我正在处理的文档的主副本保存在我的 U 盘上(因为我随身带着它,这样就可以随时处理它),并且在我每次将 U 盘插入到那台机器上时,这些主文档将备份回我的计算机上。换句话说就是,我的计算机是备份驱动器,而产生的数据是移动的。源代码是可用的,你可以随意查看 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 盘已经插入到我的计算机上,而我插入的其它 U 盘恰好名字也是 DISK,那么第二个 U 盘的卷标将被命名为 DISK_,这将导致我的脚本不会正常运行),它在我喜欢的挂载点 /mnt/hd 上挂载了 safety1(驱动器的第一个分区)。

一旦 safely 挂载之后,它将使用 rsync 将驱动器备份到我的备份文件夹(我真实使用的脚本用的是 rdiff-backup,而你可以使用任何一个你喜欢的自动备份解决方案)。

udev 让你的设备你做主

udev 是一个非常灵活的系统,它可以让你用其它系统很少敢提供给用户的方式去定义规则和功能。学习它,使用它,去享受 POSIX 的强大吧。

本文内容来自 Slackermedia Handbook,它以 GNU Free Documentation License 1.3 许可证授权使用。


via: https://opensource.com/article/18/11/udev

作者:Seth Kenlon 选题:lujun9972 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

读者对象

理解 udev 背后的基本概念,学习如何写简单的规则。

要求

  • root 权限

难度

中等

约定

  • # - 要求给定的命令使用 root 权限或者直接以一个 root 用户或者使用 sudo 命令去运行。
  • $ - 要求给定的命令以一个普通的非特权用户运行。

介绍

在 GNU/Linux 系统中,虽然设备的底层支持是在内核层面处理的,但是,它们相关的事件管理是在用户空间中通过 udev 来管理的。确切地说是由 udevd 守护进程来完成的。学习如何去写规则,并应用到发生的这些事件上,将有助于我们修改系统的行为并使它适合我们的需要。

规则如何组织

udev 规则是定义在一个以 .rules 为扩展名的文件中。那些文件主要放在两个位置:/usr/lib/udev/rules.d,这个目录用于存放系统安装的规则;/etc/udev/rules.d/ 这个目录是保留给自定义规则的。

定义那些规则的文件的命名惯例是使用一个数字作为前缀(比如,50-udev-default.rules),并且以它们在目录中的词汇顺序进行处理的。在 /etc/udev/rules.d 中安装的文件,会覆盖安装在系统默认路径中的同名文件。

规则语法

如果你理解了 udev 规则的行为逻辑,它的语法并不复杂。一个规则由两个主要的节构成:match 部分,它使用一系列用逗号分隔的键定义了规则应用的条件,而 action 部分,是当条件满足时,我们执行一些动作。

测试案例

讲解可能的选项的最好方法莫过于配置一个真实的案例,因此,我们去定义一个规则作为演示,当鼠标被连接时禁用触摸板。显然,在该规则定义中提供的属性将反映我的硬件。

我们将在 /etc/udev/rules.d/99-togglemouse.rules 文件中用我们喜欢的文本编辑器来写我们的规则。一条规则定义允许跨多个行,但是,如果是这种情况,必须在一个换行字符之前使用一个反斜线(\)表示行的延续,就和 shell 脚本一样。这是我们的规则:

ACTION=="add" \
, ATTRS{idProduct}=="c52f" \
, ATTRS{idVendor}=="046d" \
, ENV{DISPLAY}=":0" \
, ENV{XAUTHORITY}="/run/user/1000/gdm/Xauthority" \
, RUN+="/usr/bin/xinput --disable 16"

我们来分析一下这个规则。

操作符

首先,对已经使用以及将要使用的操作符解释如下:

== 和 != 操作符

== 是相等操作符,而 != 是不等于操作符。通过使用它们,我们可以确认规则上应用的键是否匹配各自的值。

分配操作符 = 和 :=

= 是赋值操作符,是用于为一个键赋值。当我们想要赋值,并且想确保它不会被其它规则所覆盖,我们就需要使用 := 操作符来代替,使用这个操作符分配的值,它就不能被改变。

+= 和 -= 操作符

+=-= 操作符各自用于从一个指定的键定义的值列表中增加或者移除一个值。

我们使用的键

现在,来分析一下在这个规则中我们使用的键。首先,我们有一个 ACTION 键:通过使用它,当在一个设备上发生了特定的事件,我们将指定我们要应用的规则的具体内容。有效的值有 addremove 以及 change

然后,我们使用 ATTRS 关键字去指定一个属性去匹配。我们可以使用 udevadm info 命令去列出一个设备属性,提供它的名字或者 sysfs 路径即可:

udevadm info -ap /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39':
    KERNEL=="input39"
    SUBSYSTEM=="input"
    DRIVER==""
    ATTR{name}=="Logitech USB Receiver"
    ATTR{phys}=="usb-0000:00:1d.0-1.2/input1"
    ATTR{properties}=="0"
    ATTR{uniq}==""

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010':
    KERNELS=="0003:046D:C52F.0010"
    SUBSYSTEMS=="hid"
    DRIVERS=="hid-generic"
    ATTRS{country}=="00"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1':
    KERNELS=="2-1.2:1.1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usbhid"
    ATTRS{authorized}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceClass}=="03"
    ATTRS{bInterfaceNumber}=="01"
    ATTRS{bInterfaceProtocol}=="00"
    ATTRS{bInterfaceSubClass}=="00"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{supports_autosuspend}=="1"

  looking at parent device '/devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2':
    KERNELS=="2-1.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{authorized}=="1"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bDeviceClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bMaxPacketSize0}=="8"
    ATTRS{bMaxPower}=="98mA"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bcdDevice}=="3000"
    ATTRS{bmAttributes}=="a0"
    ATTRS{busnum}=="2"
    ATTRS{configuration}=="RQR30.00_B0009"
    ATTRS{devnum}=="12"
    ATTRS{devpath}=="1.2"
    ATTRS{idProduct}=="c52f"
    ATTRS{idVendor}=="046d"
    ATTRS{ltm_capable}=="no"
    ATTRS{manufacturer}=="Logitech"
    ATTRS{maxchild}=="0"
    ATTRS{product}=="USB Receiver"
    ATTRS{quirks}=="0x0"
    ATTRS{removable}=="removable"
    ATTRS{speed}=="12"
    ATTRS{urbnum}=="1401"
    ATTRS{version}==" 2.00"

    [...]

上面截取了运行这个命令之后的输出的一部分。正如你从它的输出中看到的那样,udevadm 从我们提供的指定路径开始,并且提供了所有父级设备的信息。注意设备的属性都是以单数的形式报告的(比如,KERNEL),而它的父级是以复数形式出现的(比如,KERNELS)。父级信息可以做为规则的一部分,但是同一时间只能有一个父级可以被引用:不同父级设备的属性混合在一起是不能工作的。在上面我们定义的规则中,我们使用了一个父级设备属性:idProductidVendor

在我们的规则中接下来做的事情是,去使用 ENV 关键字:它既可以用于设置也可以用于去匹配环境变量。我们给 DISPLAYXAUTHORITY 分配值。当我们使用 X 服务器程序进行交互去设置一些需要的信息时,这些变量是非常必要的:使用 DISPLAY 变量,我们指定服务器运行在哪个机器上,用的是哪个显示和屏幕;使用 XAUTHORITY 提供了一个文件路径,其包含了 Xorg 认证和授权信息。这个文件一般位于用户的家目录中。

最后,我们使用了 RUN 字:它用于运行外部程序。非常重要:这里没有立即运行,但是一旦所有的规则被解析,将运行各种动作。在这个案例中,我们使用 xinput 实用程序去改变触摸板的状态。我不想解释这里的 xinput 的语法,它超出了本文的范围,只需要注意这个触摸板的 ID 是 16

规则设置完成之后,我们可以通过使用 udevadm test 命令去调试它。这个命令对调试非常有用,它并不真实去运行 RUN 指定的命令:

$ udevadm test --action="add" /devices/pci0000:00/0000:00:1d.0/usb2/2-1/2-1.2/2-1.2:1.1/0003:046D:C52F.0010/input/input39

我们提供给命令的是使用 --action 选项,以及设备的 sysfs 路径的模拟动作。如果没有报告错误,说明我们的规则运行的很好。要在真实的环境中去使用它,我们需要重新加载规则:

# udevadm control --reload

这个命令将重新加载规则文件,但是,它只对重新加载之后发生的事件有效果。

我们通过创建一个 udev 规则了解了基本的概念和逻辑,这只是 udev 规则中众多的选项和可能的设置中的一小部分。udev 手册页提供了一个详尽的列表,如果你想深入了解,请参考它。


via: https://linuxconfig.org/tutorial-on-how-to-write-basic-udev-rules-in-linux

作者:Egidio Docile 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

提问:当我尝试在 Linux 中运行 USB GPS 接收器时我遇到了下面来自 gpsd 的错误。

gpsd[377]: gpsd:ERROR: read-only device open failed: Permission denied
gpsd[377]: gpsd:ERROR: /dev/ttyUSB0: device activation failed.
gpsd[377]: gpsd:ERROR: device open failed: Permission denied - retrying read-only 

看上去 gpsd 没有权限访问 USB 设备(/dev/ttyUSB0)。我该如何永久修改它在Linux上的权限?

当你在运行一个会读取或者写入USB设备的进程时,进程的用户/组必须有权限这么做才行。当然你可以手动用chmod命令改变 USB 设备的权限,但是手动的权限改变只是暂时的。USB 设备会在下次重启时恢复它的默认权限。

作为一个永久的方式,你可以创建一个基于 udev 的 USB 权限规则,它可以根据你的选择分配任何权限模式。下面是该如何做。

首先,你需要找出 USB 设备的 vendorID 和 productID。使用lsusb命令。

$ lsusb -vvv 

上面lsusb的输出中,找出你的 USB 设备,并找出"idVendor"和"idProduct"字段。本例中,我们的结果是idVendor (0x067b)idProduct (0x2303)

下面创建一个新的udev规则。

$ sudo vi /etc/udev/rules.d/50-myusb.rules 

SUBSYSTEMS=="usb", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", GROUP="users", MODE="0666"

用你自己的"idVendor"和"idProduct"来替换。MODE="0666"表示USB设备的权限。

现在重启电脑并重新加载 udev 规则:

$ sudo udevadm control --reload 

接着验证下 USB 设备的权限。


via: http://ask.xmodulo.com/change-usb-device-permission-linux.html

作者:Dan Nanni 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出