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到用户空间。
- 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创建一个进程执行的。具体是汇编,比较难懂。
- /*
- * 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");
还有几个具体也不分析了,大概了解一下它的主要流程就好了。