linux设备驱动模型浅析3--设备热插拔的支持

        热插拔(hotplug,打这个词的时候我常常想到热干面)不一定非要指类似U盘那样的插入拔出,此处的热插拔广义上讲,是指一个设备加入系统,内核如何通知用户空间。举个简单的例子,如果你的电脑中有块PCI网卡,针对该网卡的驱动程序以内核模块的形式被编译(obj-m),那么Linux系统在启动过程中是如何自动加载该网卡的驱动模块呢?大家都知道现在udev负责干这事,其实除了udev,还可以有其他的手法,你自己就可以这样做。

        
当然设备驱动程序一般不会和这些太底层的kobject/kset家伙打交道,因为更高层次的device,busdriverkobject/kset那一层的细节实现都给封装了起来。所以设备热插拔的uevent事件最终的源头来自于device_add,本帖这里肯定不会讨论devicedriver如何绑定那一摊子事情。下面看看device_add的源码,是如何实现uevent机制的:

 

1kobject, ktype, kset

 

kobject代表sysfs中的目录。

ktype代表kobject的类型,主要包含release函数和attr的读写函数。比如,所有的bus都有同一个bus_type;所有的class都有同一个class_type

kset包含了subsystem概念,kset本身也是一个kobject,所以里面包含了一个kobject对象。另外,kset中包含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, struct kobj_uevent_env *env);

这三个函数都与uevent相关。filter用于判断uevent是否要发出去。name用于得到subsystem的名字。uevent用于填充env变量。

 

2device_add流程(uevent内核部分)

<drivers/base/core.c>

    int device_add(struct device *dev)

    {

          ...

          kobject_uevent(&dev->kobj, KOBJ_ADD);

          ...

    }

热插拔的核心实现就那一个函数调用,这里device_add对应的是KOBJ_ADD,那么移除设备自然对应KOBJ_REMOVE了。kobject_uevent函数最终调用的是kobject_uevent_env,后者才是真正干事的伙计。
   
下面给出kobject_uevent_env函数的核心框架:

   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 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. */

      //如果kset中有filter函数,调用filter函数,看看是否需要过滤uevent消息。

        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;

                  }

 

        /* originating subsystem */

      //如果kset中有name函数,调用name函数得到subsystem的名字;否则,subsystem的名字是

      //ksetkobject的名字。

        if (uevent_ops && uevent_ops->name)

                  subsystem = uevent_ops->name(kset, kobj);

        else

                  subsystem = kobject_name(&kset->kobj);

        if (!subsystem) {

                  pr_debug("unset subsystem caused the event to drop!\n");

                  return 0;

        }

 

        /* environment buffer */

        env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);

        if (!env)

                  return -ENOMEM;

 

        /* complete object path */

        devpath = kobject_get_path(kobj, GFP_KERNEL);

        if (!devpath) {

                  retval = -ENOENT;

                  goto exit;

        }

 

        /* default keys */

      //增加环境变量ACTION=<action name>

        retval = add_uevent_var(env, "ACTION=%s", action_string);

        if (retval)

                  goto exit;

      //增加环境变量DEVPATH=<kobjs path>

        retval = add_uevent_var(env, "DEVPATH=%s", devpath);

        if (retval)

                  goto exit;

      //增加环境变量SUBSYSTEM=<subsystem name>

        retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);

        if (retval)

                  goto exit;

 

        /* keys passed in from the caller */

      //增加环境变量kobject_uevent_env中参数envp_ext指定的环境变量。

        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 */

      //调用ksetuevent函数,这个函数会继续填充环境变量。

        if (uevent_ops && uevent_ops->uevent) {

                  retval = uevent_ops->uevent(kset, kobj, env);

                  if (retval) {

                           pr_debug ("%s - uevent() returned %d\n",

                                     __FUNCTION__, retval);

                           goto exit;

                  }

        }

 

        /* we will send an event, so request a new sequence number */

        spin_lock(&sequence_lock);

      //增加环境变量SEQNUM=<seq>,这里seq是静态变量,每次累加。

        seq = ++uevent_seqnum;

        spin_unlock(&sequence_lock);

        retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);

        if (retval)

                  goto exit;

//调用netlink发送uevent消息。

#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

 

        /* call uevent_helper, usually only enabled during early boot */

     //调用uevent_helper,最终转换成对用户空间sbin/mdev的调用。

     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;

}

 

 3uevent的用户空间部分 

uevent的用户空间程序有两个,一个是udev,一个是mdev

udev通过netlink监听uevent消息,它能完成两个功能:

       1.自动加载模块

       2.根据uevent消息在dev目录下添加、删除设备节点。

另一个是mdevmdevbusybox的代码包中能找到,它通过上节提到的uevent_helper函数被调用。

 

下面简要介绍udev的模块自动加载过程:

etc目录下有一个uevent规则文件/etc/udev/rules.d/50-udev.rules

udev程序收到uevent消息后,在这个规则文件里匹配,如果匹配成功,则执行这个匹配定义的shell命令。例如,规则文件里有这么一行:

ACTION=="add", SUBSYSTEM=="?*", ENV{MODALIAS}=="?*", RUN+="/sbin/modprobe $env{MODALIAS}"

所以,当收到ueventadd事件后,shell能自动加载在MODALIAS中定义的模块。

 

mdev的模块自动加载过程与之类似,它的配置文件在/etc/mdev.conf中。例如:

$MODALIAS=.* 0:0 660 @modprobe "$MODALIAS"

这条规则指的是:当收到的环境变量中含有MODALIAS,那么加载MODALIAS代表的模块。

mdev的详细说明在busyboxdocs/mdev.txt中。

 

4uevent在设备驱动模型中的应用

sys目录下有一个子目录devices,代表一个kset

创建设备时,调用的device_initialize函数中,默认会把kset设置成devices_kset,即devices子目录代表的kset

devices_kset中设置了uevent操作集device_uevent_ops

static struct kset_uevent_ops device_uevent_ops = {

       .filter =    dev_uevent_filter,

       .name =   dev_uevent_name,

       .uevent = dev_uevent,

};

 

dev_uevent_filter中,主要是规定了要想发送ueventdev必须有class或者bus

dev_uevent_name中,返回devclass或者bus的名字。

dev_uevent函数:

      如果dev有设备号,添加环境变量MAJORMINOR

      如果dev->type有值,设置DEVTYPE=<dev->type->name>

      如果dev->driver,设置DRIVER=<dev->driver->name>

      如果有bus,调用busuevent函数。

      如果有class,调用classuevent函数。

如果有dev->type,调用dev->type->uevent函数。

 

一般在busuevent函数中,都会添加MODALIAS环境变量,设置成dev的名字。这样,uevent传到用户空间后,就可以通过对MODALIAS的匹配自动加载模块。这样的bus例子有platformI2C等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值