您的位置:首页 > 运维架构 > Linux

Linux设备模型(四) uevent

2018-02-28 15:03 162 查看
热插拔事件:在linux系统中,当系统配置发生变化时,如添加kset到系统或移动kobject,一个通知会从内核空间发送到用户空间,这就是热插拔事件。

热插拔事件的产生通常是由在总线驱动程序层的逻辑所控制。

热插拔事件会导致用户空间中的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点等来响应热插拔事件。

比如,当U盘通过USB线缆插入到系统时。热插拔事件会导致对/sbin/hotplug程序的调用,该程序通过加载驱动程序,创建设备节点,挂装分区,或者其他正确的动作来响应。

uevent事件是kobject的一部分,用于在kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。

在上边kset结构体中就有通知事件uevent,设备模型中任何设备状态发生改变时需要上报,会触发uevent提供的接口。

而在Linux系统,可执行文件的执行,依赖于环境变量,环境变量的作用是为执行用户空间程序设置运行环境。

因此
kobj_uevent_env
用于组织此次事件上报时的环境变量,
kset_uevent_ops->uevent
就是设置这个环境变量到用户空间

struct kset_uevent_ops
{
int (*filter)(struct kset *kset, struct kobject *kobj);//事件过滤
const char *(*name)(struct kset *kset, struct kobject *kobj);//返回字符串给用户空间的热插拔程序
int (*uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);//传递热插拔程序的环境变量
};

filter() 函数让 kset 代码决定是否将事件传递给用户空间。用于过滤掉不需要导出到用户空间的事件
name 该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有Kobject将不允许上报uvent
uevent() 函数用于设置用户热插拔处理程序的环境变量


环境变量参数的结构体

#define UEVENT_NUM_ENVP         32 /* number of env pointers */
#define UEVENT_BUFFER_SIZE      2048 /* buffer for the variables */
struct kobj_uevent_env {
char *envp[UEVENT_NUM_ENVP];
int envp_idx;
char buf[UEVENT_BUFFER_SIZE];
int buflen;
};


当内核中打开
CONFIG_HOTPLUG
这个宏,也就是支持热插拔后,会有对应的这几个函数被定义

kobject_uevent
在后边上层容器(设备、驱动、总线)中会被调用到,其中在
kset_register
中就有调用

/*
include/linux/kobject.h
lib/kobject_event.c
*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);

int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)

int kobject_action_type(const char *buf, size_t count, enum kobject_action *type);

kobject_uevent  不添加环境变量的上报
kobject_uevent_env  以envp为环境变量,上报一个指定action的uevent。

add_uevent_var  以格式化字符的形式(类似printf、printk等),将环境变量copy到env指针中。
kobject_action_type  将enum kobject_action类型的Action,转换为字符串。

enum kobject_action {
KOBJ_ADD,
KOBJ_REMOVE,
KOBJ_CHANGE,
KOBJ_MOVE,
KOBJ_ONLINE,
KOBJ_OFFLINE,
KOBJ_MAX
};
ADD/REMOVE      kobject(或上层数据结构)的添加/移除事件。
ONLINE/OFFLINE  kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
CHANGE          kobject(或上层数据结构)的状态或者内容发生改变。
MOVE            kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysfs中更改了目录结构)。
CHANGE          如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数。


代码参考内核源码 lib/kobject_event.c

int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
return kobject_uevent_env(kobj, action, NULL);
}
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
{
...
/* 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;
...

/*
* 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 */
...
#endif
...
/* call uevent_helper, usually only enabled during early boot */
if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
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;
}


显然 uevent 的机制,就是设置环境变量,然后调用用户空间程序 mdev 进行更新设备。

uevent模块准备好上报事件的格式后,在
kobject_uevent
中有两种方法把事件上报到用户空间:

一种是通过kmod模块,直接调用用户空间的可执行文件;

一种是通过netlink通信机制(需要内核使能
CONFIG_NET
),将事件从内核空间传递给用户空间。

这部分源码就在
kobject_uevent
函数里

从源码分析来看,在利用Kmod向用户空间上报event事件时,会调用内核的接口
call_usermodehelper
,配置好环境变量,然后直接执行用户空间的可执行程序 mdev 。

uevent_helper为指定的执行文件路径

总结:

kobject_uevent的工作

1、将 device 的 kobject 的PATH 、name、主次设备号等等设置到环境变量里
2、调用用户空间 mdev,自动创建设备节点


它的作用会在上层容器,device和driver的kset对象中体现比较重要,也正是这个函数接口,会调用mdev自动创建设备节点

对于热插拔事件的调用:

当具体对象有事件发生时,相应的操作函数中(如device_add()),会调用事件消息接口kobject_uevent()。
在该接口中,首先会添加一些共性的消息(路径、子系统名等),然后会回调kset -> uevent_ops -> uevent(kset, kobj, env)来添加该对象特有的事件消息(如device对象的设备号、设备名、驱动名、DT信息)
一切准备完毕,就会通过两种可能的方法向用户空间发送消息并执行相关程序:1.netlink广播;2. uevent_helper程序。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息