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