Linux设备模型3(基于Linux6.6)---uevnet介绍
一、 Uevent概述
Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。
该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。
Uevent
是 Linux 内核中的一种机制,用于在设备发生变化时通知用户空间。这种机制基于内核的设备模型(kobject
),通过 uevent
通知用户空间有关设备状态、配置、属性或事件的信息。Uevent
是设备管理系统的重要组成部分,尤其在 udev
(用户空间设备管理器)中扮演着核心角色。
1.1 Uevent 触发的背景
Uevent
主要在以下几种设备事件发生时触发:
- 设备添加(例如,一个新的硬件设备被插入)
- 设备删除(例如,设备被移除)
- 设备属性更新(例如,设备的配置文件发生变化)
- 设备状态改变(例如,设备的电源状态或连接状态改变)
当这些事件发生时,内核会生成一个 Uevent
,并将它发送到用户空间,通常是通过 udev
来处理和响应这些事件。
1.2 Uevent 工作原理
-
内核生成 Uevent 内核通过
kobject_uevent()
函数生成一个Uevent
,并将其发送到用户空间。这通常是在设备驱动的add
、remove
或change
操作中发生的。 -
Uevent 通知机制 内核会向用户空间发送一个包含设备相关信息的
Uevent
,用户空间可以通过监听这些Uevent
来了解设备状态变化。Uevent 通常包含:- 设备的名称(例如,
/dev/sda
) - 设备的类型(例如,
block
、net
等) - 设备的属性(例如,设备的厂商、型号、驱动等信息)
Uevent 通常以字符串的形式传递,格式类似于:
ACTION=add DEVPATH=/devices/pci0000:00/0000:00:1f.2
- 设备的名称(例如,
-
用户空间处理 Uevent 用户空间通过
udev
(或其他相似工具)接收到Uevent
后,进行相应的处理。udev
负责管理/dev
目录的设备节点,并执行相应的规则或脚本来配置设备。 -
udev 规则
udev
可以根据Uevent
中的信息(如设备类型、ID、路径等)应用不同的规则,执行相应的操作(例如,创建设备节点、加载模块、设置权限等)。
1.3 Uevent 消息格式
Uevent
通常以以下格式发送给用户空间:
ACTION=<action>
DEVPATH=<device-path>
SUBSYSTEM=<subsystem>
DEVNAME=<device-name>
...
常见的字段包括:
-
ACTION: 事件类型,表示设备的状态变化。常见的值有:
add
: 设备添加remove
: 设备移除change
: 设备状态或属性更改
-
DEVPATH: 设备路径,指示设备在
/sys
中的路径。 -
DEVNAME: 设备名称,通常对应
/dev
目录下的设备节点名称(例如sda
、eth0
)。 -
SUBSYSTEM: 子系统,指示设备所属的内核子系统(如
block
、net
、input
等)。 -
PRODUCT: 设备的产品信息(如供应商 ID、产品 ID)。
-
ID_VENDOR: 设备的供应商名称。
-
ID_MODEL: 设备的型号。
1.4 Uevent 与 udev
Uevent
是 udev
的核心基础,udev
监听内核发送的 Uevent
,并根据 Uevent
执行相应的设备管理任务,如:
- 创建设备节点:为新设备在
/dev
目录下创建设备文件。 - 加载驱动程序:根据设备信息加载相应的内核模块。
- 配置权限:为设备设置合适的权限。
- 触发脚本:运行与设备相关的用户自定义脚本。
udev
在内核和用户空间之间提供了设备管理的桥梁,基于 Uevent
,可以动态地响应硬件设备的变化。
1.5 生成 Uevent 的常见操作
-
设备添加:当一个设备被识别并添加到系统时,内核会发送一个
Uevent
,通知用户空间设备的添加事件。 -
设备移除:当一个设备被移除或卸载时,内核会发送一个
Uevent
,通知用户空间设备的删除事件。 -
设备属性更新:设备的属性(如状态、能力)发生变化时,内核会生成
Uevent
,并通知用户空间更新设备的信息。 -
设备状态变化:例如,设备的挂载状态、网络连接状态等发生变化时,也会触发
Uevent
。
1.6 相关的函数和文件
kobject_uevent()
:用于生成Uevent
的内核函数。/sys
目录:设备和驱动的配置信息存储在/sys
目录中,Uevent
也通过该目录传递设备信息。udevadm
工具:用于查询和调试udev
规则的工具。- 例如:
udevadm info --name=/dev/sda
可以显示/dev/sda
的相关信息。
- 例如:
1.7 Uevent 在实际中的应用
- 自动加载驱动模块:
udev
根据设备信息自动加载必要的驱动程序。 - 设备权限管理:
udev
会根据设备信息设置设备文件的权限。 - 创建设备文件:
udev
会在/dev
目录下创建新的设备文件。 - 触发用户空间脚本:
udev
可以配置触发特定的用户空间脚本来执行与设备相关的操作(如格式化磁盘、启动网络接口等)。
二、 Uevent机制
下面图片描述了Uevent模块在内核中的位置:
由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。
Uevent 机制的基本流程如下:
-
内核事件生成:
- 当一个设备的状态发生变化时,内核中的设备模型(
kobject
)会触发一个Uevent
事件。例如,当新硬件设备插入或设备被删除时,内核会调用kobject_uevent()
函数生成一个 Uevent 事件。
- 当一个设备的状态发生变化时,内核中的设备模型(
-
Uevent 事件格式:
- 生成的 Uevent 事件会携带有关设备的详细信息,通常以环境变量的形式表示。这些信息包括但不限于:
ACTION
:设备操作类型(add
、remove
、change
)DEVNAME
:设备名称(例如/dev/sda
)DEVPATH
:设备在/sys
中的路径SUBSYSTEM
:设备所属的内核子系统(如block
、net
)ID_*
:设备的标识符(如ID_VENDOR
、ID_MODEL
)
例如,设备添加事件的 Uevent 消息可能是:
- 生成的 Uevent 事件会携带有关设备的详细信息,通常以环境变量的形式表示。这些信息包括但不限于:
-
ACTION=add DEVPATH=/devices/pci0000:00/0000:00:1f.2 DEVNAME=/dev/sda SUBSYSTEM=block
-
用户空间处理 Uevent:
- 内核将 Uevent 事件通过
uevent
通知机制发送到用户空间。通常,udev
(用户空间设备管理器)会监听这些事件并根据规则对设备进行处理。 udev
会根据 Uevent 中携带的信息执行相应的操作,如创建设备节点、加载驱动程序、设置设备文件权限等。
- 内核将 Uevent 事件通过
-
udev
处理 Uevent:udev
是 Linux 系统中用于设备管理的核心工具,它会监听内核发送的所有 Uevent 事件并执行规则。- 例如,
udev
可以根据 Uevent 中的设备信息来决定:- 是否创建一个新的设备文件(例如
/dev/sda
) - 是否加载相应的内核模块
- 是否设置设备的权限或属性
- 是否触发用户定义的脚本或应用程序
- 是否创建一个新的设备文件(例如
三、 Uevent的内部逻辑解析
3.1、 Source Code位置
Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:
- include/linux/kobject.h
- lib/kobject_uevent.c
3.2、 数据结构描述
kobject.h定义了uevent相关的常量和数据结构,如下:include/linux/kobject.h
/*
* The actions here must match the index to the string array
* in lib/kobject_uevent.c
*
* Do not add new actions here without checking with the driver-core
* maintainers. Action strings are not meant to express subsystem
* or device specific properties. In most cases you want to send a
* kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
* specific variables added to the event environment.
*/
enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_BIND,
KOBJ_UNBIND,
};
以下是每个枚举值的含义:
1. KOBJ_ADD
描述:表示一个新的 kobject 被添加到系统中,通常指设备或对象首次被创建或加入内核管理。
应用场景:设备驱动程序加载时,内核会创建新的 kobject 来代表设备,并且会发出 KOBJ_ADD 事件。
2. KOBJ_REMOVE
描述:表示一个已有的 kobject 被从系统中移除。
应用场景:设备被卸载或删除时,内核会发出 KOBJ_REMOVE 事件来通知设备已经被移除。
3. KOBJ_CHANGE
描述:表示一个已存在的 kobject 的属性发生了变化。
应用场景:当设备的状态或其他属性发生变化时,内核会发出 KOBJ_CHANGE 事件。
4. KOBJ_MOVE
描述:表示一个 kobject 被移动或重新定位。
应用场景:某些内核对象可能需要在不同的父节点下重新排列。例如,设备从一个父设备(或父目录)迁移到另一个父设备时,内核会发出 KOBJ_MOVE 事件。
5. KOBJ_ONLINE
描述:表示一个 kobject 变为在线状态。
应用场景:设备或对象被激活或进入可用状态时,内核会发出 KOBJ_ONLINE 事件。例如,网络接口从 DOWN 状态变为 UP 时,可能会发出这个事件。
6. KOBJ_OFFLINE
描述:表示一个 kobject 变为离线状态。
应用场景:设备或对象被禁用或不再可用时,内核会发出 KOBJ_OFFLINE 事件。例如,设备进入关闭或未激活状态时,可能会发出这个事件。
7. KOBJ_BIND
描述:表示一个 kobject 与某个资源或对象绑定。
应用场景:设备或内核对象可能需要与其他资源(如驱动程序、特定设备等)建立绑定关系时,会发出 KOBJ_BIND 事件。
8. KOBJ_UNBIND
描述:表示一个 kobject 与某个资源或对象解绑。
应用场景:当设备或内核对象不再与某个资源(如驱动程序、设备等)绑定时,内核会发出 KOBJ_UNBIND 事件。
- kobj_uevent_env
include/linux/kobject.h
#define UEVENT_HELPER_PATH_LEN 256
#define UEVENT_NUM_ENVP 64 /* number of env pointers */
#define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
struct kobj_uevent_env {
char *argv[3];
char *envp[UEVENT_NUM_ENVP];
int envp_idx;
char buf[UEVENT_BUFFER_SIZE];
int buflen;
};
前面有提到过,在利用Kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在Linux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量。
envp,指针数组,用于保存每个环境变量的地址,最多可支持的环境变量数量为UEVENT_NUM_ENVP。
envp_idx,用于访问环境变量指针数组的index。
buf,保存环境变量的buffer,最大为UEVENT_BUFFER_SIZE。
buflen,访问buf的变量。
- kset_uevent_ops
struct kset_uevent_ops {
int (* const filter)(struct kset *kset, struct kobject *kobj);
const char *(* const name)(struct kset *kset, struct kobject *kobj);
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
kset_uevent_ops是为kset量身订做的一个数据结构,里面包含filter和uevent两个回调函数,用处如下:
-
filter,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口过滤,阻止不希望上报的event,从而达到从整体上管理的目的。
-
name,该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uvent
-
uevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
3.3、 内部动作
通过kobject.h,uevent模块提供了如下的APIinclude/linux/kobject.h
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp[]);
int kobject_synth_uevent(struct kobject *kobj, const char *buf, size_t count);
__printf(2, 3)
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
static int kobject_action_type(const char *buf, size_t count,
enum kobject_action *type,
const char **args)
lib/kobject_uevent.c
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
{
struct kobj_uevent_env *env;
const char *action_string = kobject_actions[action];
const char *devpath = NULL;
const char *subsystem;
struct kobject *top_kobj;
struct kset *kset;
const struct kset_uevent_ops *uevent_ops;
int i = 0;
int retval = 0;
/*
* Mark "remove" event done regardless of result, for some subsystems
* do not want to re-trigger "remove" event via automatic cleanup.
*/
if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
/* search the kset we belong to 知道到该kobj从属的kset*/
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent; /* 找的方法很简单,若它的kset不存在,则查找其父节点的kset是否存在,不存在则继续查找父父节点.... */
if (!top_kobj->kset) {
pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
"without kset!\n", kobject_name(kobj), kobj,
__func__);
return -EINVAL; /* 最终还没找到就报错 */
}
kset = top_kobj->kset; /* 找到与之相关的kset */
uevent_ops = kset->uevent_ops;
/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) { /* uevent_suppress被置位,则忽略上报uevent */
pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* skip the event, if the filter returns zero. */
if (uevent_ops && uevent_ops->filter) /* 所属的筛选函数存在则筛选,返回0表示被筛掉了,不再上报 */
if (!uevent_ops->filter(kset, kobj)) {
pr_debug("kobject: '%s' (%p): %s: filter function "
"caused the event to drop!\n",
kobject_name(kobj), kobj, __func__);
return 0;
}
/* originating subsystem */
if (uevent_ops && uevent_ops->name)
subsystem = uevent_ops->name(kset, kobj); /* name函数存在,则使用kset返回的kset的name */
else
subsystem = kobject_name(&kset->kobj); /* 否则用kset里kobj的name做kset的name */
if (!subsystem) { /* kset的name不存在,也不允许上报 */
pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
"event to drop!\n", kobject_name(kobj), kobj,
__func__);
return 0;
}
/* environment buffer */
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); /* 分配一个用于此次环境变量的buffer */
if (!env)
return -ENOMEM;
/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL); /* 根据kobj得到它在sysfs中的路径 */
if (!devpath) {
retval = -ENOENT;
goto exit;
}
/* default keys 添加当前要上报的行为,path,name到env的buffer中 */
retval = add_uevent_var(env, "ACTION=%s", action_string);
if (retval)
goto exit;
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
if (retval)
goto exit;
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
if (retval)
goto exit;
/* keys passed in from the caller */
if (envp_ext) { /* 自己传的外部环境变量要不为空,则解析并添加到env的buffer中 */
for (i = 0; envp_ext[i]; i++) {
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
}
}
/* let the kset specific function add its stuff */
if (uevent_ops && uevent_ops->uevent) { /* 如果uevent_ops中的uevent存在,则调用该接口发送该kobj的env */
retval = uevent_ops->uevent(kset, kobj, env);
if (retval) {
pr_debug("kobject: '%s' (%p): %s: uevent() returned "
"%d\n", kobject_name(kobj), kobj,
__func__, retval);
goto exit;
}
}
/*
* Mark "add" and "remove" events in the object to ensure proper
* events to userspace during automatic cleanup. If the object did
* send an "add" event, "remove" will automatically generated by
* the core, if not already done by the caller.
*/
if (action == KOBJ_ADD) /* 如果action是add或remove的话要更新kobj中的state */
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;
mutex_lock(&uevent_sock_mutex);
/* we will send an event, so request a new sequence number */
/* 每次发送一个事件,都要有它的事件号,该事件号不能重复,u64 uevent_seqnum,把它也作为环境变量添加到buffer最后面 */
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
if (retval) {
mutex_unlock(&uevent_sock_mutex);
goto exit;
}
retval = kobject_uevent_net_broadcast(kobj, env, action_string,
devpath);
mutex_unlock(&uevent_sock_mutex);
#ifdef CONFIG_UEVENT_HELPER
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
struct subprocess_info *info;
retval = add_uevent_var(env, "HOME=/");
if (retval)
goto exit;
retval = add_uevent_var(env,
"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
if (retval)
goto exit;
retval = init_uevent_argv(env, subsystem);
if (retval)
goto exit;
retval = -ENOMEM;
info = call_usermodehelper_setup(env->argv[0], env->argv,
env->envp, GFP_KERNEL,
NULL, cleanup_uevent_env, env);
if (info) {
retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
env = NULL; /* freed by cleanup_uevent_env */
}
}
#endif
exit:
kfree(devpath);
kfree(env);
return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);
kobject_uevent_env,以envp为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。具体动作如下:
- 查找kobj本身或者其parent是否从属于某个kset,如果不是,则报错返回(注2:由此可以说明,如果一个kobject没有加入kset,是不允许上报uevent的)
- 查看kobj->uevent_suppress是否设置,如果设置,则忽略所有的uevent上报并返回(注3:由此可知,可以通过Kobject的uevent_suppress标志,管控Kobject的uevent的上报)
- 如果所属的kset有uevent_ops->filter函数,则调用该函数,过滤此次上报(注4:这佐证了3.2小节有关filter接口的说明,kset可以通过filter接口过滤不希望上报的event,从而达到整体的管理效果)
- 判断所属的kset是否有合法的名称(称作subsystem,和前期的内核版本有区别),否则不允许上报uevent
- 分配一个用于此次上报的、存储环境变量的buffer(结果保存在env指针中),并获得该Kobject在sysfs中路径信息(用户空间软件需要依据该路径信息在sysfs中访问它)
- 调用add_uevent_var接口(下面会介绍),将Action、路径信息、subsystem等信息,添加到env指针中
- 如果传入的envp不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
- 如果所属的kset存在uevent_ops->uevent接口,调用该接口,添加kset统一的环境变量到env指针
- 根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
- 调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
- 以uevent_helper、subsystem以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehelper函数,上报uevent。
其中uevent_helper的内容是由内核配置项CONFIG_UEVENT_HELPER_PATH(位于./drivers/base/Kconfig)决定的(可参考lib/kobject_uevent.c, line 32),该配置项指定了一个用户空间程序(或者脚本),用于解析上报的uevent,例如"/sbin/hotplug”。
call_usermodehelper的作用,就是fork一个进程,以uevent为参数,执行uevent_helper。
kobject_uevent,和kobject_uevent_env功能一样,只是没有指定任何的环境变量。
lib/kobject_uevent.c
/**
* kobject_uevent - notify userspace by sending an uevent
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
add_uevent_var,以格式化字符的形式(类似printf、printk等),将环境变量copy到env指针中。
lib/kobject_uevent.c
/**
* add_uevent_var - add key value string to the environment buffer
* @env: environment buffer structure
* @format: printf format for the key=value pair
*
* Returns 0 if environment variable was added successfully or -ENOMEM
* if no space was available.
*/
int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
{
va_list args;
int len;
if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
return -ENOMEM;
}
va_start(args, format);
len = vsnprintf(&env->buf[env->buflen],
sizeof(env->buf) - env->buflen,
format, args);
va_end(args);
if (len >= (sizeof(env->buf) - env->buflen)) {
WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
return -ENOMEM;
}
env->envp[env->envp_idx++] = &env->buf[env->buflen];
env->buflen += len + 1;
return 0;
}
EXPORT_SYMBOL_GPL(add_uevent_var);
kobject_action_type,将enum kobject_action类型的Action,转换为字符串。
lib/kobject_uevent.c
/* the strings here must match the enum in include/linux/kobject.h */
static const char *kobject_actions[] = {
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
[KOBJ_BIND] = "bind",
[KOBJ_UNBIND] = "unbind",
};
/**
* kobject_action_type - translate action string to numeric type
*
* @buf: buffer containing the action string, newline is ignored
* @len: length of buffer
* @type: pointer to the location to store the action type
*
* Returns 0 if the action string was recognized.
*/
static int kobject_action_type(const char *buf, size_t count,
enum kobject_action *type,
const char **args)
{
enum kobject_action action;
size_t count_first;
const char *args_start;
int ret = -EINVAL;
if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
count--;
if (!count)
goto out;
args_start = strnchr(buf, count, ' ');
if (args_start) {
count_first = args_start - buf;
args_start = args_start + 1;
} else
count_first = count;
for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) {
if (strncmp(kobject_actions[action], buf, count_first) != 0)
continue;
if (kobject_actions[action][count_first] != '\0')
continue;
if (args)
*args = args_start;
*type = action; /* 比较成功则数字cation就是对应的action */
ret = 0;
break;
}
out:
return ret;
}
说明:怎么指定处理uevent的用户空间程序(简称uevent helper)?
上面介绍kobject_uevent_env的内部动作时,有提到,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper )处理该event。而该uevent helper的路径保存在uevent_helper数组中。
char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
配置内核.config文件把CONFIG_UEVENT_HELPER_PATH设置为空,则热插拔的路径这个全局数组默认是为空的。
可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。
在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。这可以通过把helper的路径写入到"/sys/kernel/uevent_helper”文件中实现。实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码,这里不再详细描述。
要指定处理 uevent 的用户空间程序(即 uevent helper),你需要配置 /sys
文件系统中的相关设置。具体步骤如下:
-
设置 uevent helper: 可以通过向
/sys/class/net/<interface>/device/uevent_helper
文件写入指定程序的路径来设置 uevent helper。这个路径是一个可执行的程序,当内核触发 uevent 时,会调用这个程序。例如,假设要为某个网络接口设置 uevent helper:
-
echo "/path/to/your/uevent_helper" > /sys/class/net/eth0/device/uevent_helper
-
全局设置 uevent helper: 你还可以设置一个全局的 uevent helper,这通常通过编辑
/etc/udev/udev.conf
配置文件来实现,文件中有一项是UEVENT_HELPER
,你可以设置为你想使用的程序路径: -
UEVENT_HELPER="/path/to/your/uevent_helper"
默认 udev 处理: 如果没有明确设置,
udev
会自动处理所有的 uevent,udev
通常位于/lib/udev/
或/etc/udev/
目录下,默认的 uevent 处理程序就是udevd
。
这些方法允许你为特定设备或全局设备设置指定的 uevent 处理程序。
四、实例分析
Uevent 事件示例,展示了设备状态变化时内核发送的事件信息:
设备添加事件:
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
ID_VENDOR=ATA
ID_MODEL=Samsung_SSD
设备删除事件:
ACTION=remove
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
设备属性变化事件:
ACTION=change
DEVPATH=/devices/pci0000:00/0000:00:1f.2
DEVNAME=/dev/sda
SUBSYSTEM=block
ID_MODEL=Samsung_SSD