uevent总结


1,Android UEventObserver
http://blog.itpub.net/7232789/viewspace-733265
UEventObserver是android Java层利用uevent与获取Kernel层状态变化的机制。


通过grep发现framework有如下模块使用UEventObserver的功能来提供服务:
电池状态:services/java/com/android/server/BatteryService.java
耳机状态:services/java/com/android/server/HeadsetObserver.java
DOCK状态:services/java/com/android/server/DockObserver.java
USB状态:services/java/com/android/server/usb/UsbService.java


它们全部继承自UEventObserver,先看看这个类的构造和原理:
./core/java/android/os/UEventObserver.java
                              |
          [ native_setup(), next_event() ]
                             \|/
./core/jni/android_os_UEventObserver.cpp
                              |
          [ uevent_init(),uevent_next_event() ]
                             \|/
/hardware/libhardware_legacy/uevent/uevent.c
                              |                                                                                         [userspace]
---------------------[socket]-----------------------------------------------------------------------------
                              |
                             \|/                                                                                           [kernel]
     socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)
     
2,Android Uevent 分析,从kernel到framework
http://blog.csdn.net/goldfighter/article/details/7458996
Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。
一、Kernel侧:
UEVENT的发起在Kernel端,主要是通过函数
int kobject_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纮
首先,准备各个字符串:
1.准备字符串
1)获取action字符串
*action_string = kobject_actions[action];
Action为KOBJ_ADD等,kobject_actions的定义如下:
static const char *kobject_actions[] = {
       [KOBJ_ADD] =            "add",
       [KOBJ_REMOVE] =           "remove",
       [KOBJ_CHANGE] =            "change",
       [KOBJ_MOVE] =         "move",
       [KOBJ_ONLINE] =             "online",
       [KOBJ_OFFLINE] =     "offline",
};
2)获取subsystem字符串
subsystem = kobject_name(&kset->kobj);
static inline const char *kobject_name(const struct kobject *kobj)
{
       return kobj->name;
}
这里主要获取kobj的名字。
以“power_supply”为例,在power_supply_core.c中注册class power_supply:
power_supply_class = class_create(THIS_MODULE, "power_supply");
将调用以下函数class_createà __class_createà __class_registerà kobject_set_name:
error = kobject_set_name(&cp->subsys.kobj, "%s", cls->name);
其中的cls->name就是“power_class”最终在kobject_set_name_vargs中赋值给kobject->name
3)devpath字符串,是改变了的uevent所在的sysfs中的位置
devpath = kobject_get_path(kobj, GFP_KERNEL);
2.填充字符串
然后分配一个env空间存储字符串,
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);将上面这些字符串填充到其中去,
retval = add_uevent_var(env, "ACTION=%s", action_string);
retval = add_uevent_var(env, "DEVPATH=%s", devpath);
retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
接着加入不同class的附加的字符串
retval = add_uevent_var(env, "%s", envp_ext[i]);
retval = uevent_ops->uevent(kset, kobj, env);
然后加上该Uenvent的序号,该序号是不断递增的。
add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq)。
3.发送
字符串准备完毕,就要准备发送了,由于Android的CONFIG_NET选项是选上的,因此可以通过socket发送:
首先分配一个skb用于存储网络发送的数据
scratch = skb_put(skb, len);
sprintf(scratch, "%s@%s", action_string, devpath);
此时scratch中就增加了change@/devices/platform/msm-battery/power_supply/usb的字符,然后将之前准备好的各个字符传加在后面
for (i = 0; i < env->envp_idx; i++) {
       len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
       strcpy(scratch, env->envp[i]);
}
最后发送调用retval = netlink_broadcast_filtered发送就OK了。
二、Android侧:
1.启动监视
private UEventObserver mPowerSupplyObserver = new UEventObserver()
{
   @Override
public void onUEvent(UEventObserver.UEvent event) {
            update();
        }
}
申明一个observer对象,然后调用startObserving启动对该对象的监视。
mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");
最终会调用到UEventObserver的addObserver:
private ArrayList<Object> mObservers = new ArrayList<Object>();
public void addObserver(String match, UEventObserver observer) {
   synchronized(mObservers) {
       mObservers.add(match);
      mObservers.add(observer);
   }
}
该函数最终会将”SUBSYSTEM=power_supply”增加到匹配序列中,当kernel发送具有该字符串的数据时,就返回匹配成功,然后调用mPowerSupplyObserver的onUEvent函数;
public void run() {
            ………………….
            while (true) {
                len = next_event(buffer);
                if (len > 0) {
                    String bufferStr = new String(buffer, 0, len);  // easier to search a String
                    synchronized (mObservers) {
                        for (int i = 0; i < mObservers.size(); i += 2) {
                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
                                ((UEventObserver)mObservers.get(i+1))
                                        .onUEvent(new UEvent(bufferStr));
                            }
                        }
                    }
                }
            }
        }
next_event(buffer)从底层接收数据,然后在for循环中比较,如果符合,则调用onUevent。之所以for循环时要加2,是因为一次startObserving是调用了两次mObservers.add,其中第一次的是匹配字符串。
2.JNI函数
其中next_event是一个JNI函数(android_os_UEventObserver.c):
private static native int next_event(byte[] buffer);
static JNINativeMethod gMethods[] = {
    {"native_setup", "()V",   (void *)android_os_UEventObserver_native_setup},
    {"next_event",   "([B)I", (void *)android_os_UEventObserver_next_event},
};
android_os_UEventObserver_next_event会调用到uevent_next_event,
3.Socket接口
在hardware/libhardware_legacy/uevent/vim uevent.c中,
int uevent_next_event(char* buffer, int buffer_length)
该函数监听socket,并将socket收到的数据保存到buffer中
nr = poll(&fds, 1, -1);
   
if(nr > 0 && fds.revents == POLLIN) {
    int count = recv(fd, buffer, buffer_length, 0);
if (count > 0) {
………………………………..
}
该socket是在int uevent_init()中创建的
s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
fd=s;




3,linux下热插拔事件的产生是怎样通知到用户空间,kobject_uevent_env之uevent_helper
http://blog.csdn.net/bingqingsuimeng/article/details/7924300
我打算做个最简单的脚本/sbin/myhotplug,这个脚本只干一件事,在/home/dennis目录下生成一个hotplug文件:
</sbin/myhotplug>
#!/bin/sh
cd /home/dennis
touch hotplug
然后把这个脚本程序的文件名给打入到内核空间的uevent_helper[0]上:
root@build-server:/sys/kernel# echo "/sbin/myhotplug" > uevent_helper
root@build-server:/sys/kernel# cat uevent_helper
/sbin/myhotplug
好了,现在检查一下你的/home/dennis目录下面有没有hotplug这个文件,有的话就删掉,否则怎么知道是新生成的呢。
现在,找个U盘插到你的电脑里,然后再看一下/home/dennis目录,有个hotplug文件对吧?如果你现在删除这个文件,再把U盘给拔了,
你会再次发现这个文件。这意味着什么,意味着你可以轻而易举地捕捉到设备加入/移出系统等事件,如果你的脚本足够智能,那么你就会想到很多很有创意的玩法对吧?


最后,对于PCI设备而言,Linux系统在启动过程中会扫描系统中所有PCI设备,对发现的每一个设备都会调用device_add函数,正如你前面看到的那样,
udev将会被通知,它负责找到对应的驱动模块并加载。当然,如果你愿意,你也可以去捕捉这些事件。


4,设备模型的uevent机制
http://www.cnblogs.com/black-mamba/p/5055683.html
内核模块的热插拔事件的通知基于uevent机制。


当kobject的状态发生改变(如,add, remove等)时,会通知用户空间,用户空间接收到事件通知后可以做相应的处理。
uevent把事件上报给用户空间的两种途径:
  1.通过kmod模块,直接调用用户空间的可执行程序或脚本。
  2.通过netlink通信机制,将事件从内核空间传递到用户空间。


linux-3.5/include/linux/kobject.h


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


#define UEVENT_HELPER_PATH_LEN      256
#define UEVENT_NUM_ENVP         32  /* number of env pointers */
#define UEVENT_BUFFER_SIZE      2048    /* buffer for the variables */


//在利用kmod模块向用户空间上报event事件时,会直接执行用户空间的可执行文件。而在linux系统中,可执行文件的执行,依赖于环境变量,
//因此kobj_uevent_env用于组织此次事件上报是的环境变量。
struct kobj_uevent_env {
    char *envp[UEVENT_NUM_ENVP];//指针数组,用于保存每个环境变量的地址,最多支持32个环境变量
    int envp_idx;//用户访问环境变量数组的索引
    char buf[UEVENT_BUFFER_SIZE];//保存环境变量的buffer
    int buflen;//???
};


struct kset_uevent_ops {
    int (* const filter)(struct kset *kset, struct kobject *kobj);//当任何kobject需要上报uevent时,它所属的kset可以通过filter借口过滤,阻止不希望上报的uevent。
    const char *(* const name)(struct kset *kset, struct kobject *kobj);//该接口可以返回kset的名称。如果一个kset没有合法的名称,则其下的所有kobject将不允许上报uevent
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);//当任何kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
              //因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了。
};


#if defined(CONFIG_HOTPLUG)
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
            char *envp[]);


__printf(2, 3)
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_env ,以 envp 为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。


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[])
{
    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;
#ifdef CONFIG_NET
    struct uevent_sock *ue_sk;
#endif


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


    /* search the kset we belong to */
    //1.查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的)
    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*/
    //2.查看kobj->uevent_suppress是否被设置;如果设置了,则忽略所有的uevent上报,并返回0.
    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. */
    //3.如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event)
    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 */
    //4.判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem;
    //若没有合法的名称,则不上报uevent
    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 */
    //5.分配一个此次上报的用于保存环境变量的buffer,
    env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
    if (!env)
        return -ENOMEM;


    /* complete object path */
    //6.获得该kobject在sysfs中路径
    devpath = kobject_get_path(kobj, GFP_KERNEL);
    if (!devpath) {
        retval = -ENOENT;
        goto exit;
    }


    /* default keys */
    //7.添加ACTION到env
    retval = add_uevent_var(env, "ACTION=%s", action_string);
    if (retval)
        goto exit;
    //8.添加DEVPATH(kobject路径信息)到env
    retval = add_uevent_var(env, "DEVPATH=%s", devpath);
    if (retval)
        goto exit;
    //9.添加SUBSYSTEM到env
    retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    if (retval)
        goto exit;


    /* keys passed in from the caller */
    //10.如果传入的envp_ext不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
    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 */
    //11.如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针
    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.
     */
    //12.根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
    if (action == KOBJ_ADD)
        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 */
    //13.调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号
    retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
    if (retval) {
        mutex_unlock(&uevent_sock_mutex);
        goto exit;
    }
//14.如果定义了"CONFIG_NET”,则使用netlink发送该uevent
#if defined(CONFIG_NET)
    /* send netlink message */
    list_for_each_entry(ue_sk, &uevent_sock_list, list) {
        struct sock *uevent_sock = ue_sk->sk;
        struct sk_buff *skb;
        size_t len;


        if (!netlink_has_listeners(uevent_sock, 1))
            continue;


        /* 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_filtered(uevent_sock, skb,
                                0, 1, GFP_KERNEL,
                                kobj_bcast_filter,
                                kobj);
            /* ENOBUFS should be handled in userspace */
            if (retval == -ENOBUFS || retval == -ESRCH)
                retval = 0;
        } else
            retval = -ENOMEM;
    }
#endif
    mutex_unlock(&uevent_sock_mutex);


    /* call uevent_helper, usually only enabled during early boot */
    //15.以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,
    //        调用kmod模块提供的call_usermodehelper函数,上报uevent。
    if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
        char *argv [3];


        argv [0] = uevent_helper;//在/sys/kernel/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模块通过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”中相应的代码。


在/etc/init.d/rcS脚本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,会发现cat /sys/kernel/uevent_helper 即是/sbin/mdev。
说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会写到/sys/kernel/uevent_helper中。


自己手动echo "/kernel/main" > uevent_helper(之前的/sbin/mdev会被覆盖),当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行,
表明事件已经上报给用户空间。


Q1:用户空间怎样去识别上报的事件到底是什么事件?下一步研究


call_usermodehelper函数能够方便的在内核中直接新建和运行用户空间的程序,并且该程序有root权限。
call_usermodeheler函数的参数用法和execve函数一致。
call_usermodehelper()->call_usermodehelper_exec()


5,Linux 设备模型浅析之 uevent 篇(2)
http://www.cnblogs.com/0822vaj/p/3500289.html
通过前面的分析可知,要实现 hotplug 机制,需要有用户空间的程序配合才行。对于
pc 机的 linux 系统,采用的是 udevd 服务程序,其通过监听 NETLINK_KOBJECT_UEVENT 获
得内核发出的 uevent 事件和环境变量,然后再查找匹配的 udev rules,根据找到的 rules 做动
作,udev 的具体实现原理可参照网上的一些文章。在《Linux 设备模型浅析之设备篇》中讲
过,在每个注册的 device 文件夹下会生成一个 uevent 属性文件,其作用就是实现手动触发
hotplug 机制。可以向其中写入“add”和“remove”等命令,以添加和移除设备。在系统启动后注
册了很多 device,但用户空间还没启动,所以这些事件并没有处理,udevd 服务启动后,会扫
描/sys 目录里所有的 uevent 属性文件,向其写入"add”命令,从而触发 uevent 事,这样 udevd 服
务程序就有机会处理这些事件了。在嵌入式系统中使用的是 mdev,是 udev 的简化版本,在启
动脚本 rcS 中会有这样一句命令/sbin/mdev -s,其作用就是刚刚讲到的,扫描/sys 目录里所有的
uevent 属性文件,向其写入"add”命令,触发 uevent 事件,从而 mdev 有机会处理这些事件。
从上面的分析可以看出,每当内核注册设备或驱动时都会产生 uevent 事件,这样用户空间
的 udev 或 mdev 就有机会捕捉到这些事件,根据匹配的规则作一定的处理,比如在/dev 目录下
生成设备节点或使用 modprobe 加载驱动程序,等等。从而实现自动生成设备节点、加载驱动程
序等等这些热拔插机制。
     
     

你可能感兴趣的:(uevent总结)