关键词:uevent、netlink、ADD/REMOVE/CHANGE、uevent_helper、hotplug、usermode helper、mdev、mdev.conf等等。
本文从三方面了解uevent相关内容:内核中uevent如何传送、用户空间如何处理uevent、如何通过mdev实现热插拔功能。
1. Linux uevent分析
kobject_action定义了 Linux下的uevent类型;struct kerenl_uevent_env表示一个待发送的uevent。
uevent_net_init()创建发送uevent所需要的socket等信息。
内核驱动通过kobject_uevent()/kobject_uevent_env()发送uevent到用户空间,主要包括两部分工作:一是通过netlink_broadcast_filtered()发送netlink消息;另一是通过call_usermodehelper_setup()/call_usermodehelper_exec()调用用户空间程序处理uevent消息。
1.1 uevent数据结构
kobject_action定义了kobject的动作,包括ADD、REMOVE、CHANGE等等。用户空间根据ADD或者REMOVE处理热插拔时间,电池模块根据CHANGE处理电量更新。
kobj_uevent_env用于表示一个kobject事件,argv是用户空间执行的helper参数;envp和buf组成发送uevent字符串信息。
enum kobject_action { KOBJ_ADD,------------------------ADD/REMOVE添加/移除事件。 KOBJ_REMOVE, KOBJ_CHANGE,---------------------设备状态或者内容发生改变。 KOBJ_MOVE,-----------------------更改名称或者更改parent,即更改了目录结构。 KOBJ_ONLINE,---------------------设备上线/下线事件,常表示使能或者去使能。 KOBJ_OFFLINE, KOBJ_MAX }; static const char *kobject_actions[] = { [KOBJ_ADD] = "add", [KOBJ_REMOVE] = "remove", [KOBJ_CHANGE] = "change", [KOBJ_MOVE] = "move", [KOBJ_ONLINE] = "online", [KOBJ_OFFLINE] = "offline", }; struct kobj_uevent_env { char *argv[3];------------------------------用户空间可执行文件路径,以及参数等。 char *envp[UEVENT_NUM_ENVP];----------------指针数组,保存每个环境变量的地址。 int envp_idx; char buf[UEVENT_BUFFER_SIZE];---------------环境变量内容。 int buflen; };
1.2 uevent初始化
uevent_net_init()创建类型为NETLINK_KOBJECT_UEVENT的socket,并将其放入uevent_sock_list链表上。uevent_net_exit()则将其从uevent_socket_list中摘除,并且释放socket相关资源。
static int uevent_net_init(struct net *net) { struct uevent_sock *ue_sk; struct netlink_kernel_cfg cfg = { .groups = 1, .flags = NL_CFG_F_NONROOT_RECV, }; ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL); if (!ue_sk) return -ENOMEM; ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT, &cfg);------------创建NETLINK_KOBJECT_UEVENT类型的socket。 if (!ue_sk->sk) { printk(KERN_ERR "kobject_uevent: unable to create netlink socket! "); kfree(ue_sk); return -ENODEV; } mutex_lock(&uevent_sock_mutex); list_add_tail(&ue_sk->list, &uevent_sock_list);-----------------------------------将创建的uevent_sock加入到uevent_sock_list中。 mutex_unlock(&uevent_sock_mutex); return 0; } static void uevent_net_exit(struct net *net) { struct uevent_sock *ue_sk; mutex_lock(&uevent_sock_mutex); list_for_each_entry(ue_sk, &uevent_sock_list, list) { if (sock_net(ue_sk->sk) == net) goto found; } mutex_unlock(&uevent_sock_mutex); return; found: list_del(&ue_sk->list); mutex_unlock(&uevent_sock_mutex); netlink_kernel_release(ue_sk->sk); kfree(ue_sk); } static struct pernet_operations uevent_net_ops = { .init = uevent_net_init, .exit = uevent_net_exit, }; static int __init kobject_uevent_init(void) { return register_pernet_subsys(&uevent_net_ops);-----------将uevent网络协议模块添加到新的命名空间子系统中,并且调用init初始化函数。 } postcore_initcall(kobject_uevent_init);
1.3 对uevent_helper设置
对uevent_helper设置,可以对/proc/sys/kernel/hotplug写可执行文件路径即可。
然后在内核触发uevent事件的之后调用相关可执行文件进行处理。
static struct ctl_table kern_table[] = { ... #ifdef CONFIG_UEVENT_HELPER { .procname = "hotplug", .data = &uevent_helper, .maxlen = UEVENT_HELPER_PATH_LEN, .mode = 0644, .proc_handler = proc_dostring, }, #endif... { } };
或者还可以对/proc/kernel/uevent_helper写入可执行文件路径。
static ssize_t uevent_helper_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%s ", uevent_helper); } static ssize_t uevent_helper_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { if (count+1 > UEVENT_HELPER_PATH_LEN) return -ENOENT; memcpy(uevent_helper, buf, count); uevent_helper[count] = '