1.Uevent的功能
Uevent是Kobject的一部份linux 设备驱动,用于在Kobject状态发生改变时,比如降低、移除等,通知用户空间程序。用户空间程序收到这样的风波后,会做相应的处理。
2.Uevent在kernel中的位置
下边图片描述了Uevent模块在内核中的位置:
3.Uevent的内部逻辑解析3.1SourceCode位置
Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件,如下:
3.2数据结构描述
kobject.h定义了uevent相关的常量和数据结构,如下:
/* include/linux/kobject.h, line 50 */ enum kobject_action { KOBJ_ADD, KOBJ_REMOVE, KOBJ_CHANGE, KOBJ_MOVE, KOBJ_ONLINE, KOBJ_OFFLINE, KOBJ_MAX };
kobject_action定义了event的类型,包括:
ADD/REMOVE,Kobject(或下层数据结构)的添加/移除风波。
ONLINE/OFFLINE,Kobject(或下层数据结构)的上线/下线风波,虽然是是否使能。
CHANGE,Kobject(或下层数据结构)的状态或则内容发生改变。
MOVE,Kobject(或下层数据结构)更更名称或则修改Parent(意味着在sysfs中修改了目录结构)。
CHANGE,假如设备驱动须要上报的风波不再前面风波的范围内,或则是自定义的风波,可以使用该event,并携带相应的参数。
1 /* include/linux/kobject.h, line 31 */ 2: #define UEVENT_NUM_ENVP 32 /* number of env pointers */ 3: #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */ 4: 5: /* include/linux/kobject.h, line 116 */ 6: struct kobj_uevent_env { 7: char *envp[UEVENT_NUM_ENVP]; 8: int envp_idx; 9: char buf[UEVENT_BUFFER_SIZE]; 10: int buflen; 11: };
后面有提及过,在借助Kmod向用户空间上报event风波时,会直接执行用户空间的可执行文件。而在Linux,可执行文件的执行,依赖于环境变量,因而kobj_uevent_env用于组织这次风波上报时的环境变量。
envp,表针链表,用于保存每位环境变量的地址,最多可支持的环境变量数目为UEVENT_NUM_ENVP。
envp_idxlinux手机软件,用于访问环境变量表针链表的index。
buf,保存环境变量的buffer,最大为UEVENT_BUFFER_SIZE。
buflen,访问buf的变量。
1: /* include/linux/kobject.h, line 123 */ 2: struct kset_uevent_ops { 3: int (* const filter)(struct kset *kset, struct kobject *kobj); 4: const char *(* const name)(struct kset *kset, struct kobject *kobj); 5: int (* const uevent)(struct kset *kset, struct kobject *kobj, 6: struct kobj_uevent_env *env); 7: };
kset_uevent_ops是为kset量身订做的一个数据结构,上面包含filter和uevent两个反弹函数,好处如下:
filter,当任何Kobject须要上报uevent时,它所属的kset可以通过该插口过滤,制止不希望上报的event,进而达到从整体上管理的目的。
name,该插口可以返回kset的名称。假如一个kset没有合法的名称,则其下的所有Kobject将不容许上报uvent.
uevent,当任何Kobject须要上报uevent时,它所属的kset可以通过该插口统一为这种event添加环境变量。由于好多时侯上报uevent时的环境变量都是相同的,因而可以由kset统一处理,就不须要让每位Kobject只身添加了
3.2内部实现
#if defined(CONFIG_HOTPLUG) int kobject_uevent(struct kobject *kobj, enum kobject_action action); int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]); __printf(2, 3) int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...); int kobject_action_type(const char *buf, size_t count, enum kobject_action *type); kobject_uevent_env ,以 envp 为环境变量,上报一个指定action的uevent。环境变量的作用是为执行用户空间程序指定运行环境。 int kobject_uevent(struct kobject *kobj, enum kobject_action action) { return kobject_uevent_env(kobj, action, NULL); } int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]) { struct kobj_uevent_env *env; const char *action_string = kobject_actions[action]; const char *devpath = NULL; const char *subsystem; struct kobject *top_kobj; struct kset *kset; const struct kset_uevent_ops *uevent_ops; int i = 0; int retval = 0; #ifdef CONFIG_NET struct uevent_sock *ue_sk; #endif pr_debug("kobject: '%s' (%p): %sn", kobject_name(kobj), kobj, __func__); /* search the kset we belong to */ //1.查找当前kobject或其parent是否从属于某个kset;如果都不从属于某个kset,则返回错误。(说明一个kobject若没有加入kset,是不会上报uevent的) top_kobj = kobj; while (!top_kobj->kset && top_kobj->parent) top_kobj = top_kobj->parent; if (!top_kobj->kset) { pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " "without kset!n", kobject_name(kobj), kobj, __func__); return -EINVAL; } kset = top_kobj->kset; uevent_ops = kset->uevent_ops; /* skip the event, if uevent_suppress is set*/ //2.查看kobj->uevent_suppress是否被设置;如果设置了,则忽略所有的uevent上报,并返回0. if (kobj->uevent_suppress) { pr_debug("kobject: '%s' (%p): %s: uevent_suppress " "caused the event to drop!n", kobject_name(kobj), kobj, __func__); return 0; } /* skip the event, if the filter returns zero. */ //3.如果所属的kset有uevent_ops->filter,则调用该函数,若该函数返回0,则过滤此次上报。(kset 可以通过filter接口过滤不希望上报的event) 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; } /* originating subsystem */ //4.判断所属的kset是否有合法的名称,若uevent_ops->name存在就用其返回的名称作为subsystem;若uevent_ops->name不存在就用kset本身的kobject的名称作为subsystem; //若没有合法的名称,则不上报uevent 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; } /* environment buffer */ //5.分配一个此次上报的用于保存环境变量的buffer, env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; /* complete object path */ //6.获得该kobject在sysfs中路径 devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* default keys */ //7.添加ACTION到env retval = add_uevent_var(env, "ACTION=%s", action_string); if (retval) goto exit; //8.添加DEVPATH(kobject路径信息)到env retval = add_uevent_var(env, "DEVPATH=%s", devpath); if (retval) goto exit; //9.添加SUBSYSTEM到env retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); if (retval) goto exit; /* keys passed in from the caller */ //10.如果传入的envp_ext不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中 if (envp_ext) { for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, "%s", envp_ext[i]); if (retval) goto exit; } } /* let the kset specific function add its stuff */ //11.如果 uevent_ops->uevent 存在,调用该接口,添加kset统一的环境变量到env指针 if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kset, kobj, env); if (retval) { pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%dn", kobject_name(kobj), kobj, __func__, retval); goto exit; } } /* * Mark "add" and "remove" events in the object to ensure proper * events to userspace during automatic cleanup. If the object did * send an "add" event, "remove" will automatically generated by * the core, if not already done by the caller. */ //12.根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态 if (action == KOBJ_ADD) kobj->state_add_uevent_sent = 1; else if (action == KOBJ_REMOVE) kobj->state_remove_uevent_sent = 1; mutex_lock(&uevent_sock_mutex); /* we will send an event, so request a new sequence number */ //13.调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号 retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum); if (retval) { mutex_unlock(&uevent_sock_mutex); goto exit; } //14.如果定义了"CONFIG_NET”,则使用netlink发送该uevent #if defined(CONFIG_NET) /* send netlink message */ list_for_each_entry(ue_sk, &uevent_sock_list, list) { struct sock *uevent_sock = ue_sk->sk; struct sk_buff *skb; size_t len; if (!netlink_has_listeners(uevent_sock, 1)) continue; /* 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 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_filtered(uevent_sock, skb, 0, 1, GFP_KERNEL, kobj_bcast_filter, kobj); /* ENOBUFS should be handled in userspace */ if (retval == -ENOBUFS || retval == -ESRCH) retval = 0; } else retval = -ENOMEM; } #endif mutex_unlock(&uevent_sock_mutex); /* call uevent_helper, usually only enabled during early boot */ //15.以uevent_helper、 subsystem 以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数, // 调用kmod模块提供的call_usermodehelper函数,上报uevent。 if (uevent_helper[0] && !kobj_usermode_filter(kobj)) { char *argv [3]; argv [0] = uevent_helper;//在/sys/kernel/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); } exit: kfree(devpath); kfree(env); return retval; }
uevent模块通过kmod上报uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或则,简称ueventhelper)处理该event。而该ueventhelper的路径保存在uevent_helper字段中。
可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定ueventhelper。但这些方法会为每位eventfork一个进程,随着内核支持的设备数目的增多,这些方法在系统启动时将会是致命的(可以造成显存溢出等)。因而只有在初期的内核版本中会使用这些方法,如今内核不再推荐使用该方法。因而内核编译时,须要把该配置项留空。
在系统启动后,大部份的设备已然ready,可以按照须要linux 设备驱动,重新指定一个ueventhelper,便于测量系统运行过程中的热插拔风波。这可以通过把helper的路径写入到"/sys/kernel/uevent_helper"文件中实现。实际上,内核通过sysfs文件系统的方式,将uevent_helper链表开放到用户空间,供用户空间程序更改访问,具体可参考"./kernel/ksysfs.c”中相应的代码。
在/etc/init.d/rcS中添加echo"/sbin/mdev">/proc/sys/kernel/hotplug,会发觉cat/sys/kernel/uevent_helper即是/sbin/mdev。说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会讲到/sys/kernel/uevent_helper中。
自己自动echo"/kernel/main">uevent_helper(之前的/sbin/mdev会被覆盖)红帽linux,当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行,表明风波早已上报给用户空间。
call_usermodehelper函数就能便捷的在内核中直接新建和运行用户空间的程序,但是该程序有root权限。call_usermodeheler函数的参数用法和execve函数一致。call_usermodehelper()->call_usermodehelper_exec()
本文原创地址://q13zd.cn/uxjkztgbtzyn.html编辑:刘遄,审核员:暂无