博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
用户态事件
阅读量:6690 次
发布时间:2019-06-25

本文共 7723 字,大约阅读时间需要 25 分钟。

hot3.png

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

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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,还包括其他的事件,比如关于路由的。剩下的其他操作跟套接字操作都差不多。

转载于:https://my.oschina.net/kaedehao/blog/631771

你可能感兴趣的文章
spring-security中的csrf防御机制(跨域请求伪造)
查看>>
ubuntu安装python-ldap模块
查看>>
使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)...
查看>>
javadoc 构建出错(二)
查看>>
urumuqi 网络赛 H skiing DP
查看>>
linux系统查找具体进程
查看>>
c#执行Oracle存储过程
查看>>
adb_安装软件
查看>>
廖雪峰官网学习js 字符串
查看>>
phpcms 如何获取文章
查看>>
C# 如何防止重放攻击(转载)
查看>>
C#匿名类型
查看>>
kubernetes 身份与权限认证 (ServiceAccount && RBAC)
查看>>
Apache Nutch 1.3 学习笔记二
查看>>
ActiveMQ
查看>>
Nginx服务器部署 负载均衡 反向代理
查看>>
C++学习笔记:指向函数的指针
查看>>
Child Action
查看>>
# 2017-2018-1 20155319 实验五 《通讯协议设计》
查看>>
通用后台管理系统(1)-数据库设计
查看>>