一、udev机制简介
udev
是一个通用的内核设备管理器,u
是usersapce
(用户空间)的缩写。udev
机制是Linux kernel
的设备管理机制,它以守护进程的方式运行于Linux
系统,并监听在新设备初始化或设备从系统中移除时,内核通过netlink socket
所发出的uevent
。udev
依赖于内核提供的 uevent
接口,内核在sysfs
创建或删除一个设备之后,会通过netlink socket
发送uevent
通知udevd
,udevd
是udev
的守护进程,在系统启动时会读取并分析 udev
规则文件提供的所有规则,并保存在内存。udevd
守护进程收到sysfs
传过来的uevent
后,在/dev/
目录创建或者删除设备节点。用户态udevd
进程根据事件信息匹配不同规则从而进行不同的处理逻辑。
注意:重启
udevd
命令:systemctl restart systemd-udevd
【拓展1】当一个新设备连接到系统时(比如
USB
驱动器),内核会生成一个uevent
,并将其发送给udevd
。udevd
接收到这个消息后将执行以下步骤:
解析uevent信息:提取出关键信息如设备类型、厂商 ID
和产品ID
等。应用规则:根据 /etc/udev/rules.d/
目录下定义好的规则文件来决定如何处理该事件。创建或删除节点:根据规则文件中定义好操作指令,在 /dev
目录下创建或删除相应文件节点。触发其他操作:可能包括加载模块、设置权限和所有权以及执行自定义脚本等。
UDEV
规则非常灵活且功能强大,可以匹配几乎任何属性,并且可以运行各种命令来配置每个特定类型的硬件。每条udev
规则都由一系列以逗号分隔符隔开键值对组成;其中有匹配键(用于识别特定事件),赋值键(设置属性),以及操作键(指示要执行哪些命令)。
Linux
内核中的设备信息都是通过 sysfs
文件系统导出的,位于 /sys
目录下。
在CentOS7
及以上系统中使用的是在systemd
中实现的udevd
进程。 udev
规则文件的扩展名为.rules
,主要位于两个目录:
-
/etc/udev/rules.d/
: 自定义规则,即定制的rules
, 优先级高于/usr/lib/udev/rules.d/
,官方建议客户写的rules
都放这里。 -
/usr/lib/udev/rules.d/
: 系统自带规则,udev
默认/预置的rules
。
udev
规则是以规则文件名按字母顺序进行匹配处理的,一般文件名中会带有数字前缀,如:40-redhat.rules
,处理顺序与规则放在哪个目录下无关,但如果不同目录下规则文件同名,/etc/udev/rules.d
下的文件会覆盖/usr/lib/udev/rules.d/
下的文件。
udev
规则语法本身不是很复杂,每条规则由一系列key/value
对儿组成,这些key/value
对可以分为匹配
和赋值
两种。当规则中所有的匹配
都满足时, 赋值
部分的行为被调用。每条规则至少要有一个匹配
和一个赋值
。
【udev的运行方式】
用户在 /etc/udev/rules.d/
或/usr/lib/udev/rules.d
目录下提供udev
规则文件,说白了,也就是设备节点命名文件。内核注册设备驱动后,在 sysfs
子系统中通过netlink socket
发送uevent
数据,应用层的udevd
接收到uevent
数据后进行解析,然后在/etc/udev/rules.d/
或/usr/lib/udev/rules.d
目录下进行检索和匹配,找到设备驱动对应的udev
规则文件,最后根据udev
规则文件要求的命名方式去创建设备节点。
二、udev 管理设备
1、udevadm
udevadm
是一个 udev
管理工具。可用于监视和控制 udev
的运行时行为、请求内核事件、管理事件队列,以及提供简单的调试机制。
1.1 监视正在运行的 udev 守护进程
程序 udevadm monitor
用于将驱动程序核心时间和 udev
事件处理的计时可视化。执行 udevadm monitor
后,会出现如下内容:
[root@localhost ~]# udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
之后发生的所有 udev
事件都会显示。例如下面是生成与移除VF
后显示的内容:
[root@localhost ~]# udevadm monitor
monitor will print the received events for:
UDEV - the event which udev sends out after rule processing
KERNEL - the kernel uevent
KERNEL[1271087.566476] remove /devices/virtual/net/eth1/queues/rx-0 (queues)
KERNEL[1271087.566521] remove /devices/virtual/net/eth1/queues/tx-0 (queues)
KERNEL[1271087.566535] remove /devices/virtual/net/eth1 (net)
UDEV [1271087.567602] remove /devices/virtual/net/eth1/queues/rx-0 (queues)
UDEV [1271087.567850] remove /devices/virtual/net/eth1/queues/tx-0 (queues)
UDEV [1271087.568114] remove /devices/virtual/net/eth1 (net)
KERNEL[1271087.576202] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0/queues/rx-0 (queues)
KERNEL[1271087.576220] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0/queues/tx-0 (queues)
KERNEL[1271087.576258] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0 (net)
UDEV [1271087.577425] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0/queues/rx-0 (queues)
UDEV [1271087.577887] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0/queues/tx-0 (queues)
UDEV [1271087.578266] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0 (net)
KERNEL[1271087.592205] unbind /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
KERNEL[1271087.592272] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
UDEV [1271087.593353] unbind /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
UDEV [1271087.593514] remove /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
KERNEL[1271113.007358] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
KERNEL[1271113.007416] change /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
KERNEL[1271113.007628] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/eth0 (net)
KERNEL[1271113.007638] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/eth0/queues/rx-0 (queues)
KERNEL[1271113.007646] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/eth0/queues/tx-0 (queues)
KERNEL[1271113.007798] bind /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
KERNEL[1271113.007815] change /devices/pci0000:00/0000:00:01.1/0000:01:00.0 (pci)
KERNEL[1271113.007838] add /devices/virtual/net/eth1 (net)
KERNEL[1271113.007847] add /devices/virtual/net/eth1/queues/rx-0 (queues)
KERNEL[1271113.007854] add /devices/virtual/net/eth1/queues/tx-0 (queues)
UDEV [1271113.009314] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
UDEV [1271113.009779] change /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
UDEV [1271113.009941] change /devices/pci0000:00/0000:00:01.1/0000:01:00.0 (pci)
UDEV [1271113.016572] add /devices/virtual/net/eth1 (net)
UDEV [1271113.016942] add /devices/virtual/net/eth1/queues/tx-0 (queues)
UDEV [1271113.016989] add /devices/virtual/net/eth1/queues/rx-0 (queues)
KERNEL[1271113.051731] move /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0 (net)
UDEV [1271113.070080] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0 (net)
UDEV [1271113.070503] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/eth0/queues/tx-0 (queues)
UDEV [1271113.070523] add /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/eth0/queues/rx-0 (queues)
UDEV [1271113.070880] bind /devices/pci0000:00/0000:00:01.1/0000:01:08.0 (pci)
UDEV [1271113.072174] move /devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0 (net)
每行表示一个事件。第一个字段中,KERNEL
表示这是一个内核产生的事件,UDEV
表示 udev
事件。第二个字段是计时,单位是微秒。第三个字段表示事件的动作,add
表示添加, remove
表示移除。最后是 sysfs
文件系统中添加或删除的文件和目录。
1.2 查询设备信息
udevadm info
用于查询 sysfs
文件系统中的设备信息,信息是按照 Linux
设备模型的层次结构显示的,首先是这个设备的路径和信息,然后依次遍历它的父设备。
显示的第一段信息如下:
# -a选项:显示指定设备的所有sysfs记录的属性,以用来udev规则匹配特殊的设备。该选项显示
# 链上的所有设备信息,最大可以到sys目录
# -p选项:查询系统设备的路径
[root@localhost ~]# udevadm info -a -p /sys/class/net/ens2np0
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:01.1/0000:01:00.0/net/ens2np0':
KERNEL=="ens2np0"
SUBSYSTEM=="net"
DRIVER==""
ATTR{addr_assign_type}=="0"
ATTR{addr_len}=="6"
ATTR{address}=="88:3c:c5:a0:18:6e"
ATTR{broadcast}=="ff:ff:ff:ff:ff:ff"
ATTR{carrier}=="1"
ATTR{carrier_changes}=="1"
ATTR{carrier_down_count}=="0"
ATTR{carrier_up_count}=="1"
ATTR{dev_id}=="0x0"
ATTR{dev_port}=="0"
ATTR{dormant}=="0"
ATTR{duplex}=="full"
ATTR{flags}=="0x1003"
ATTR{gro_flush_timeout}=="0"
ATTR{ifalias}==""
ATTR{ifindex}=="7"
ATTR{iflink}=="7"
ATTR{link_mode}=="0"
ATTR{mtu}=="1500"
ATTR{name_assign_type}=="4"
ATTR{napi_defer_hard_irqs}=="0"
ATTR{netdev_group}=="0"
ATTR{operstate}=="up"
ATTR{phys_port_name}=="p0"
ATTR{phys_switch_id}=="883cc5a0186d"
ATTR{proto_down}=="0"
ATTR{speed}=="25000"
ATTR{testing}=="0"
ATTR{threaded}=="0"
ATTR{tx_queue_len}=="1000"
ATTR{type}=="1"
looking at parent device '/devices/pci0000:00/0000:00:01.1/0000:01:00.0':
KERNELS=="0000:01:00.0"
SUBSYSTEMS=="pci"
DRIVERS=="nfp"
ATTRS{ari_enabled}=="1"
ATTRS{broken_parity_status}=="0"
ATTRS{class}=="0x020000"
ATTRS{consistent_dma_mask_bits}=="40"
ATTRS{current_link_speed}=="8.0 GT/s PCIe"
ATTRS{current_link_width}=="8"
ATTRS{d3cold_allowed}=="1"
ATTRS{device}=="0x4000"
ATTRS{dma_mask_bits}=="40"
ATTRS{driver_override}=="(null)"
ATTRS{enable}=="1"
ATTRS{irq}=="117"
ATTRS{local_cpulist}=="0-15,32-47"
ATTRS{local_cpus}=="0000ffff,0000ffff"
ATTRS{max_link_speed}=="8.0 GT/s PCIe"
ATTRS{max_link_width}=="8"
ATTRS{msi_bus}=="1"
ATTRS{numa_node}=="0"
ATTRS{power_state}=="D0"
ATTRS{revision}=="0x00"
ATTRS{sriov_drivers_autoprobe}=="1"
ATTRS{sriov_numvfs}=="1"
ATTRS{sriov_offset}=="64"
ATTRS{sriov_stride}=="1"
ATTRS{sriov_totalvfs}=="55"
ATTRS{sriov_vf_device}=="6003"
ATTRS{sriov_vf_total_msix}=="0"
ATTRS{subsystem_device}=="0x0bf9"
ATTRS{subsystem_vendor}=="0x1da8"
ATTRS{vendor}=="0x1da8"
......
KERNEL
是设备在内核中的名称,SUBSYSTEM
表示它所属的子系统,ATTR{}
表示各种属性。这些字段都会在 udev
规则中用到。
1.3 重新载入规则与设置日志
-
重载
udev rules
,对之后触发的新设备有效,对之前已经触发的无效。udevadm control --relaod-rules
-
更改
udev
日志等级为info
,udev
默认等级为err
。可以在
udev
的配置文件/etc/udev/udev.conf
中查看,如果改为info
或者debug
的话,会有冗长的udev
日志被记录下来。日志具体信息可以在syslog
中查看。udevadm control --log-priority=info
1.4 真正触发udev事件
udevadm trigger
可以真正触发udev
事件,但是不会真正改变硬件,只是触发kernel
和udev
的事件,会触发udev rules
。而 udevadm test
则是模拟向udev
发送设备信息,并不会真的触发,而是将触发后的过程信息打印出来,对调试很有帮助, 我用这个查看脚本是否会被执行。
注意:
test
不会真正触发事件,而trigger
可以。
2、udev 规则
udev
规则可以与内核添加到事件本身的属性或者内核导出到 sysfs
的任何信息匹配。规则还可以从外部程序请求其他信息。根据提供的规则匹配每个事件。所有规则都位于 /etc/udev/rules.d
目录下,按文件名排序依次执行。以“#”
开头的行被视为注释。
规则文件中的每一行至少包含一个关键字值对。有两种类型的关键字,匹配关键字和指派关键字。如果所有匹配关键字与它们的值匹配,则应用此规则并将指派关键字指派给特定的值。匹配规则可以指定设备节点的名称、添加指向该节点的符号链接或者运行作为事件处理一部分的特定程序。如果找不到匹配的规则,则使用默认设备节点名来创建设备节点。udev
手册页中描述了有关规则语法和提供用来与数据匹配或导入数据的关键字的详细信息。
下面这个例子来自 /etc/udev/rules.d/70-persistent-net.rules
。
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="88:3c:c5:a0:18:6e", ATTR{device/sriov_numvfs}="1", NAME="ens2np0"
该规则由六个键构成:四个匹配键 (SUBSYSTEM,ACTION,DRIVERS,ATTR
) 和两个赋值键 (ATTR,NAME
)。四个匹配键搜索设备列表以查找所有的网络设备。只有完全匹配才能触发执行此规则。两个赋值键分别给网络设备设置 VF
个数和命名。
2.1 在 udev 规则中使用运算符
创建可以从若干不同运算符选择的关键字,具体取决于希望创建的关键字类型。匹配关键字通常仅用于查找匹配或明显不匹配搜索值的值。匹配关键字包含以下运算符之一:
-
==
比较等于性。如果关键字包含搜索模式,则匹配该模式的所有结果均有效。 -
!=
比较不等于性。如果关键字包含搜索模式,则匹配该模式的所有结果均有效。
赋值关键字可以使用下面的任何运算符:
-
=
为关键字指派值。如果关键字以前由一列值构成,关键字将重置,并且仅指派一个值。 -
+=
为包含一列项的关键字添加一个值。 -
:=
指派最终值。不允许后面的规则进行任何后续更改。
2.2 模式匹配
可以精确匹配,udev
也可以使用像正则一样的通配符匹配:
-
*
, 匹配任何字符,零次或多次。 -
?
, 匹配前面的字符零次或一次。 -
[]
,匹配括号中指定的任何单个字符。
2.3 在 udev 规则中使用替换项
udev
规则支持使用占位符和替换项使用匹配的结果。请按照在其他任何脚本中的相同方式使用。在 udev
规则中可使用以下替换项:
-
%r、$root
设备目录
/dev
(默认)。 -
%p、$devpath
DEVPATH
的值。 -
%k、$kernel
KERNEL
的值或内部设备名称。 -
%n、$number
设备号。
-
%N、$tempnode
设备文件的临时名称。
-
%M、$major
设备的主编号。
-
%m、$minor
设备的次编号。
-
%s{attribute}/$attr{attribute}
sysfs
属性的值(由attribute
指定)。 -
%E{variable}、$attr{variable}
环境变量的值(由
variable
指定)。 -
%c、$result
PROGRAM
的输出。 -
%%
%
字符。 -
$$
$
字符。
2.4 使用 udev 匹配关键字
匹配关键字描述应用 udev
规则之前必须满足的条件。以下匹配关键字可用:
-
ACTION
事件操作的名称,如
add
或remove
(添加或删除设备时)。 -
DEVPATH
事件设备的设备路径,如
DEVPATH=/sys/bus/pci/drivers/igb
,用于搜索与igb
驱动程序有关的所有事件。 -
KERNEL
事件设备的内部(内核)名称。
-
SUBSYSTEM
事件设备的子系统,如
SUBSYSTEM=net
(用于与NET
设备有关的所有事件)。 -
ATTR{filename}
事件设备的
sysfs
属性。例如,要匹配vendor
属性文件名中包含的字符串,可以使用ATTR{vendor}==“On(sS)tream”
。 -
KERNELS
让
udev
向上搜索设备路径以查找匹配的设备名称。 -
SUBSYSTEMS
让
udev
向上搜索设备路径以查找匹配的设备子系统名称。 -
DRIVERS
让
udev
向上搜索设备路径以查找匹配的设备驱动程序名称。 -
ATTRS{filename}
让
udev
向上搜索设备路径以查找具有匹配的sysfs
属性值的设备。 -
ENV{key}
环境变量的值,如
ENV{ID_BUS}=“ieee1394
,用于搜索与该FireWire
总线ID
有关的所有事件。 -
PROGRAM
让
udev
执行外部程序。程序必须返回退出码零,才能成功。程序的输出(打印到stdout
)可用于RESULT
关键字。 -
RESULT
匹配上次
PROGRAM
调用的输出字符串。在与PROGRAM
关键字相同的规则中包含该关键字,或在后面的一个中。
2.5 使用 udev 指派关键字
与上述匹配键相比,赋值键未描述必须满足的条件。它们将值、名称和操作指派给由 udev
维护的设备节点。
-
NAME
将创建的设备节点的名称。在一个规则设置节点名称之后,将对该节点忽略带有
NAME
关键字的其他所有规则。 -
SYMLINK
与要创建的节点有关的符号链接名称,即创建符号链接。多个匹配的规则可添加要使用设备节点创建的符号链接。也可以通过使用空格字符分隔符号链接名称,在一个规则中为一个节点指定多个符号链接。
-
OWNER, GROUP, MODE
新设备节点的权限。
GROUP
给设备分组,OWNER
给设备分配所有者,MODE
给设备分配读写权限;此处指定的值重写已编译的任何值。 -
ATTR{key}
指定要写入事件设备的
sysfs
属性的值。如果使用==
运算符,也将使用该关键字匹配sysfs
属性的值。 -
PROGRAM
运行外部程序获取结果(
%c
)后给其他分配键使用。 -
ENV{key}
告知
udev
将变量导出到环境。如果使用==
运算符,也将使用该关键字匹配环境变量。 -
RUN
告知
udev
向程序列表添加要为该设备执行的程序,即运行外部脚本。请记住,将此程序限制于很短的任务,以免妨碍此设备的后续事件。 -
LABEL
添加
GOTO
可跳至的标签。 -
GOTO
告知
udev
跳过一些规则,继续执行具有按GOTO
关键字引用的标签的规则。 -
IMPORT{type}
将变量装载入外部程序输出之类的事件环境中。
udev
导入不同类型的若干变量。如果未指定任何类型,udev
将尝试根据文件许可权限的可执行位来自行确定类型。 -
program
告知udev
执行外部程序并导入其输出。 -
file
告知udev
导入文本文件。 -
parent
告知udev
从父设备导入储存的关键字。 -
WAIT_FOR_SYSFS
告知
udev
等待要为某个设备创建的指定sysfs
文件。例如,WAIT_FOR_SYSFS=“ioerr_cnt”
通知udev
等待ioerr_cnt
文件创建完成。 -
OPTIONS
OPTION
关键字可能有若干值: -
last_rule
告知udev
忽略后面的所有规则。 -
ignore_device
告知udev
完全忽略此事件。 -
ignore_remove
告知udev
忽略后面针对设备的所有删除事件。 -
all_partitions
告知udev
为块设备上的所有可用分区创建设备节点。
3、udev 使用的文件
-
/sys/*
Linux
内核提供的虚拟文件系统,用于导出所有当前已知设备。此信息由udev
用于在/dev
中创建设备节点 -
/dev/*
动态创建的设备节点和引导时从
/lib/udev/devices/*
复制的静态内容 -
/etc/udev/udev.conf
主
udev
配置文件。 -
/etc/udev/rules.d/*
udev
事件匹配规则. -
/lib/udev/devices/*
静态
/dev
内容。 -
/lib/udev/*
从
udev
规则调用的帮助程序。
【拓展2】语法可以参考如下链接:
udev(7) — Arch manual pages(https://man.archlinux.org/man/udev.7)
三、udev实战演练
1、使用udev重命名网卡
传统上,Linux
中的网络接口被枚举为eth[0123…]50
,但这些名称不一定与机箱上的实际标签相对应。具有多个网络适配器的现代服务器平台可能会遇到这些接口命名不确定和反直觉的情况。这既影响主板上嵌入的网络适配器(Lan-on-Motherboard
,或LOM
),也影响外接适配器(单端口和多端口)。
在Red Hat Enterprise Linux
中,udev
支持许多不同的命名方案。默认情况下,根据固件、拓扑和位置信息分配固定的名称。这样做的好处是,名称是完全自动的,完全可预测的,即使添加或删除硬件(不需要重新枚举),它们也保持固定,并且可以无缝地替换损坏的硬件。缺点是它们有时比传统使用的eth
或wla
名称更难读。例如:enp5050
。
默认情况下,systemd
将使用以下策略来命名接口,以应用支持的命名方案:
-
方案1:如果固件或
BIOS
提供的信息适用且可用,则应用包含固件或BIOS
提供的板载设备索引号(例如:eno1
)的名称,否则退回到方案2
。 -
方案2:如果来自固件或
BIOS
的信息适用且可用,则应用包含固件或BIOS
提供的PCI Express
热插拔槽索引号(例如:ens1
)的名称,否则退回到方案3
。 -
方案3:如果适用,则应用包含硬件连接器物理位置的名称(例如:
enp2so
),否则在所有其他情况下直接退回到方案5。 -
方案4:包含接口
MAC
地址的名称(例如:enx78e7d1ea46da
),默认情况下不使用,但如果用户选择,可以使用。 -
方案5:传统的不可预测的内核命名方案,如果所有其他方法都失败(例如:
eth0
),则使用该方案。
现在问题来了,如何真正意义上实现按意愿去设置网卡的名称呢?这里有个通用的方法:
编辑/etc/udev/rules.d/70-persistent-net.rules
文件,如果有这个文件,则直接编辑就可以,如果没有就新建一个。然后在文件中按以下的格式输入规则:
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="aa:bb:cc:dd:ee:01", NAME="ethx"
注意:
MAC
地址中的字母,必须是小写,否则可能会无法正确匹配。
只需要根据情况,填写MAC
地址和名字就可以了。另外,针对已有的网络配置,如CentOS
下/etc/sysconfig/network-scripts/
底下的那些ifcfg-xxx
配置文件,也需要针对性的进行修改。修改完成后,执行udevadm control --reload-rules
重新载入所有 udev
规则,随后重启机器即可生效。
【拓展3】
udevadm control --reload-rules
命令会重新加载udev
规则,使系统重新识别您的设备。如果您修改了udev
规则文件,需要使用此命令来重新加载规则文件,使更改生效。另外,您也可以使用以下命令来检查
udev
规则是否正确:udevadm test /path/to/device
,其中,/path/to/device
是您要检查的设备的路径。这个命令会检查udev
规则是否正确,并输出相关的调试信息,可以帮助您找到问题所在。
当然,如果你不确定规则是否正常,怕导致系统异常,不想贸然重启,udevd
会自动检测规则变化, 因而我们不需要重启udevd
。可以使用udevadm test
来检测规则是否正确:
# eth0是待修改的网络接口设备名称
udevadm test /sys/class/net/eth0
查看执行udevadm test
前网络设备接口的名称,如下:
[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether aa:bb:cc:dd:ee:01 brd ff:ff:ff:ff:ff:ff
inet 10.0.20.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0
valid_lft 86222sec preferred_lft 86222sec
inet6 fe80::5054:ff:fe03:15fa/64 scope link
valid_lft forever preferred_lft forever
截取执行结果如下:
[root@localhost ~]# udevadm test /sys/class/net/eth0
calling: test
version 239 (239-44.el8)
This program is for debugging only, it does not run any program
specified by a RUN key. It may show incorrect results, because
some values may be different, or not available at a simulation run.
Load module index
Network interface NamePolicy= disabled on kernel command line, ignoring.
Parsed configuration file /usr/lib/systemd/network/99-default.link
Created link configuration context.
Reading rules file: /usr/lib/udev/rules.d/10-dm.rules
Reading rules file: /usr/lib/udev/rules.d/11-dm-parts.rules
Reading rules file: /usr/lib/udev/rules.d/13-dm-disk.rules
Reading rules file: /usr/lib/udev/rules.d/40-elevator.rules
Reading rules file: /usr/lib/udev/rules.d/40-redhat.rules
...
Reading rules file: /usr/lib/udev/rules.d/65-libwacom.rules
Reading rules file: /usr/lib/udev/rules.d/66-kpartx.rules
Reading rules file: /usr/lib/udev/rules.d/68-del-part-nodes.rules
...
Reading rules file: /usr/lib/udev/rules.d/70-joystick.rules
Reading rules file: /usr/lib/udev/rules.d/70-mouse.rules
Reading rules file: /etc/udev/rules.d/70-persistent-net.rules
Reading rules file: /usr/lib/udev/rules.d/70-power-switch.rules
Reading rules file: /usr/lib/udev/rules.d/70-touchpad.rules
Reading rules file: /usr/lib/udev/rules.d/70-uaccess.rules
...
Reading rules file: /usr/lib/udev/rules.d/99-systemd.rules
Reading rules file: /usr/lib/udev/rules.d/99-vmware-scsi-udev.rules
rules contain 49152 bytes tokens (4096 * 12 bytes), 18760 bytes strings
2665 strings (35207 bytes), 1811 de-duplicated (17302 bytes), 855 trie nodes used
PROGRAM '/lib/udev/rename_device' /usr/lib/udev/rules.d/60-net.rules:1
starting '/lib/udev/rename_device'
Process '/lib/udev/rename_device' succeeded.
NAME 'ethx' /etc/udev/rules.d/70-persistent-net.rules:1
IMPORT builtin 'net_id' /usr/lib/udev/rules.d/75-net-description.rules:6
Using default interface naming scheme 'rhel-8.0'.
...
RUN '/usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/$name --prefix=/net/ipv4/neigh/$name --prefix=/net/ipv6/conf/$name --prefix=/net/ipv6/neigh/$name' /usr/lib/udev/rules.d/99-systemd.rules:60
Error changing net interface name 'eth0' to 'ethx': Device or resource busy
could not rename interface '2' from 'eth0' to 'ethx': Device or resource busy
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:03.0/net/eth0
ID_BUS=pci
ID_MODEL_FROM_DATABASE=82540EM Gigabit Ethernet Controller (PRO/1000 MT Desktop Adapter)
ID_MODEL_ID=0x100e
ID_NET_DRIVER=e1000
...
IFINDEX=2
INTERFACE=eth0
SUBSYSTEM=net
SYSTEMD_ALIAS=/sys/subsystem/net/devices/ethx
TAGS=:systemd:
...
run: '/usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/ethx --prefix=/net/ipv4/neigh/ethx --prefix=/net/ipv6/conf/ethx --prefix=/net/ipv6/neigh/ethx'
Unload module index
Unloaded link configuration context.
根据上面结果中的报错可知,因为当前网络设备接口up
,故无法直接修改名称,将该接口down
掉,则可生效。截取部分执行结果如下:
RUN '/usr/lib/systemd/systemd-sysctl --prefix=/net/ipv4/conf/$name --prefix=/net/ipv4/neigh/$name --prefix=/net/ipv6/conf/$name --prefix=/net/ipv6/neigh/$name' /usr/lib/udev/rules.d/99-systemd.rules:60
renamed network interface 'eth0' to 'ethx'
changed devpath to '/devices/pci0000:00/0000:00:03.0/net/ethx'
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:03.0/net/ethx
ID_BUS=pci
此时,查看网络设备接口是否已修改成功,结果如下:
[root@localhost ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ethx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether aa:bb:cc:dd:ee:01 brd ff:ff:ff:ff:ff:ff
inet 10.0.20.15/24 brd 10.0.2.255 scope global dynamic noprefixroute ethx
valid_lft 86339sec preferred_lft 86339sec
inet6 fe80::ea56:9cd2:f59f:82f4/64 scope link noprefixroute
valid_lft forever preferred_lft forever
可知,网络设备接口eth0
已成功修改为ethx
。
2、使用udev开机设置网卡SR-IOV
在虚拟化场景下,SR-IOV
(Single Root I/O Virtualization
)是一个很常用的功能,通过SR-IOV
,一个物理的设备(Physical Function
),可以派生出很多虚拟设备(Virtual Function
),这些虚拟设备具有简单的 PCIe
功能。以网卡为例,通过SR-IOV
,我们可以将一块网卡,虚拟化成很多块网卡,这些虚拟出来的网卡,有自己独立的 PCIe
地址,中断,配置空间等,这些虚拟出来的网卡,可以作为单独的 PCIe
设备被 attach
到虚拟机中,实现网络功能,当然,场景并不局限于 VM
。
这里先不关注SR-IOV
的应用场景,或者其实现原理,而是关心一个简单的问题:如何设置SR-IOV
,并且能稳定的实现开机启动时就设置好呢?
这里以网卡为例,假设要开启SR-IOV
的网卡名字为ens2np0
,文档里会提示你echo 1 > /sys/class/net/ens2np0/device/sriov_numvfs
。在系统启动完成之后,这么做肯定是没有问题。问题是,在系统启动阶段,这么做是不是可以呢?红帽的文档第 8
章 配置 SR-IOV
网络(https://access.redhat.com/documentation/zh-cn/red_hat_enterprise_linux_openstack_platform/7/html/networking_guide/sec-sr-iov
)通过rc.local
来实现:
# chmod +x /etc/rc.d/rc.local
# echo "echo 1 > /sys/class/net/ens2np0/device/sriov_numvfs" >> /etc/rc.local
但是呢,文档里也提示了:
注意:因为额外的
systemd
,Red Hat Enterprise Linux
将会并行启动服务,而不是依次启动它们。这意味着,rc.local
在引导过程中被执行的位置是不固定的。因此,一些不可预见的情况可能会出现,我们不推荐使用这个方法。
也就是说,因为systemd
的并行特性,可能这么做不一定能获得预期的结果。
同样是这篇文档,还提供了驱动 options
的方式:
[root@compute ~]# echo "options igb max_vfs=7" >>/etc/modprobe.d/igb.conf
然而不巧的是,并不是所有的网卡驱动都支持这个option
,在例子里使用的igb
驱动,这个驱动对应的主要是Intel的千兆网卡芯片比如I350
,而我们使用的是Netronome
系列的网卡,加载的驱动是nfp
,这个驱动并没有对应的max_vfs
参数。于是乎,便想到了使用udev
,通过编写 udev rule
文件,实现开机自动打开SR-IOV
功能。
既然是 udev
,那就需要一些匹配规则了,针对网卡来说,最稳定的匹配规则之一,就是网卡的 MAC 地址,所以,可以确定的是,肯定会根据网卡 MAC 地址去匹配:
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="88:3c:c5:a0:18:6e", NAME="ens2np0"
然后呢,这里仅仅是设置了网卡名字,SR-IOV
怎么开呢?我们先进到网卡的目录看一眼:
[root@localhost ~]# ls /sys/class/net/ens2np0/
addr_assign_type broadcast carrier_down_count dev_id duplex ifalias link_mode napi_defer_hard_irqs phys_port_id power speed testing type
address carrier carrier_up_count dev_port flags ifindex mtu netdev_group phys_port_name proto_down statistics threaded uevent
addr_len carrier_changes device dormant gro_flush_timeout iflink name_assign_type operstate phys_switch_id queues subsystem tx_queue_len
可以这么理解:规则里的ATTR{address}
,相当于/sys/class/net/ens2np0/address
文件,那么也就是说 ATTR
所代表的目录就是/sys/class/net/ens2np0/
,那/sys/class/net/ens2np0/device/sriov_numvfs
这个配置文件不就是ATTR{device/sriov_numvfs}
么?是这样么?是的,没错,那在规则文件里,使用=
号赋值就行了吧:
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="88:3c:c5:a0:18:6e", ATTR{device/sriov_numvfs}="1", NAME="ens2np0"
是不是真的可以呢?可以测试一下,把上面的内容保存到/etc/udev/rules.d/70-persistent-net.rules
:
[root@localhost ~]# cat /etc/udev/rules.d/70-persistent-net.rules
SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="88:3c:c5:a0:18:6e", ATTR{device/sriov_numvfs}="1", NAME="ens2np0"
执行udevadm test /sys/class/net/ens2np0
,从一大堆输出里找到如下内容:
[root@localhost ~]# udevadm test /sys/class/net/ens2np0
...
NAME 'ens2np0' /etc/udev/rules.d/70-persistent-net.rules:1
ATTR '/sys/devices/pci0000:00/0000:00:01.1/0000:01:00.0/net/ens2np0/device/sriov_numvfs' writing '1' /etc/udev/rules.d/70-persistent-net.rules:1
...
表示SR-IOV
功能设置成功。查看/sys/class/net
,果然下面新增了名为ens2v0
的VF
。
[root@localhost ~]# ls -l /sys/class/net
total 0
lrwxrwxrwx 1 root root 0 Dec 6 09:22 br0 -> ../../devices/virtual/net/br0
lrwxrwxrwx 1 root root 0 Dec 6 11:22 eno1 -> ../../devices/pci0000:40/0000:40:01.1/0000:41:00.0/net/eno1
lrwxrwxrwx 1 root root 0 Dec 6 11:22 ens2 -> ../../devices/pci0000:00/0000:00:01.1/0000:01:00.0/net/ens2
lrwxrwxrwx 1 root root 0 Dec 6 11:22 ens2np0 -> ../../devices/pci0000:00/0000:00:01.1/0000:01:00.0/net/ens2np0
lrwxrwxrwx 1 root root 0 Dec 6 11:22 ens2np1 -> ../../devices/pci0000:00/0000:00:01.1/0000:01:00.0/net/ens2np1
lrwxrwxrwx 1 root root 0 Dec 21 02:26 ens2v0 -> ../../devices/pci0000:00/0000:00:01.1/0000:01:08.0/net/ens2v0
lrwxrwxrwx 1 root root 0 Dec 21 02:26 eth1 -> ../../devices/virtual/net/eth1
lrwxrwxrwx 1 root root 0 Dec 6 11:22 eth3 -> ../../devices/virtual/net/eth3
lrwxrwxrwx 1 root root 0 Dec 6 11:21 lo -> ../../devices/virtual/net/lo
lrwxrwxrwx 1 root root 0 Dec 6 09:22 mgmtbr0 -> ../../devices/virtual/net/mgmtbr0
lrwxrwxrwx 1 root root 0 Dec 6 09:22 ovs-system -> ../../devices/virtual/net/ovs-system
lrwxrwxrwx 1 root root 0 Dec 6 09:23 virbr0 -> ../../devices/virtual/net/virbr0
执行udevadm control --reload-rules
重新加载 udev
规则,重启系统试试,依然能工作。问题解决。
3、使用udev执行外部脚本
首先,编写需要调用的脚本:vnic_hook.sh
,放置在 /tmp/
目录,内容如下:
#/bin/bash
echo "ARGS: $@" >> /tmp/vnic_hook.log
【拓展4】外部
.sh
等shell
脚本不执行的原因执行
/usr/bin/my_program
时,udev
环境的各个部分都可用作环境变量,包括SUBSYSTEM
等键值。您还可以使用ACTION
环境变量来检测设备是连接还是断开 –ACTION
将分别是“添加”或“删除”。
udev
不会在任何活动终端上运行这些程序,也不会在shell
的上下文中执行它们。确保您的程序被标记为可执行,如果它是一个shell
脚本,请确保它以适当的shebang
(例如#!/bin/sh
)开头,并且不要期望任何标准输出出现在您的终端上。
为了简要说明, 我们只是简单地输出所有传入的参数到/tmp/vnic_hook.log
中。
接着, 创建规则文件/etc/udev/rules.d/80-vnic.rules
:
SUBSYSTEM=="net", KERNEL=="ethx", RUN+="/tmp/vnic_hook.sh $env{ACTION} %k"
RUN
关键字来运行外部程序,规则中RUN
关键字支持字符串替换,其中$env{ACTION}
表示事件行为, 如add
, remove
等, %k
表示设备的内核名称,这两个作为参数传递给vnic_hook.sh
脚本。细节参考【拓展2】。
使用udevadm trigger
触发udev
事件,使外部脚本运行,但是我们如何知晓是否真的触发udev
事件成功呢?在触发之前,我们先查看一下/tmp/
路径下文件:
[root@localhost ~]# ls -l /tmp/
total 4
drwx------. 3 root root 17 Dec 22 06:15 systemd-private-xxx-chronyd.service-EXipX8
-rwxr-xr-x. 1 root root 53 Dec 22 06:15 vnic_hook.sh
运行 udevadm trigger
触发 udev
事件,之后,我们再查看 /tmp/
路径下文件:
[root@localhost ~]# ls -l /tmp/
total 8
drwx------. 3 root root 17 Dec 22 06:15 systemd-private-xxx-chronyd.service-EXipX8
-rw-r--r--. 1 root root 18 Dec 22 07:15 vnic_hook.log
-rwxr-xr-x. 1 root root 53 Dec 22 06:15 vnic_hook.sh
通过对比触发前后的结果可知,该目录下果然生成了外部脚本中创建的文件,udev
事件确实触发成功了。或者执行udevadm control --reload-rules
重新加载 udev
规则,随后重启系统,发现外部脚本调用成功。
至此,使用 udev
执行外部脚本方法介绍完毕,本文所用脚本较为简单,复杂脚本可自行尝试。
四、总结
udev
可以干的事不止于此,在日常的学习和生产环境中可以帮我们大忙,感兴趣的朋友可以在此基础上自行探索,最后感慨一下:udev
真的是个强大的工具!
原文始发于微信公众号(Linux二进制):udev动态管理Linux内核设备 | 操作系统重启/开机重命名网卡及配置SR-IOV功能
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/302079.html