注册了的内核对象可以发送用户态事件,接口为:
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,还包括其他的事件,比如关于路由的。剩下的其他操作跟套接字操作都差不多。