用户态事件

    注册了的内核对象可以发送用户态事件,接口为:

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

int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])
// 参数说明
1) kobj: 发生事件的内核对象
2) action: 事件的类型
3) envp_ext: 指向一个已NULL结尾的字符串数组,其中每个字符串是一条环境变量的设置信息,这些环境变量添加在默认的环境变量之后。
 
返回值:
0 表示成功,负数为错误

    如果不需要添加而外的环境变量,可以用一个由上述函数包装而成的函数:

int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
    return kobject_uevent_env(kobj, action, NULL);
}

    内核对象发送用户态事件,需要满足如下条件才能将事件发送出去:

1) 内核对象必须属于某个内核集合,否则就不会发送;

2)内核集合的filter函数会对事件进行过滤处理,如果返回0,则表示事件被过滤掉,不需要发送;

    下面来具体分析下kobject_uevent_env()函数的源码实现,看看用户态事件是如何发送的。源码比价长,这里将它进行拆分,分别进行说明,源码在lib/

1. 确定内核对象kobj所属的内核集合kseet:

struct kobject *top_kobj;
struct kset *kset;
struct kset_uevent_ops *uevent_ops;
 
top_kobj = kobj;
while (!top_kobj->kset && top_kobj->parent)
top_kobj = top_kobj->parent;
 
 /* 如果内核对象kobj没有所属的内核集合kset,则返回-EINVAL*/
if (!top_kobj->kset) {
    pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
        "without kset!\n", kobject_name(kobj), kobj, __func__);
    return -EINVAL;
}

2. 保存内核对象kobj所属的kset,以及kset的用户态事件接口uevent_ops,方便后续的操作:

kset = top_kobj->kset;
uevent_ops = kset->uevent_ops;

    这里补充说明一下内核集合的用户态事件操作方法:

struct kset_uevent_ops {
    // 返回0表示被过滤掉,不需要发送
    int (*filter)(struct kset *kset, struct kobject *kobj);
    // 用于子系统名称的过滤
    const char *(*name)(struct kset *kset, struct kobject *kobj);
    // 在这个函数内部可以添加我们自定义的环境变量,若返回非0,则会丢弃事件
    int (*uevent)(struct kset *kset, struct kobject *kobj,
          struct kobj_uevent_env *env);
};

3. 如果uevent_ops->filter存在,调用其执行过滤操作,如果返回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;
        }

4. 获取内核对象kobj的子系统名subsystem

/* 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;
    }

5. 获取内核对象完整的对象路径devpath,即内核对象对应的sysfs目录路径

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

6.  把上述获取到的值,保存到uevent_env对象中:

    struct kobj_uevent_env *env = NULL;
 
    /* environment buffer */
    env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
    if (!env)
        return -ENOMEM;

    /* 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;

实际上,任何一个内核对象发送用户态事件,都会包含4个缺省的key:

1) ACTION:

2) DEVPATH:

3) SUBSYSTEM:

4) SEQNUM:

    不过SEQNUM是在最后才加入到uevent_env对象中,后面会有说明。

7. 如果调用者有其他环境变量需要添加,则加入到uevent_env对象中: 每一个环境变量的格式必须是: key=val

/* 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;
        }
    }

8. 调用钩子函数uevent_ops->uevent,增加对象特有的环境变量:

    /* 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;
        }
    }

注意这个钩子函数,可以执行些额外的操作,加入其它的环境变量,在后面分析设备对象与驱动对象的用户态事件内容的时候,就会发现它们会存在不一样的环境变量。

8. 加入SEQNUM环境变量:

    /* 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;

9. 把我们需要发送的环境变量加入到uevent_ent对象后,就可以通过netlink方式发送了:

 /* 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;
    }

    这个uevent_sock套接字的创建,是在kobject_uevent_init时创建的:

static int __init kobject_uevent_init(void)
{
    uevent_sock = netlink_kernel_create(&init_net, NETLINK_KOBJECT_UEVENT,
                        1, NULL, NULL, THIS_MODULE);
    if (!uevent_sock) {
        printk(KERN_ERR
               "kobject_uevent: unable to create netlink socket!\n");
        return -ENODEV;
    }
    netlink_set_nonroot(NETLINK_KOBJECT_UEVENT, NL_NONROOT_RECV);
    return 0;
}

postcore_initcall(kobject_uevent_init);

10.  内核除了调用netlink套接字发送给用户层程序,还会调用用户态助手进行处理:

 /* 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);
    }

    可见,调用用户态助手发送用户态事件,还会添加HOME=/PATH=/sbin:/bin:/usr/sbin:/usr/bin这两个环境变量。

    到这里,基本上就把内核对象发送用户态事件的过程基本分析完了,对于不同的内核对象,比如总线、设备等,在注册的时候发送的内容均不一样,这个不一样就是通过uevent这个钩子函数来实现的。

    上面提到会通过netlink方式发送用户态事件,那么我们可以编写一个简单的应用程序,通过netlink方式来接收这些用户态事件,方便我们了解:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <linux/types.h>
#include <linux/netlink.h>


int main(int argc, char **argv)
{
    printf("**test uevent:\n");
    // 1. 创建NETLINK套接字
    int sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
    if (sock < 0) {
        perror("socket fail");
        return -1;
    }

    struct sockaddr_nl addr;
    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();
    addr.nl_groups = -1;

    // 2. 通过bind方法来加入到NETLINK多播组中
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind fail");
        close(sock);
        return -1;
    }

    char buf[4096];
    int ret;
    int i;
    while (1) {
        memset(buf, 0, sizeof(buf));
        // 3. 接收UEVENT消息,并显示
        ret = recv(sock, buf, sizeof(buf), 0);
        printf("recv %d>>>>>>>>>>>>>>>>>>>>\n", ret);

        if (ret < 1) {
            perror("recv fail");
            break;
        }

        i = 0;
        while (i < ret) {
            if (buf[i] == '\0')
                buf[i] = '\n';

            ++i;
        }

        printf("recv: %s\n", buf);
    }

    close(sock);
    return 0;
}

    这里要注意一下的就是netlink方式套接字的类型是UDP的,地址族为AF_NETLINK,  最后的那个参数表示 我们接收的事件类型,因为除了用户态事件NETLINK_KOBJECT_UEVENT,还包括其他的事件,比如关于路由的。剩下的其他操作跟套接字操作都差不多。

你可能感兴趣的:(用户态事件)