一、Uevent机制
1.前提摘要
(1)Sysfs文件系统
内核设备模型主要的模块和用户之间能看到的相关部分就是sysfs文件系统了。内核在启动的时候会注册sysfs文件系统,并且在启动系统的初期。通过mount命令挂载sysfs文件系统到/sys挂载点。
Mount -t sysfs sysfs /sys
那么sysfs文件系统的作用是什么呢。概括的说有三点:
1)、建立系统中总线、驱动、设备三者之间的桥梁
2)、像用户空间展示内核中各种设备的拓扑图
3)、提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能。
2)Kobject:Sysfs文件系统中最基本的结构就是kobject,kobject可以代表一个设备,一条总线等。在sys目录下直观的以一个目录表示出来。
(3)Uevent机制
上面的分析其实只是对Linux设备模型做了一些基础性的了解。也就是一个穿针引线的作用,如果要细致了解,需要仔细阅读代码。有了上面对于sysfs的基础。接下来我们来比较详细的了解一下uevent机制。
什么是uevent机制。这个得从热插拔设备开始说起。最简单的一个例子就是U盘了。当我们在计算机上插上一个U盘的时候,系统的USB hub会检测到U盘设备接入,并且完成设备枚举过程(从设备上读出相应的设备信息),并在内核中创建相应的设备结构体。但是,usb设备千奇百态,内核不可能预先将所有usb设备驱动都增加到内存中来。也就是当插入U盘设备的时候,内核中不一定存在对应这个设备的usb驱动。这个时候USB驱动也许以模块的形式保存在硬盘上。载入驱动必然只能从用户态来进行,那这时候应该怎么办呢?
看到这里的时候,有人一定会想,人工敲入命令载入驱动,呵呵。这必然是一种方法,但是是一种很古老的方法。Linux对类似的情况设计了一种uevent的机制。当有新的设备加入的时候,将设备的信息发送消息到用户态。而用户态有一个udev的进程监听这个信息。当收到信息后做一定的解析,根据解析到的结果和用户程序的配置做一些处理,也包括加载驱动程序。
2.具体介绍(Linux设备模型(3)_Uevent)
Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序。
用户空间程序收到这样的事件后,会做相应的处理。
该机制通常是用来支持热拔插设备的,例如U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,
更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。
Uevent在kernel中的位置
由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口。Uevent模块准备好上报事件的格式后。
可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;
另一种是通过netlink通信机制,将事件从内核空间传递给用户空间。
注1:有关kmod和netlink,会在其它文章中描述,因此本文就不再详细说明了。
Uevent的内部逻辑解析
Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:
- include/linux/kobject.h
- lib/kobject_uevent.c
前面有提到过,在利用Kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在Linux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量。
说明:怎么指定处理uevent的用户空间程序(简称uevent helper)?
上面介绍kobject_uevent_env的内部动作时,有提到,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper )处理该event。而该uevent helper的路径保存在uevent_helper数组中。
可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。因此内核编译时,需要把该配置项留空。
在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。这可以通过把helper的路径写入到"/sys/kernel/uevent_helper”文件中实现。实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码,这里不再详细描述。
3.实例分析(Uevent 上报event事件给上层的详细讲解_sunweizhong1024的专栏-CSDN博客_add_uevent_var)headphone_event 上报事件的分析
本文章讲解插入headphone的时候,向上层上报event函数的整个过程
#ifdef CONFIG_I_LOVE_PBJ30
void headphone_event(int state)
{
switch_set_state(&wired_switch_dev, state);
}
EXPORT_SYMBOL_GPL(headphone_event);
#endif
headphone_event 函数会调用switch_set_state函数进行上报事件
接下来会调用kobject_uevent_env函数进行上报事件。
最终调用add_uevent_var()将用户空间需要的参数添加到环境变量中去,如
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;
4.实例分析2
Uevent 是内核通知Android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。
(一)、Kernel侧:UEVENT的发起在Kernel端,主要是通过函数
intkobject_uevent_env(struct kobject*kobj, enum kobject_action action,char*envp_ext[])
该函数的主要功能是根据参数组合一个字符串并发送。一个典型的字符串如下:change@/devices/platform/msm-battery/power_supply/usb纮ACTION=change纮DEVPATH=/devices/platform/msm-battery/power_supply/usb纮SUBSYSTEM=power_supply纮POWER_SUPPLY_NAME=usb纮POWER_SUPPLY_ONLINE=0纮SEQNUM=1486纮
上面这块来自网上,这段内容是否有问题,待考究。
下面看这个函数: int kobject_uevent_env(structkobject *kobj, enum kobject_action action,char *envp_ext[])
static const char *kobject_actions[] ={
[KOBJ_ADD] = "add",
[KOBJ_REMOVE] = "remove",
[KOBJ_CHANGE] = "change",
[KOBJ_MOVE] = "move",
[KOBJ_ONLINE] = "online",
[KOBJ_OFFLINE] = "offline",
};
//以上为kobject标准的动作,调用时需要传入相应的enum值
///以下是获取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;
}
//下面准备要传递的信息数据
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;
//envp_ext[i]是传进来的参数,为该event时携带的一些自定义的信息
if (envp_ext) {
for (i = 0; envp_ext[i]; i++){
retval = add_uevent_var(env, "%s", envp_ext[i]);
if (retval)
goto exit;
//下面通过网络socket将数据发送出去
mutex_lock(&uevent_sock_mutex);
list_for_each_entry(ue_sk,&uevent_sock_list, list) {
struct sock *uevent_sock =ue_sk->sk;
struct sk_buff*skb;
size_t len;
NETLINK_CB(skb).dst_group =1;//下面开始发送数据
retval =netlink_broadcast_filtered(uevent_sock, skb,
0, 1, GFP_KERNEL,
kobj_bcast_filter,
kobj);
}