熱插拔機制: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