此文章翻译自:Storage Instantiation Daemon (sid-project.github.io)。
强烈推荐看下此文章,不仅对之前的udev内容做了部分补充,更能加强对udev的理解。工作中涉及有udev的道友可以看下原文。
先决条件
udevd内置命令-sid
在udevd和存储实例化守护程序(SID)之间建立一个通信通道,以便能够在两者之间有效地交换信息。
udevd支持实现内置命令,这些命令在udevd启动时初始化,并在udevd停止时完成。这样udevd在处理每个uevent时不需要fork新进程,就像在udev规则中引用外部命令时。
在udev规则中调用内置命令方式与调用任何外部命令非常相似,在规则中使用RUN{builtin}或IMPORT{builtin}即可。
注意:udevd内置命令使用内部模块化接口直接集成到udevd源代码中。udevd不支持外部和动态加载的模块实现此功能。
udevd和SID之间的通道使用本地流和面向连接的socket IPC实现(AF_UNIX/AF_LOCAL socket域,SOCK_STREAM socket类型)。
如果udevd创建了一个新的worker进程,连接将在第一次调用sid内置命令时建立,并保持打开状态,直到worker进程退出(或在超时的情况下被udevd进程终止)。
由于我们使用流和面向连接的通道,接收方(SID)可以检测到对方(worker进程)的断连。这样,SID能够检测到worker进程超时并在意外发生断连时触发后续的回退操作。
sid支持的子命令
- sid active
目的: 检查SID的可用性。
输入: None。
输出: SID_BRIDGE_STATUS=
- active:表示SID的兼容版本正在运行,
- inactive:表示未运行,
- incompatible:表示SID正在运行,但其版本与当前sid udevd内置命令不兼容。
示例:
IMPORT{builtin}="sid active"
ENV{SID_BRIDGE_STATUS}=="active", ...
ENV{SID_BRIDGE_STATUS}=="incompatible", ...
ENV{SID_BRIDGE_STATUS}=="inactive", ...
- sid scan
目的: 执行SID的识别和扫描程序、更新SID数据库、执行相关操作;
输入: 以KEY=VALUE格式表示的udev环境;
输出: 以KEY=VALUE格式表示的环境,其中包含changed或added的项目。
示例:
IMPORT{builtin}="sid scan"
- sid checkpoint
[ ...]
目的: 向SID发送关于匹配checkpoint的消息。
输入: 在匹配udev规则时达到的
输出: None。
注: 安装或启用其他udev规则可以使用checkpoint进行调试,这些规则将在SID执行时执行,以便SID能够跟踪uevent处理的进展。
示例:
PROGRAM{builtin}="sid checkpoint my_checkpoint A B C"
RUN{builtin}="sid checkpoint complete"
- sid version
目的: 返回sid udevd内置命令和SID程序的版本信息。
输入: None。
输出: 一组KEY=VALUE:
- SID_PROTOCOL=
- SID_MAJOR=
- SID_MINOR=
- SID_RELEASE=
- SID_BUILTIN_PROTOCOL=
- SID_BUILTIN_MAJOR=
- SID_BUILTIN_MINOR=
- SID_BUILTIN_RELEASE=
注: 尽管可以在udev规则中调用sid version,但更合理的是将其作为udevadm test-builtin命令的一部分进行调用,用于收集当前系统环境的调试和版本信息。请注意,udevadm test-builtin命令需要给定一个现有设备的sysfs路径作为参数。sid version内置命令不与任何设备绑定,因为它只收集版本信息,所以可以在这里使用任意现有的sysfs路径来满足要求。
示例:
# udevadm test-builtin "sid version" /sys/kernel
SID_PROTOCOL=1
SID_MAJOR=0
SID_MINOR=0
SID_RELEASE=1
SID_BUILTIN_PROTOCOL=1
SID_BUILTIN_MAJOR=0
SID_BUILTIN_MINOR=0
SID_BUILTIN_RELEASE=1
增强synthetic-uevents
在处理uevents时需要考虑并不是所有uevents都是直接在内核中触发的,还有用户空间触发的合成uevents(如将事件写入/sys/.../uevent文件)。为了能够更好地区分synthetic-uevents和kernel-uevents,需要增强synthetic-uevent接口。
- 内核增加支持增强的synthetic-uevents特性
增强synthetic-uevents接口在写入/sys/…/uevent文件时可以传递附加参数。
提议是在事件名称中传递一个唯一标识符,将生成的uevent标记为事务的一部分。该事务可以跨越一个或多个uevents。该标识符将作为环境变量出现在生成的uevent中。如果我们使用一个标识符和多个uevents,我们将它们逻辑上组合在一起 - 在这种情况下,任何读取这些uevents的用户空间进程都可以观察和/或收集组中的所有成员。此外,通过这种方式,可以与合成uevent处理进行同步,并且在继续后续处理之前,我们可以等待组中的所有更改稳定下来。
以前无法等待单个uevent处理完成,只能使用udevadm settle命令等待所有uevent处理完,该命令将等待很多无关的uevents。
为了向后兼容,synthetic-uevents不需要标识符,内核只是自动添加一个值为零的标识符。
注意:使用UUID表示标识符 ,UUID可以使用工具生成(如util-linux工具集的uuidgen)或使用各种库(如util-linux的libuuid或libsystemd的sd-id128)。
用户空间进程触发synthetic-uevents可以追加环境变量,追加的KEY=VALUE环境变量列表需要在标识符之后。另外KEY和VALUE要求必须是字母和数字,每个KEY=VALUE之间用空格隔开。为了避免现有udev环境变量键的名称冲突,写/sys/.../uevent文件时内核将自动在每个KEY前添加SYNTH_。
以下是增强的synthetic-uevent接口使用示例:
# uuid=$(uuidgen)
# echo $uuid
4f60b88c-3052-4daa-8904-2e4efe8563ef
# echo "change 4f60b88c-3052-4daa-8904-2e4efe8563ef A=1 B=abc" > /sys/block/sda/uevent
# 监听输出结果
#udevadm monitor -k -u -p
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:10.0/host32/target32:0:0/32:0:0:0/block/sda
SUBSYSTEM=block
SYNTH_UUID=0b591ea4-10d4-431a-b663-7528c6e9043b
SYNTH_ARG_A=1
SYNTH_ARG_B=abc
sys/.../uevent接口仍然保持向后兼容,可以只写入事件(add\change等)触发,不追加额外的变量仍然可以工作
# echo "change" > /sys/block/sda/uevent
# udevadm monitor --kernel --property
DEVPATH=/devices/pci0000:00/0000:00:08.0/virtio4/host2/target2:0:0/2:0:0:0/block/sda
DEVTYPE=disk
MAJOR=8
MINOR=0
SEQNUM=2028
SUBSYSTEM=block
SYNTH_UUID=0
上面示例中SYNTH_UUID=0是内核自动添加的,即使在写入/sys/.../uevent来生成synthetic-uevent时没有使用额外的参数,仍然可以在处理它们时区分kernel-uevent和synthetic-uevents,synthetic-uevents始终包含SYNTH_UUID键。
注意:增强的synthetic-uevent接口kernel版本4.13才开始支持。
- OPTIONS+="watch"规则增强的synthetic-uevents特性
作为应用OPTIONS+="watch"规则的结果生成的所有synthetic-uevents将包含以下KEY=VALUE对:
SYNTH_UUID="00000000-0000-0000-0000-00000000000"
SYNTH_ARG_UDEV_WATCH="1"
这意味着,每次udev写入/sys/.../uevent文件生成synthetic-uevents时,它将使用以下字符串触发uevent:
change 00000000-0000-0000-0000-00000000000 UDEV_WATCH=1
在基于OPTIONS="watch"规则生成的synthetic-uevents中使用空UUID是因为这里没有任何作用,预留以后使用。
- udevadm trigger增强的synthetic-uevents特性
udevadm trigger调用生成的所有合成uevents将包含以下KEY=VALUE对:
SYNTH_UUID=""
SYNTH_ARG_UDEV_TRIGGER="1"
这意味着,每次udev写入/sys/.../uevent文件生成synthetic-uevents时,将使用此字符串触发uevent:
UDEV_TRIGGER=1
此外,udevadm trigger有个新的参数-wait-uevent,作用是在退出之前等待udev中的所有相关uevent处理(包括所有udev规则处理)完成。
例如,要为所有属于块子系统的设备触发change uevent,并在退出udevadm trigger之前等待udev中的所有相关uevent处理完成,命令如下:
udevadm trigger --subsystem-match block --action change --wait-uevent
要为所有属于块子系统的设备触发change uevent,并使所有生成的uevents在uevent环境中设置UUID为
6cab53e2-b9c9-4c43-9d1d-0d8673fb62b0,命令如下:
udevadm trigger --subsystem-match block --action change --uuid 6cab53e2-b9c9-4c43-9d1d-0d8673fb62b0
- libudev支持增强synthetic-uevents
libudev库包含有生成synthetic-uevent的函数,函数原型如下:
int udev_device_synth_uevent(struct udev_device *udev_device, const char *action, bool wait, unsigned long long timeout);
该接口为udev_device生成类型为action的synthetic-uevent。如果wait为true,函数将在内部设置uevent监视器以等待对应的udev uevent,并在收到uevent或timeout时退出。
使用device_synth_uevent函数生成的所有synthetic-uevents都将包含以下KEY=VALUE对:
SYNTH_UUID=""
SYNTH_ARG_LIBUDEV_TRIGGER="1"
这意味着,每次在此场景中libudev写入/sys/.../uevent文件以生成synthetic-uevents时,将使用此字符串触发uevent:
LIBUDEV_TRIGGER=1
未来可能会提供更多的函数来扩展此功能,并通过libudev库提供类似于udevadm trigger的功能。
最终,各种工具和用户空间组件建议使用这个接口,而不是依赖于OPTIONS+="watch"规则。