Chinaunix首页 | 论坛 | 博客
  • 博客访问: 227698
  • 博文数量: 49
  • 博客积分: 2101
  • 博客等级: 大尉
  • 技术积分: 525
  • 用 户 组: 普通用户
  • 注册时间: 2010-09-07 10:38
文章分类

全部博文(49)

文章存档

2010年(49)

我的朋友

分类: 嵌入式

2010-10-06 22:06:36

刚刚学习了设备模型和sysfs,现在来看看热插拔的细节。内核版本是linux2.6.21

 

1 引子-热插拔的产生

         最常见的热插拔设备就是usb设备了,那就拿usb设备举例好了。当usb器件(U盘、usb鼠标等)插入计算机的usb接口时,usb控制器会发现usb器件的插入(linuxusb 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套接字和内核通信,修改内核中的路由信息表。

         而对于热插拔事件来说,就需要在用户空间实现一个系统后台服务用于监听套接字,处理任何收到的事件。实际上,这个守护任务就是udevdudev通过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套接字将内核事件送到用户空间。送出的值包括kobjsys中的路径、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套接字将内核事件送到用户空间。送出的值包括kobjsys中的路径、action以及环境变量envp;这些值是在(4)、(5)、(6)、(7)中获取的。

         综合上面的过程,发现kset中的uevent_ops扮演了非常重要的角色,下面来研究一下。

3.2 kset中的uevent_ops

         ldd3linux设备模型那一章的热插拔事件产生那一小节中,重点介绍了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-bushttp://blog.csdn.net/colorant/archive/2008/07/04/2611559.aspx

阅读(1677) | 评论(0) | 转发(0) |
0

上一篇:Linux设备模型与sysfs文件系统

下一篇:没有了

给主人留下些什么吧!~~