Lnux2.6内核udev机制(基于2.6.26)
作者:guolele1990 2011年1月12日
最近在研究输入子系统时关于应用层与handler层时有点迷惑,查找了很多资料,发现是设备节点问题,于是看起udev(嵌入式中的是mdev)机制,下面是这个机制的分析。
首先我们知道udev或者mdev都是应用程序,他们的作用就是起自动创建节点作用,这点是必须清楚,也就是它是工作在用户空间的,但是我们设备驱动,无论是event handler层还是driver 层都是工作在内核空间的,那么内核是怎样处理的?其实内核就是利用kobject_uevent来将这件事报告给用户空间,用户空间再调用相应程序(udev、mdev)来创建设备文件(或叫设备节点)。
下面分析kobject_uevent,老风格,先上内核描述
/**
* kobject_uevent - notify userspace by ending an uevent
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
这里就是说这函数是通过结束一个uevent来给用户空间报告,不过感觉翻译不对头,因为后面分析这不是结束这uevent,而是最终调用指定的uevent_helper或者是通过netlink发出(这是一种特殊类型的socket,专门用于内核空间与用户空间的异步通信,比较有效率,就是通过网络发送通知)去。这函数就是kobject_uevent_env的包装。
上kobject_uevent_env描述,更清楚
/**
* kobject_uevent_env - send an uevent with environmental data
*
* @action: action that is happening
* @kobj: struct kobject that the action is happening to
* @envp_ext: pointer to environmental data
*
* Returns 0 if kobject_uevent() is completed with success or the
* corresponding error when it fails.
*/
它的作用就是发送一个有参数的uevent到用户空间。
[cpp] view plain copy print ?
- int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
-
- char *envp_ext[])
-
- {
-
- ……
-
-
-
- /* 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 "
-
- "without kset!/n", kobject_name(kobj), kobj,
-
- __func__);
-
- return -EINVAL;
-
- }
-
- /*这里要说明一下,这个uevent_ops其实就是对应内核里的(系统一共有 block_uevent_ops memory_uevent_ops device_uevent_ops class_uevent_ops module_uevent_ops )这几大类 kset_uevent_ops*/
-
- //这里是为device_uevent_ops,因为现在调用的是创建设备的文件,更深一层的就是一开始device_init时定义的kset,现在我们用的都是这kset所以也就指定了这一个。
-
- kset = top_kobj->kset;
-
- uevent_ops = kset->uevent_ops;
-
-
-
- /* 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;
-
- }
-
- //如果定义了uevent_ops里的name就调用,因为这里有,调用的翻回名字
-
- /* originating subsystem */
-
- if (uevent_ops && uevent_ops->name)
-
- subsystem = uevent_ops->name(kset, kobj);
-
- else
-
- subsystem = kobject_name(&kset->kobj);
-
- …….
-
- /* environment buffer */
-
- ……
-
- //获得路径
-
- /* complete object path */
-
- devpath = kobject_get_path(kobj, GFP_KERNEL);
-
- if (!devpath) {
-
- retval = -ENOENT;
-
- goto exit;
-
- }
-
- //填充参数
-
- /* 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;
-
-
-
- /* keys passed in from the caller */
-
- if (envp_ext) {
-
- for (i = 0; envp_ext[i]; i++) {
-
- retval = add_uevent_var(env, envp_ext[i]);
-
- if (retval)
-
- goto exit;
-
- }
-
- }
-
-
-
- /* let the kset specific function add its stuff */
-
- if (uevent_ops && uevent_ops->uevent) {
-
- retval = uevent_ops->uevent(kset, kobj, env);
-
- ……
-
- }
-
-
-
- /*
-
- * 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.
-
- */
-
- if (action == KOBJ_ADD)
-
- kobj->state_add_uevent_sent = 1;
-
- else if (action == KOBJ_REMOVE)
-
- kobj->state_remove_uevent_sent = 1;
-
- //这个uevent_seqnum是会在/sys/kernel/ /uevent_sequence里显示,是表示产生·过几个uevent
-
- /* 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;
-
-
-
- //这个在新版里如果是采用网络通报的话,应用层里就采用socket来接受这event,这样比较有效率,不用整天去那读
-
- #if defined(CONFIG_NET)
-
- /* 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;
-
- netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL);
-
- }
-
- }
-
- #endif
-
- //这里是先判断uevent_helper是否为空,它是什么?他默认是/proc/sys/kernel/hotplug,但是我们可以通过echo /sbin/mdev > /proc/sys/kernel/hotplug来修改,这也是很多嵌入式初始化文件里要做的事
-
- /* 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;
-
-
-
- call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
-
- }
-
-
-
- exit:
-
- kfree(devpath);
-
- kfree(env);
-
- return retval;
-
- }
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]){ …… /* 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 " "without kset!/n", kobject_name(kobj), kobj, __func__); return -EINVAL; }/*这里要说明一下,这个uevent_ops其实就是对应内核里的(系统一共有 block_uevent_ops memory_uevent_ops device_uevent_ops class_uevent_ops module_uevent_ops )这几大类 kset_uevent_ops*///这里是为device_uevent_ops,因为现在调用的是创建设备的文件,更深一层的就是一开始device_init时定义的kset,现在我们用的都是这kset所以也就指定了这一个。 kset = top_kobj->kset; uevent_ops = kset->uevent_ops; /* 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; }//如果定义了uevent_ops里的name就调用,因为这里有,调用的翻回名字 /* originating subsystem */ if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kset, kobj); else subsystem = kobject_name(&kset->kobj);……. /* environment buffer */ ……//获得路径 /* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; }//填充参数 /* 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; /* keys passed in from the caller */ if (envp_ext) { for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, envp_ext[i]); if (retval) goto exit; } } /* let the kset specific function add its stuff */ if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kset, kobj, env); …… } /* * 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. */ if (action == KOBJ_ADD) kobj->state_add_uevent_sent = 1; else if (action == KOBJ_REMOVE) kobj->state_remove_uevent_sent = 1;//这个uevent_seqnum是会在/sys/kernel/ /uevent_sequence里显示,是表示产生·过几个uevent /* 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; //这个在新版里如果是采用网络通报的话,应用层里就采用socket来接受这event,这样比较有效率,不用整天去那读#if defined(CONFIG_NET) /* 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; netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL); } }#endif//这里是先判断uevent_helper是否为空,它是什么?他默认是/proc/sys/kernel/hotplug,但是我们可以通过echo /sbin/mdev > /proc/sys/kernel/hotplug来修改,这也是很多嵌入式初始化文件里要做的事 /* 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; call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC); } exit: kfree(devpath); kfree(env); return retval;}
而call_usermodehelper,里setup就是将一些信息放在struct subprocess_info结构里,方便调用call_usermodehelper_exec,关于这函数,上一描述
/**
* call_usermodehelper_exec - start a usermode application
* @sub_info: information about the subprocessa
* @wait: wait for the application to finish and return status.
* when -1 don't wait at all, but you get no useful error back when
* the program couldn't be exec'ed. This makes it safe to call
* from interrupt context.
*
* Runs a user-space application. The application is started
* asynchronously if wait is not set, and runs as a child of keventd.
* (ie. it runs with full root capabilities).
*/
这函数的就是调用用户模式的应用程序,也就是调用mdev或者udev.其中udev、mdev是内核通过vfork创建一个进程执行的。具体是汇编,比较难懂。
[cpp] view plain copy print ?
- /*
-
- * We were successful. We won't be returning to our caller, but
-
- * instead to user space by manipulating the kernel stack.
-
- */
-
- asm( "add r0, %0, %1/n/t"
-
- "mov r1, %2/n/t"
-
- "mov r2, %3/n/t"
-
- "bl memmove/n/t" /* copy regs to top of stack */
-
- "mov r8, #0/n/t" /* not a syscall */
-
- "mov r9, %0/n/t" /* thread structure */
-
- "mov sp, r0/n/t" /* reposition stack pointer */
-
- "b ret_to_user"
-
- :
-
- : "r" (current_thread_info()),
-
- "Ir" (THREAD_START_SP - sizeof(regs)),
-
- "r" (®s),
-
- "Ir" (sizeof(regs))
-
- : "r0", "r1", "r2", "r3", "ip", "lr", "memory");
/* * We were successful. We won't be returning to our caller, but * instead to user space by manipulating the kernel stack. */asm( "add r0, %0, %1/n/t" "mov r1, %2/n/t" "mov r2, %3/n/t" "bl memmove/n/t" /* copy regs to top of stack */ "mov r8, #0/n/t" /* not a syscall */ "mov r9, %0/n/t" /* thread structure */ "mov sp, r0/n/t" /* reposition stack pointer */ "b ret_to_user" : : "r" (current_thread_info()), "Ir" (THREAD_START_SP - sizeof(regs)), "r" (®s), "Ir" (sizeof(regs)) : "r0", "r1", "r2", "r3", "ip", "lr", "memory");
还有几个具体也不分析了,大概了解一下它的主要流程就好了。