热插拔机制:hotplug


热插拔(hotplug)  

热插拔:在不重启系统的情况下,增减硬件设备。

本文主要介绍linux下的热插拔

热插拔:最要还是由于驱动实现了向用户态通知,

(1)外设插入,硬件中断响应

(2)总线发现新的设备,驱动调用device_add(设备驱动??)

(3)device_add调用kobject_uevent(, KOBJ_ADD),向用户空间广播新设备加入事件通知;这里发出通知的方式,就是netlink;

(4)用户空间运行的daemon(udev)收到event事件广播;

(5)udev根据消息和环境变量,查询sysfs中的/sys的变化,按照规则(/etc/udev/rules.d/*),在/dev目录下自动创建设备节点;

(6)运行 /sbin/hotplug 脚本

 1、普通用户热插拔

 当用户向系统添加或删除设备时,内核会产生一个热插拔事件,并在/proc/sys/kernel/hotplug文件里查找处理设备连接的用户空间程序,这个用户空间程序主要有/sbin/hotplug与/sbin/mdev. 

echo /sbin/hotplug > /proc/sys/kernel/hotplug 
或者 
echo /sbin/mdev > /proc/sys/kernel/hotplug 
mdev -s
也就是只需要实现/sbin/hotplug或者/sbin/mdev脚本。

 2、快速实现热插拔(hotplug)

(1)在/sbin/hotplug文件中对ACTION环境变量处理
后续增加!!!

3、热插拔原理

笔者以USB转TTY驱动为例子,事例代码内核版本:5.14.11

3.1 USB设备驱动probe

/drivers/usb/serial/usb-serial.c文件中
static int usb_serial_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
........ retval
= device_add(&port->dev);
........
}

usb_serial_probe函数为USB设备和USB转TTY驱动匹配后,执行的probe函数。
usb_serial_probe---》device_add函数。将设备添加到linux设备模型中
device_add--》kobject_uevent

3.2 kobjet、uevent

/drivers/base/core.c文件中
int device_add(struct device *dev)
{
    .....
    kobject_uevent(&dev->kobj, KOBJ_ADD);
    .....

kobject_uevent这个函数原型如下,就是向用户空间发送uevent,可以理解为驱动(内核态)向用户(用户态)发送了一个KOBJ_ADD。

/**
 * kobject_uevent - notify userspace by sending an uevent
 *
 * @kobj: struct kobject that the action is happening to
 * @action: action that is happening
 *
 * Returns 0 if kobject_uevent() is completed with success or the
 * corresponding error when it fails.
 */
int kobject_uevent(struct kobject *kobj, enum kobject_action action)
{
    return kobject_uevent_env(kobj, action, NULL);
}
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp_ext[])

 kobject_uevent--》kobject_uevent_env

 uevent:有新的设备加入时,用将设备信息发送到用户态。户态udev进程监听这个信息,解析并结合用户程序的配置做一些处理(也包括加载驱动程序)。

uevent上报设备信息的API为kobject_uevent、kobject_uevent_env,笔者可只追踪到此处(uevent内核实现的netlink可只做了解)。

uevent是kobject的一部分,用于在kobject状态发生改变时,例如增加、移除等,通知用户空间程序。用户空间程序收到这样的事件后,会做相应的处理。

/*
 * The actions here must match the index to the string array
 * in lib/kobject_uevent.c
 *
 * Do not add new actions here without checking with the driver-core
 * maintainers. Action strings are not meant to express subsystem
 * or device specific properties. In most cases you want to send a
 * kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
 * specific variables added to the event environment.
 */
enum kobject_action {
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_BIND,
    KOBJ_UNBIND,
};

kobject_action定义了event的类型,包括:

action 意义
ADD/REMOVE kobject(或上层数据结构)的添加/移除事件。
ONLINE/OFFLINE kobject(或上层数据结构)的上线/下线事件,其实是是否使能。
CHANGE kobject(或上层数据结构)的状态或者内容发生改变。
MOVE kobject(或上层数据结构)更改名称或者更改parent(意味着在sysfs中更改了目录结构)。
CHANGE 如果设备驱动需要上报的事件不再上面事件的范围内,或者是自定义的事件,可以使用该event,并携带相应的参数
  1 /**
  2  * kobject_uevent_env - send an uevent with environmental data
  3  *
  4  * @kobj: struct kobject that the action is happening to
  5  * @action: action that is happening
  6  * @envp_ext: pointer to environmental data
  7  *
  8  * Returns 0 if kobject_uevent_env() is completed with success or the
  9  * corresponding error when it fails.
 10  */
 11 int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
 12                char *envp_ext[])
 13 {
 14     struct kobj_uevent_env *env;
 15     const char *action_string = kobject_actions[action];
 16     const char *devpath = NULL;
 17     const char *subsystem;
 18     struct kobject *top_kobj;
 19     struct kset *kset;
 20     const struct kset_uevent_ops *uevent_ops;
 21     int i = 0;
 22     int retval = 0;
 23 
 24     /*
 25      * Mark "remove" event done regardless of result, for some subsystems
 26      * do not want to re-trigger "remove" event via automatic cleanup.
 27      */
 28     if (action == KOBJ_REMOVE)
 29         kobj->state_remove_uevent_sent = 1;
 30 
 31     pr_debug("kobject: '%s' (%p): %s\n",
 32          kobject_name(kobj), kobj, __func__);
 33 
 34     /* search the kset we belong to */
 35     top_kobj = kobj;
 36     while (!top_kobj->kset && top_kobj->parent)
 37         top_kobj = top_kobj->parent;
 38 
 39     if (!top_kobj->kset) {
 40         pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
 41              "without kset!\n", kobject_name(kobj), kobj,
 42              __func__);
 43         return -EINVAL;
 44     }
 45 
 46     kset = top_kobj->kset;
 47     uevent_ops = kset->uevent_ops;
 48 
 49     /* skip the event, if uevent_suppress is set*/
 50     if (kobj->uevent_suppress) {
 51         pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
 52                  "caused the event to drop!\n",
 53                  kobject_name(kobj), kobj, __func__);
 54         return 0;
 55     }
 56     /* skip the event, if the filter returns zero. */
 57     if (uevent_ops && uevent_ops->filter)
 58         if (!uevent_ops->filter(kset, kobj)) {
 59             pr_debug("kobject: '%s' (%p): %s: filter function "
 60                  "caused the event to drop!\n",
 61                  kobject_name(kobj), kobj, __func__);
 62             return 0;
 63         }
 64 
 65     /* originating subsystem */
 66     if (uevent_ops && uevent_ops->name)
 67         subsystem = uevent_ops->name(kset, kobj);
 68     else
 69         subsystem = kobject_name(&kset->kobj);
 70     if (!subsystem) {
 71         pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
 72              "event to drop!\n", kobject_name(kobj), kobj,
 73              __func__);
 74         return 0;
 75     }
 76 
 77     /* environment buffer */
 78     env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
 79     if (!env)
 80         return -ENOMEM;
 81 
 82     /* complete object path */
 83     devpath = kobject_get_path(kobj, GFP_KERNEL);
 84     if (!devpath) {
 85         retval = -ENOENT;
 86         goto exit;
 87     }
 88 
 89     /* default keys */
 90     retval = add_uevent_var(env, "ACTION=%s", action_string);
 91     if (retval)
 92         goto exit;
 93     retval = add_uevent_var(env, "DEVPATH=%s", devpath);
 94     if (retval)
 95         goto exit;
 96     retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
 97     if (retval)
 98         goto exit;
 99 
100     /* keys passed in from the caller */
101     if (envp_ext) {
102         for (i = 0; envp_ext[i]; i++) {
103             retval = add_uevent_var(env, "%s", envp_ext[i]);
104             if (retval)
105                 goto exit;
106         }
107     }
108 
109     /* let the kset specific function add its stuff */
110     if (uevent_ops && uevent_ops->uevent) {
111         retval = uevent_ops->uevent(kset, kobj, env);
112         if (retval) {
113             pr_debug("kobject: '%s' (%p): %s: uevent() returned "
114                  "%d\n", kobject_name(kobj), kobj,
115                  __func__, retval);
116             goto exit;
117         }
118     }
119 
120     switch (action) {
121     case KOBJ_ADD:
122         /*
123          * Mark "add" event so we can make sure we deliver "remove"
124          * event to userspace during automatic cleanup. If
125          * the object did send an "add" event, "remove" will
126          * automatically generated by the core, if not already done
127          * by the caller.
128          */
129         kobj->state_add_uevent_sent = 1;
130         break;
131 
132     case KOBJ_UNBIND:
133         zap_modalias_env(env);
134         break;
135 
136     default:
137         break;
138     }
139 
140     mutex_lock(&uevent_sock_mutex);
141     /* we will send an event, so request a new sequence number */
142     retval = add_uevent_var(env, "SEQNUM=%llu", ++uevent_seqnum);
143     if (retval) {
144         mutex_unlock(&uevent_sock_mutex);
145         goto exit;
146     }
147     retval = kobject_uevent_net_broadcast(kobj, env, action_string,
148                           devpath);
149     mutex_unlock(&uevent_sock_mutex);
150 
151 #ifdef CONFIG_UEVENT_HELPER
152     /* call uevent_helper, usually only enabled during early boot */
153     if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
154         struct subprocess_info *info;
155 
156         retval = add_uevent_var(env, "HOME=/");
157         if (retval)
158             goto exit;
159         retval = add_uevent_var(env,
160                     "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
161         if (retval)
162             goto exit;
163         retval = init_uevent_argv(env, subsystem);
164         if (retval)
165             goto exit;
166 
167         retval = -ENOMEM;
168         info = call_usermodehelper_setup(env->argv[0], env->argv,
169                          env->envp, GFP_KERNEL,
170                          NULL, cleanup_uevent_env, env);
171         if (info) {
172             retval = call_usermodehelper_exec(info, UMH_NO_WAIT);
173             env = NULL;    /* freed by cleanup_uevent_env */
174         }
175     }
176 #endif
177 
178 exit:
179     kfree(devpath);
180     kfree(env);
181     return retval;
182 }
183 EXPORT_SYMBOL_GPL(kobject_uevent_env);
View Code

 

3.3 netlink

初始化环境变量, 包括 HOME、PATH、ACTION、DEVPATH、SUBSYSTEM 等

 
   
#define UEVENT_HELPER_PATH_LEN		256
#define UEVENT_NUM_ENVP 64 /* number of env pointers */ #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */

struct kobj_uevent_env { char *argv[3]; char *envp[UEVENT_NUM_ENVP]; //指向buf中的每一个字符串 int envp_idx; //指明buf中的字符串的个数 char buf[UEVENT_BUFFER_SIZE]; //存放所有环节变量字符串 int buflen; //buf含有字符串的个数 };

"ACTION=add"和"DEVPATH=/dev/usb"保存在结构体中buf中,所有的动作都放在buf中,每个环境变量存储着该环境在buf中的起始位置。

env->envp[0]=&env->buf[0];

env->envp[1] = &env->buf[11];

env->buf[11]=‘D’。

buf中存放字符串如下:

A C T I O N = a d d \0 D E P A T H = / d e v / u s b \0  
 
  

argv [0] = uevent_helper、argv[1]存储了本kobj_uevent_env的buf指针、argv[2]一般为NULL。

 

kobject_uevent_env--》kobject_uevent_net_broadcast--》uevent_net_broadcast_untagged或者uevent_net_broadcast_tagged--》netlink_broadcast,实际上就是将buf中的内容发送到用户空间,用户空间udev监听到此消息,则解析。

/**
 * kobject_uevent_env - send an uevent with environmental data
 *
 * @kobj: struct kobject that the action is happening to
 * @action: action that is happening
 * @envp_ext: pointer to environmental data
 *
 * Returns 0 if kobject_uevent_env() 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[])
{
    retval = kobject_uevent_net_broadcast(kobj, env, action_string, devpath); } static int kobject_uevent_net_broadcast(struct kobject *kobj, struct kobj_uevent_env *env, const char *action_string, const char *devpath) { if (!net) ret = uevent_net_broadcast_untagged(env, action_string, devpath); else ret = uevent_net_broadcast_tagged(net->uevent_sock->sk, env, action_string, devpath); } static int uevent_net_broadcast_untagged(struct kobj_uevent_env *env, const char *action_string, const char *devpath) { retval = netlink_broadcast(uevent_sock, skb_get(skb), 0, 1, GFP_KERNEL); } static int uevent_net_broadcast_tagged(struct sock *usk, struct kobj_uevent_env *env, const char *action_string, const char *devpath) { ret = netlink_broadcast(usk, skb, 0, 1, GFP_KERNEL); }

 

 netlink_broadcast函数是netlink通信的API(netlink_broadcast是如何实现通信的,读者如果感兴趣自行追查下去)

3.4 usermode helper

 usermode helper用于帮助在内核空间启动一个用户空间程序。

首先通过call_usermodehelper_setup()初始化一个struct subprocess_info实例;

然后调用call_usermodehelper_exec()执行,通过kernel_thread()创建线程,入口函数call_usermodehelper_exec_async()调用do_execve()加载用户空间程序。

argv [0] = uevent_helper,uevent_helper存放着用户态可执行的进程。

旧的:编译内核时,通过CONFIG_UEVENT_HELPER_PATH配置项,静态指定uevent helper。但这种方式会为每个event fork一个进程,随着内核支持的设备数量的增多,这种方式在系统启动时将会是致命的(可以导致内存溢出等)。因此只有在早期的内核版本中会使用这种方式,现在内核不再推荐使用该方式。

新的:因此内核编译时,需要把该配置项留空。在系统启动后,大部分的设备已经ready,可以根据需要,重新指定一个uevent helper,以便检测系统运行过程中的热拔插事件。

这可以通过把helper的路径写入到"/sys/kernel/uevent_helper"文件中实现。实际上,内核通过sysfs文件系统的形式,将uevent_helper数组开放到用户空间,供用户空间程序修改访问,具体可参考"./kernel/ksysfs.c”中相应的代码。

    在/etc/init.d/rcS脚本中添加 echo "/sbin/mdev" > /proc/sys/kernel/hotplug,会发现cat /sys/kernel/uevent_helper 即是/sbin/mdev。说明/proc/sys/kernel/hotplug中的可执行文件路径最终还是会写到/sys/kernel/uevent_helper中。自己手动echo "/kernel/main" > uevent_helper(之前的/sbin/mdev会被覆盖),当lsmod、rmmod时,/sys/kernel/uevent_helper中的/kernel/main会执行,表明事件已经上报给用户空间。

 

 

 

 未完待续,明日继续!!!!!

 

 

参考链接:

https://www.cnblogs.com/schips/p/linux_device_model_4.html

http://blog.chinaunix.net/uid-30016340-id-4633121.html

 https://www.pianshen.com/article/5537271854/

 https://www.cnblogs.com/klb561/p/9245883.html

https://www.cnblogs.com/rockyching2009/tag/USB/

 uevent:http://www.wowotech.net/linux_kenrel/uevent.html

热插拔优化:https://blog.csdn.net/zjujoe/article/details/2986634

如何存储ACTION以及环境变量:https://blog.csdn.net/chengbeng1745/article/details/105307211

 介绍kobject_uevent_env函数内部细节:https://blog.csdn.net/bingqingsuimeng/article/details/7924473

南京:https://www.cnblogs.com/arnoldlu/p/11246204.html 

函数内部细节:https://blog.csdn.net/zjujoe/article/details/2986634

 https://blog.csdn.net/u012066426/article/details/51917369

https://www.cnblogs.com/black-mamba/p/5055683.html 

https://blog.csdn.net/W1107101310/article/details/80211885

 https://blog.csdn.net/bingqingsuimeng/category_1231880.html

https://www.cnblogs.com/fastwave2004/articles/4320725.html 

 

https://blog.csdn.net/weixin_41791581/article/details/109038043

https://www.cnblogs.com/xinghuo123/p/13269787.html


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM