udev动态管理Linux内核设备 | 操作系统重启/开机重命名网卡及配置SR-IOV功能

一、udev机制简介

udev是一个通用的内核设备管理器,uusersapce(用户空间)的缩写。udev机制是Linux kernel的设备管理机制,它以守护进程的方式运行于Linux系统,并监听在新设备初始化或设备从系统中移除时,内核通过netlink socket所发出的ueventudev 依赖于内核提供的 uevent 接口,内核在sysfs创建或删除一个设备之后,会通过netlink socket发送uevent通知udevdudevd 是udev 的守护进程,在系统启动时会读取并分析 udev 规则文件提供的所有规则,并保存在内存。udevd守护进程收到sysfs传过来的uevent后,在/dev/目录创建或者删除设备节点。用户态udevd进程根据事件信息匹配不同规则从而进行不同的处理逻辑。

注意:重启udevd命令:systemctl restart systemd-udevd

拓展1】当一个新设备连接到系统时(比如USB驱动器),内核会生成一个uevent,并将其发送给udevdudevd接收到这个消息后将执行以下步骤:

  1. 解析uevent信息:提取出关键信息如设备类型、厂商ID和产品ID等。
  2. 应用规则:根据 /etc/udev/rules.d/目录下定义好的规则文件来决定如何处理该事件。
  3. 创建或删除节点:根据规则文件中定义好操作指令,在/dev目录下创建或删除相应文件节点。
  4. 触发其他操作:可能包括加载模块、设置权限和所有权以及执行自定义脚本等。

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的运行方式

  1. 用户在/etc/udev/rules.d//usr/lib/udev/rules.d目录下提供udev规则文件,说白了,也就是设备节点命名文件。
  2. 内核注册设备驱动后,在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日志等级为infoudev默认等级为err

    可以在udev的配置文件/etc/udev/udev.conf中查看,如果改为 info 或者 debug 的话,会有冗长的 udev 日志被记录下来。日志具体信息可以在syslog中查看。

    udevadm control --log-priority=info

1.4 真正触发udev事件

udevadm trigger 可以真正触发udev事件,但是不会真正改变硬件,只是触发kerneludev的事件,会触发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支持许多不同的命名方案。默认情况下,根据固件、拓扑和位置信息分配固定的名称。这样做的好处是,名称是完全自动的,完全可预测的,即使添加或删除硬件(不需要重新枚举),它们也保持固定,并且可以无缝地替换损坏的硬件。缺点是它们有时比传统使用的ethwla名称更难读。例如: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 规则,随后重启机器即可生效。

拓展3udevadm 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-IOVSingle 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

但是呢,文档里也提示了:

注意:因为额外的 systemdRed 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,果然下面新增了名为ens2v0VF

[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}表示事件行为, 如addremove等, %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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!