免费咨询热线
13621929115作者:wowo发布于:2014-3-10 20:39 分类:统一设备模型题图转自:Linux设备模型(3)_Ueventwww.wowotech.net/devi设备模型ce_model/uevent.html
转载者注:本文在第一次阅读时,建议初接触内核代码的小白最好不要太死抠代码,先明白大致的功能,头脑中形成一个简单的认识,在不断学习中逐渐深入文章内的代码为4.14版本的内核,与原作者使用的内核,略有差别本文主体是原作者文章,小编进行进一步的排版、内容增添、内容注设备模型释,后期会根据自己的感悟用更多流程图、思维导图之类的框图表达自己的理解,敬请期待!!!
1. Uevent的功能Uevent是Kobject的一部分,用于在Kobject状态发生改变时,例如增加、移除等,通知用户空间程序用户空间程序收到这样的事件后,会做相应的处理该机制通常是用来支持热拔插设备的,例如设备模型U盘插入后,USB相关的驱动软件会动态创建用于表示该U盘的device结构(相应的也包括其中的kobject),并告知用户空间程序,为该U盘动态的创建/dev/目录下的设备节点,更进一步,可以通知其它的应用程序,将该U盘设备mount到系统中,从而动态的支持该设备。
2. Uevent在kernel中设备模型的位置下面图片描述了Uevent模块在内核中的位置:
由此可知,Uevent的机制是比较简单的,设备模型中任何设备有事件需要上报时,会触发Uevent提供的接口Uevent模块准备好上报事件的格式后,可以通过两个途径把事件上报到用户空间:一种是通过kmod模块,直接调用用户空间的可执行文件;另一种是通设备模型过netlink通信机制,将事件从内核空间传递给用户空间。
注1:有关kmod和netlink,会在其它文章中描述,因此本文就不再详细说明了3. Uevent的内部逻辑解析3.1 Source Code位置Uevent的代码比较简单,主要涉及kobject.h和kobject_uevent.c两个文件设备模型,如下:。
include/linux/kobject.hlib/kobject_uevent.c3.2 数据结构描述kobject.h定义了uevent相关的常量和数据结构,如下:kobject_action1:
/* include/linux/kobject.h, line 50 */2:enum设备模型kobject_action{3:KOBJ_ADD,4:KOBJ_REMOVE,5:KOBJ_CHANGE,6:KOBJ_MOVE
,7:KOBJ_ONLINE,8:KOBJ_OFFLINE,9:KOBJ_MAX10:};kobject_action定义了event的类型,包括:ADD/REMOVE,设备模型Kobject(或上层数据结构)的添加/移除事件。
ONLINE/OFFLINE,Kobject(或上层数据结构)的上线/下线事件,其实是是否使能CHANGE,Kobject(或上层数据结构)的状态或者内容发生改变MOVE,Kobject(或上层数据结构)更改名称或者更改Parent(意味着在sysf设备模型s中更改了目录结构)。
CHANGE,如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数kobj_uevent_env1:/* include/linux/kobject.h, line 31 */。
2:#defineUEVENT_NUM_ENV设备模型P32/* number of env pointers */3:#defineUEVENT_BUFFER_SIZE2048/* buffer for the variables */
4:5:/* include/linux/kobject.h, line 116 */6:structkobj_ue设备模型vent_env{7:char*envp[UEVENT_NUM_ENVP];8:int
envp_idx;9:charbuf[UEVENT_BUFFER_SIZE];10:intbuflen;11:};前面有提到过,在利用Kmod向用户空间上报event事件时,会直接执行用户空间的可执行文件。
而在Li设备模型nux系统,可执行文件的执行,依赖于环境变量,因此kobj_uevent_env用于组织此次事件上报时的环境变量envp,指针数组,用于保存每个环境变量的地址,最多可支持的环境变量数量为UEVENT_NUM_ENVP。
envp_idx,用于访问环境变量指针数组的indexbuf,保存环境变量的buf设备模型fer,最大为UEVENT_BUFFER_SIZEbuflen,访问buf的变量kset_uevent_ops 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没有合法的名称,则其下的所有Kobje设备模型ct将不允许上报uventuevent,当任何Kobject需要上报uevent时,它所属的kset可以通过该接口统一为这些event添加环境变量。
因为很多时候上报uevent时的环境变量都是相同的,因此可以由kset统一处理,就不需要让每个Kobject独自添加了3.3 内部动作通过kobject设备模型.h,uevent模块提供了如下的API(这些API的实现是在"lib/kobject_uevent.c”文件中):
1:/* include/linux/kobject.h, line 206 */2:intkobject_uevent(structkobject*kobj,enumkobject_设备模型actionaction
);3:intkobject_uevent_env(structkobject*kobj,enumkobject_actionaction,4:char*envp[]);5:6:__printf(2,3
)7:intadd_uevent_var(structkobj_ueven设备模型t_env*env,constchar*format,...);8:9:intkobject_action_type(const
char*buf,size_tcount,10:enumkobject_action*type);kobject_uevent_env,以envp为环境变量,上报一个指定a设备模型ction的uevent环境变量的作用是为执行用户空间程序指定运行环境。
具体动作如下:源代码巨长,可以不看.../**
* kobject_uevent_env - send an uevent with environmental data
*
* 设备模型@kobj: struct kobject that the action is happening to
* @action: action that is happening
* @envp_ext: pointer to environmental data
*
设备模型 * Returns 0 if kobject_uevent_env() is completed with success or the
* corresponding error when it fails.
*/intkobject_uevent_env(struc设备模型tkobject*kobj,enumkobject_actionaction
,char*envp_ext[]){structkobj_uevent_env*env;constchar*action_string=kobject_actions[action];constchar
*devpath=NU设备模型LL;constchar*subsystem;structkobject*top_kobj;structkset*kset;conststructkset_uevent_ops*uevent_ops
;inti=0;intretval=0;#ifdef CONFIG_NET
struc设备模型tuevent_sock*ue_sk;#endif
pr_debug("kobject: %s (%p): %s\n
",kobject_name(kobj),kobj,__func__);/* search the kset we belong to */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 "
"witho设备模型ut 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*/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. */
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__);return0;}/* originating subsystem */if
(uevent_ops&&uevent_ops->nam设备模型e)subsystem=uevent_ops->name(kset,kobj);elsesubsystem=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__);return0;}/* environment buffer */env=kzalloc(sizeof(structkobj_uevent_env),GFP_KERNEL
);if(!e设备模型nv)return-ENOMEM;/* complete object path */devpath=kobject_get_path(kobj,GFP_KERNEL);if(!devpath
){retval=-ENOENT;gotoexit;}/* default keys */retval=ad设备模型d_uevent_var(env,"ACTION=%s",action_string);if(
retval)gotoexit;retval=add_uevent_var(env,"DEVPATH=%s",devpath);if(retval)gotoexit;retval=add_uevent_va设备模型r
(env,"SUBSYSTEM=%s",subsystem);if(retval)gotoexit;/* 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)gotoexit;}}/* 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);gotoexit;}}switch(action设备模型){caseKOBJ_ADD:/*
* Mark "add" event so we can make sure we deliver "remove"
* event to userspace during automatic cleanup. If
* the ob设备模型ject did send an "add" event, "remove" will
* automatically generated by the core, if not already done
* by the caller.
*/kobj->state_a设备模型dd_uevent_sent
=1;break;caseKOBJ_REMOVE:kobj->state_remove_uevent_sent=1;break;caseKOBJ_UNBIND:zap_modalias_env(env);
break;default:break;}mutex_lock(&u设备模型event_sock_mutex);/* we will send an event, so request a new sequence number */
retval=add_uevent_var(env,"SEQNUM=%llu",(unsignedlonglong)++uevent_seqn设备模型um);if(retval){mutex_unlock(&uevent_sock_mutex
);gotoexit;}#if defined(CONFIG_NET)
/* send netlink message */list_for_each_entry(ue_sk,&uevent_设备模型sock_list
,list){structsock*uevent_sock=ue_sk->sk;structsk_buff*skb;size_tlen;if(!netlink_has_listeners(uevent_sock
,1))continue;/* allocate message wit设备模型h 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;ienvp_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(ueve设备模型nt_sock,skb,0,1,GFP_KERNEL
,kobj_bcast_filter,kobj);/* ENOBUFS should be handled in userspace */if(retval==-ENOBUFS||retval==-ESRCH
)retval=0;}elseretva设备模型l=-ENOMEM;}#endif
mutex_unlock(&uevent_sock_mutex);#ifdef CONFIG_UEVENT_HELPER
/* call uevent_helper, usually only enabled during earl设备模型y boot */
if(uevent_helper[0]&&!kobj_usermode_filter(kobj)){structsubprocess_info*info;retval=add_uevent_var(env
,"HOME=/");if(retval)gotoexit;retval=ad设备模型d_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");if(retval
)gotoexit;retval=init_uevent_argv(env,subsystem);if(retval)gotoexit;retval=-ENOMEM;inf设备模型o=call_usermodehelper_setup
(env->argv[0],env->argv,env->envp,GFP_KERNEL,NULL,cleanup_uevent_env,env);if(info){retval=call_usermodehelper_exec
(info,UMH设备模型_NO_WAIT);env=NULL;/* freed by cleanup_uevent_env */}}#endif
exit:kfree(devpath);kfree(env);
returnretval;}EXPORT_SYMBOL_GPL(kobject_uevent_env设备模型);查找kobj本身或者其parent是否从属于某个kset,如果不是,则报错返回(注2:由此可以说明,如果一个kobject没有加入kset,是不允许上报uevent的)
查看kobj->uevent_suppress是否设置,如果设置,则忽略所有的uevent上报并返回(注3:由此可知,可以通过K设备模型object的uevent_suppress标志,管控Kobject的uevent的上报)
如果所属的kset有uevent_ops->filter函数,则调用该函数,过滤此次上报(注4:这佐证了3.2小节有关filter接口的说明,kset可以通过filter接口过滤不希望上报的event,从而达到设备模型整体的管理效果)
判断所属的kset是否有合法的名称(称作subsystem,和前期的内核版本有区别),否则不允许上报uevent分配一个用于此次上报的、存储环境变量的buffer(结果保存在env指针中),并获得该Kobject在sysfs中路径信息(用户空间软件需要依据该路径信息在sysfs中访问设备模型它)
调用add_uevent_var接口(下面会介绍),将Action、路径信息、subsystem等信息,添加到env指针中如果传入的envp不空,则解析传入的环境变量中,同样调用add_uevent_var接口,添加到env指针中
如果所属的kset存在uevent_ops->uevent接口,调设备模型用该接口,添加kset统一的环境变量到env指针根据ACTION的类型,设置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent变量,以记录正确的状态
调用add_uevent_var接口,添加格式为"SEQNUM=%llu”的序列号如果设备模型定义了"CONFIG_NET”,则使用netlink发送该uevent以uevent_helper、subsystem以及添加了标准环境变量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指针为参数,调用kmod模块提供的call_usermodehel设备模型per函数,上报uevent。
其中uevent_helper的内容是由内核配置项CONFIG_UEVENT_HELPER_PATH(位于./drivers/base/Kconfig)决定的(可参考lib/kobject_uevent.c, line 32),该配置项指定了一个用户空间程序(或者脚本)设备模型,用于解析上报的uevent,例如"/sbin/hotplug”。
call_usermodehelper的作用,就是fork一个进程,以uevent为参数,执行uevent_helperkobject_uevent,和kobject_uevent_env功能一样,只是没有指定任何的环境变量。
/**
设备模型 * kobject_uevent - notify userspace by sending an uevent
*
* @kobj: struct kobject that the action is happening to
* @action: a设备模型ction that is happening
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
设备模型 */intkobject_uevent(structkobject*kobj,enumkobject_actionaction
){returnkobject_uevent_env(kobj,action,NULL);}EXPORT_SYMBOL_GPL(kobject_uevent);add_ue设备模型vent_var,以格式化字符的形式(类似printf、printk等),将环境变量copy到env指针中。
/**
* add_uevent_var - add key value string to the environment buffer
* @env: en设备模型vironment buffer structure
* @format: printf format for the key=value pair
*
* Returns 0 if environment variable was added successfully设备模型 or -ENOMEM
* if no space was available.
*/intadd_uevent_var(structkobj_uevent_env*env,constchar*format,...){va_list
args;intlen;if(env->envp_id设备模型x>=ARRAY_SIZE(env->envp)){WARN(1,KERN_ERR"add_uevent_var: too many keys\n"
);return-ENOMEM;}va_start(args,format);len=vsnprintf(&env->buf[env->buflen],设备模型sizeof(env->buf)-env->buflen
,format,args);va_end(args);if(len>=(sizeof(env->buf)-env->buflen)){WARN(1,KERN_ERR"add_uevent_var: buffer size too small
\n设备模型");return-ENOMEM;}env->envp[env->envp_idx++]=&env->buf[env->buflen];env->buflen+=len+1;return0;}EXPORT_SYMBOL_GPL
(add_uevent_var);kobject_action_type,设备模型将enum kobject_action类型的Action,转换为字符串staticintkobject_action_type。
(constchar*buf,size_tcount,enumkobject_action*type,constchar**args){enumkobject_actio设备模型naction;size_tcount_first
;constchar*args_start;intret=-EINVAL;if(count&&(buf[count-1]==\n||buf[count-1]==\0))count--;if(!count
)gotoout;args_start=strn设备模型chr(buf,count,);if(args_start){count_first=args_start-buf;args_start=args_start
+1;}elsecount_first=count;for(action=0;action
[action],buf,count_first)!设备模型=0)continue;if(kobject_actions[action][count_first]!=\0)continue;if(args)*args
=args_start;*type=action;ret=0;break;}out:returnret;}怎么指定处理uevent的用户空间程序设备模型(简称uevent helper)?上面介绍kobject_uevent_env的内部动作时,有提到,Uevent模块通过Kmod上报Uevent时,会通过call_usermodehelper函数,调用用户空间的可执行文件(或者脚本,简称uevent helper)处理该event。
而该ueven设备模型t helper的路径保存在uevent_helper数组中可以在编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢设备模型出等)。
因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式因此内核编译时,需要把该配置项留空在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。
这可以通过把helper的路径写入到"/sys/kerne设备模型l/uevent_helper”文件中实现实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码,这里不再详细描述。
转载者注:uevent helper这部分我也没有理解,等明白了设备模型,稍后补上,也可以去原文咨询原作者。
欢迎大家关注我的微信公众号——小白仓库 原创经验资料分享:包含但不仅限于FPGA、ARM、RISC-V、Linux、LabVIEW等软硬件开发,另外分享生活中的趣事以及感悟目的是建立一个平台记录学习过的知识,并分享出来自认为有用的与感兴趣的道友相互交流进步。
Copyright © 2002-2020 上海润之模型设计有限公司 版权所有 展示模型,展品模型,展厅模型,展示道具,展厅展品,展品道具,模型定制,模型公司,上海模型公司 备案号:沪ICP备20018260号