Uevent是一种在内核空间和用户空间之间通信的机制,主要用于热插拔事件(hotplug)。

uevent事件

根据include/linux/kobject.h中的定义,kobject对应的动作可分为以下几种:

enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};

这些动作对应的事件相应有以下几种:

static const char *kobject_actions[] = {
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
};

uevent环境变量

Uevent事件具体以环境变量(即字符串)的形式发送到用户空间。

struct kobj_uevent_env {
char *envp[UEVENT_NUM_ENVP]; /*环境变量指针数组*/
int envp_idx; /*数组下标*/
char buf[UEVENT_BUFFER_SIZE];
int buflen;
};

发送事件方法

在内核中通过kobject_uevent_env函数将事件发送到用户空间,但是实际上发送事件的动作由调用函数kobject_uevent_env实现。

/**
* kobject_uevent - notify userspace by ending 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);
}
EXPORT_SYMBOL_GPL(kobject_uevent);

kobject_uevent_env函数主要做了两方面的工作:

1 获取整理了与即将发送的事件相关的环境变量,如ACTION、DEVPATH和SUBSYSTE等。
2 发送事件到用户空间。

/**
* kobject_uevent_env - send an uevent with environmental data
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
* @envp_ext: pointer to environmental data
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
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;
struct kset_uevent_ops *uevent_ops;
u64 seq;
int i = 0;
int retval = 0;

pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);

/* search the kset we belong to */
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;

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;
uevent_ops = kset->uevent_ops;

/* skip the event, if uevent_suppress is set*/
if (kobj->uevent_suppress) {
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)
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);
else
subsystem = kobject_name(&kset->kobj);
if (!subsystem) {
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);
if (!env)
return -ENOMEM;

/* complete object path */
devpath = kobject_get_path(kobj, GFP_KERNEL);
if (!devpath) {
retval = -ENOENT;
goto exit;
}

/* default keys */
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) {
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) {
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)
kobj->state_add_uevent_sent = 1;
else if (action == KOBJ_REMOVE)
kobj->state_remove_uevent_sent = 1;

/* we will send an event, so request a new sequence number */
spin_lock(&sequence_lock);
seq = ++uevent_seqnum;
spin_unlock(&sequence_lock);
retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
if (retval)
goto exit;

#if defined(CONFIG_NET)
/* send netlink message */
if (uevent_sock) {
struct sk_buff *skb;
size_t len;

/* allocate message with the maximum possible size */
len = strlen(action_string) + strlen(devpath) + 2;
skb = alloc_skb(len + env->buflen, GFP_KERNEL);
if (skb) {
char *scratch;

/* add header */
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);

/* copy keys to our continuous event payload buffer */
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}

NETLINK_CB(skb).dst_group = 1;
retval = netlink_broadcast(uevent_sock, skb, 0, 1,
GFP_KERNEL);
/* ENOBUFS should be handled in userspace */
if (retval == -ENOBUFS)
retval = 0;
} else
retval = -ENOMEM;
}
#endif

/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0]) {
char *argv [3];

argv [0] = uevent_helper;
argv [1] = (char *)subsystem;
argv [2] = NULL;
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 = call_usermodehelper(argv[0], argv,
env->envp, UMH_WAIT_EXEC);
}

exit:
kfree(devpath);
kfree(env);
return retval;
}
EXPORT_SYMBOL_GPL(kobject_uevent_env);

第29~41行,查询当前kobject所属的kset。
第44~49行,如果设置了kobj->uevent_suppress(==1),则跳过该事件。
第50~57行,如果可以过滤该事件也跳过该事件。

至此,既然确定事件可以被发送,接下来设置要发送的环境变量:
第60~69行,获取subsystem。
第77~81行,获取devpath。
第84~92行,根据形参和获取的subsystem和devpath的值设置默认的环境变量 - ACTION、DEVPATH和SUBSYSTEM。
第95~101行,添加调用函数传递的其他环境变量。

即将发送事件给用户空间,那么需要标记该动作表示事件已(其实是即将)发送:
第120~123行,如果是添加事件则为kobj->state_add_uevent_sent赋值1;如果是移除事件则为kobj->state_remove_uevent_sent赋值1。

第126~131行,为即将发送的事件分配一个数字标识SEQNUM。

通过以上的准备工作环境变量均设置完毕,接下来就是将这些环境变量发送到用户空间。发送的方法可分为两种:Netlink socket和执行uevent_helper。
现在先分析Netlink socket方法。实现该方法的前提条件是定义了CONFIG_NET,然后将要发送的信息(message)存放到网络结构sk_buff中,最后调用netlink_broadcast函数广播到用户空间。这部分的源码见133~165行。
如果未定义CONFIG_NET,则可通过call_usermodehelper执行uevent_helper(缺省值是“/sbin/hotplug“)命令发送事件到用户空间。

参考
​​​Linux kernel – Uevent发送(热插拔)事件到用户空间​