熱插拔(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