Linux设备模型(四) uevent

热插拔事件:在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程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值