热插拔(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);
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