2010年(49)
分类: 嵌入式
2010-10-06 22:06:36
刚刚学习了设备模型和sysfs,现在来看看热插拔的细节。内核版本是linux2.6.21。
1 引子-热插拔的产生
最常见的热插拔设备就是usb设备了,那就拿usb设备举例好了。当usb器件(U盘、usb鼠标等)插入计算机的usb接口时,usb控制器会发现usb器件的插入(linux中usb core会控制usb控制器),而后在设备模型中的相应位置添加一个嵌入kobject的对象代表usb器件。kobject对应sysfs的一个目录,因此在sysfs文件系统下能找到该设备。
在像设备模型添加kobject时,使用的操作函数是kobject_register()。来分析一下这个函数。
int kobject_register(struct kobject * kobj) { int error = -EINVAL; if (kobj) { kobject_init(kobj); error = kobject_add(kobj); if (!error) kobject_uevent(kobj, KOBJ_ADD); } return error; } |
该函数首先初始化要注册的kobj,而后条用kobject_add()函数添加kobject到设备模型中,最后调用kobject_uevent()函数通知用户层。kobject_add()函数的细节就不分析了,只要明确其作用即可。现在考虑是热插拔的问题:内核热插拔事件是如何传递到用户层的。那就是kobject_uevent()函数来完成的了。在《linux内核设计与实现》中,有“内核事件层”这样的术语,那下面就能内核事件层开始讨论。
2 内核事件层
内核事件层实现了内核到用户的消息通知系统,在内核中,认为事件都是从幕后的kobject对象产生的。每个事件都被赋予了一个动词或动作字符串表示信号。
从内部实现来讲,内核事件由内核空间传递到用户空间需要使用netlink套接字。netlink套接字用于用户层和内核层的通信,因此用户空间获取内核事件就如同在套接字上堵塞一样易如反掌。我曾经调试过基于zebra的动态路由rip,那里面zebra守护进程就是使用netlink套接字和内核通信,修改内核中的路由信息表。
而对于热插拔事件来说,就需要在用户空间实现一个系统后台服务用于监听套接字,处理任何收到的事件。实际上,这个守护任务就是udevd。udev通过NetLink注册内核的设备事件,当有设备插入/拔除时,udev就会收到通知,它会从事件中所带参数和sysfs中的信息,加载适当的驱动程序,创建dev下的结点,让设备处于可用的状态。原先热插拔在用户层 缺省使用/sbin/hotplug,不过现在被udevd替代了。
说到这里,就要分两部分继续讨论了:一个是内核发送事件的细节;另一个就是用户层处理内核事件的细节了。
3 内核发送事件细节
3.1 kobject_uevnet()函数
从前面看到,内核事件的发送是调用kobject_uevent()函数完成的。在kobject_register()中如下调用:
kobject_uevent(kobj, KOBJ_ADD); |
在kobject_unregister()中则如下调用:
kobject_uevent(kobj, KOBJ_REMOVE); |
从这两个调用的例子来看,kobject_uevent()函数的第一个参数就是发出内核事件的kobject,而第二个参数就是通知用户层的动作action。下面看一下该函数的原型。
int kobject_uevent(struct kobject *kobj, enum kobject_action action) { return kobject_uevent_env(kobj, action, NULL); } |
函数的第二个入参,是一个enum变量,里面枚举了可能的动作。
enum kobject_action { KOBJ_ADD = (__force kobject_action_t) 0x01, /* exclusive to core */ KOBJ_REMOVE = (__force kobject_action_t) 0x02, /* exclusive to core */ KOBJ_CHANGE = (__force kobject_action_t) 0x03, /* device state change */ KOBJ_MOUNT = (__force kobject_action_t) 0x04, /* mount event for block devices (broken) */ KOBJ_UMOUNT = (__force kobject_action_t) 0x05, /* umount event for block devices (broken) */ KOBJ_OFFLINE = (__force kobject_action_t) 0x06, /* device offline */ KOBJ_ONLINE = (__force kobject_action_t) 0x07, /* device online */ KOBJ_MOVE = (__force kobject_action_t) 0x08, /* device move */ }; |
这个函数直接调用了kobject_uevent_env函数,继续贴源码。这个函数的大体思路注释在源码中。
/** * kobject_uevent_env - send an uevent with environmental data * * @action: action that is happening (usually KOBJ_MOVE) * @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. */ int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[]) { char **envp; char *buffer; char *scratch; const char *action_string; const char *devpath = NULL; const char *subsystem; struct kobject *top_kobj; struct kset *kset; struct kset_uevent_ops *uevent_ops; u64 seq; char *seq_buff; int i = 0; int retval = 0; int j; pr_debug("%s\n", __FUNCTION__); action_string = action_to_string(action); if (!action_string) { pr_debug("kobject attempted to send uevent without action_string!\n"); return -EINVAL; } /* search the kset we belong to */ 找到kobject所属的kset,如果找不到则return –EINVAL退出。寻找kset是因为kobject得热插拔是有kset中的uevent_ops来处理的。 top_kobj = kobj; if (!top_kobj->kset && top_kobj->parent) { do { top_kobj = top_kobj->parent; } while (!top_kobj->kset && top_kobj->parent); } if (!top_kobj->kset) { pr_debug("kobject attempted to send uevent without kset!\n"); return -EINVAL; } kset = top_kobj->kset; uevent_ops = kset->uevent_ops; /* skip the event, if the filter returns zero. */ 使用uevent_ops中的filter函数来过滤事件,如果事件被过滤掉,则直接返回0 if (uevent_ops && uevent_ops->filter) if (!uevent_ops->filter(kset, kobj)) { pr_debug("kobject filter function caused the event to drop!\n"); return 0; } /* environment index */ 申请环境变量envp所需指针空间 envp = kzalloc(NUM_ENVP * sizeof (char *), GFP_KERNEL); if (!envp) return -ENOMEM; /* environment values */ buffer = kmalloc(BUFFER_SIZE, GFP_KERNEL); if (!buffer) { retval = -ENOMEM; goto exit; } /* complete object path */ 获取kobj所在的路径 devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* originating subsystem */ 获取kobj所在子系统的名称 if (uevent_ops && uevent_ops->name) subsystem = uevent_ops->name(kset, kobj); else subsystem = kobject_name(&kset->kobj); /* event environemnt for helper process only */ envp[i++] = "HOME=/"; envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; /* default keys */ 设置默认环境变量 scratch = buffer; envp [i++] = scratch; scratch += sprintf(scratch, "ACTION=%s", action_string) + 1; envp [i++] = scratch; scratch += sprintf (scratch, "DEVPATH=%s", devpath) + 1; envp [i++] = scratch; scratch += sprintf(scratch, "SUBSYSTEM=%s", subsystem) + 1; for (j = 0; envp_ext && envp_ext[j]; j++) envp[i++] = envp_ext[j]; /* just reserve the space, overwrite it after kset call has returned */ envp[i++] = seq_buff = scratch; scratch += strlen("SEQNUM=18446744073709551616") + 1; /* let the kset specific function add its stuff */ 调用uevent_ops中的uevent函数来添加用户的环境变量 if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kset, kobj, &envp[i], NUM_ENVP - i, scratch, BUFFER_SIZE - (scratch - buffer)); if (retval) { pr_debug ("%s - uevent() returned %d\n", __FUNCTION__, retval); goto exit; } } /* we will send an event, request a new sequence number */ spin_lock(&sequence_lock); seq = ++uevent_seqnum; spin_unlock(&sequence_lock); sprintf(seq_buff, "SEQNUM=%llu", (unsigned long long)seq); #if defined(CONFIG_NET) /* send netlink message */ 通过netlink套接字将内核事件送到用户空间。送出的值包括kobj在sys中的路径、action以及环境变量。 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 + BUFFER_SIZE, GFP_KERNEL); if (skb) { /* add header */ scratch = skb_put(skb, len); sprintf(scratch, "%s@%s", action_string, devpath); /* copy keys to our continuous event payload buffer */ for (i = 2; envp[i]; i++) { len = strlen(envp[i]) + 1; scratch = skb_put(skb, len); strcpy(scratch, envp[i]); } NETLINK_CB(skb).dst_group = 1; netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL); } } #endif /* 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; call_usermodehelper (argv[0], argv, envp, 0); } exit: kfree(devpath); kfree(buffer); kfree(envp); return retval; } |
把注释总结起来,这个函数的主要过程是:
(1)找到kobject所属的kset,如果找不到则return –EINVAL退出。寻找kset是因为kobject得热插拔是有kset中的uevent_ops来处理的。
(2)使用uevent_ops中的filter函数来过滤事件,如果事件被过滤掉,则直接返回0
(3)申请环境变量envp所需指针空间以及设置evnp所需的临时buffer
(4)获取kobj所在的路径
(5)获取kobj所在子系统的名称
(6)设置默认环境变量evnp
(7)调用uevent_ops中的uevent函数来添加用户的环境变量
(8) 通过netlink套接字将内核事件送到用户空间。送出的值包括kobj在sys中的路径、action以及环境变量envp;这些值是在(4)、(5)、(6)、(7)中获取的。
综合上面的过程,发现kset中的uevent_ops扮演了非常重要的角色,下面来研究一下。
3.2 kset中的uevent_ops
ldd3在linux设备模型那一章的热插拔事件产生那一小节中,重点介绍了uevent_ops数据结构。不过我买的第三版的ldd3,它基于的2.6内核版本较低,所以介绍的是原来的kset_hotplug_ops数据结构,而现在内核将其数据结构名称改为kset_uevent_ops,不过这两种结构里面的内容一样的。看一下源码如下。
struct kset_uevent_ops { int (*filter)(struct kset *kset, struct kobject *kobj); const char *(*name)(struct kset *kset, struct kobject *kobj); int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp, int num_envp, char *buffer, int buffer_size); }; |
对于这部分,ldd3讲的很清楚了,而且还有例子,就没必要自己总结啦。一般来说,kset的这部分代码是不需要自己写的。
4 用户层udevd细节分析
这部分我也没什么可以参考的书籍,只是在网络上搜索学习一下。有一篇博客写的不错,分享一下。
浅谈hotplug, udev, hal, d-bus:http://blog.csdn.net/colorant/archive/2008/07/04/2611559.aspx