linux 熱插拔hotplug


熱插拔
有 2 個不同角度來看待熱插拔:
   從內核角度看,熱插拔是在硬件、內核和內核驅動之間的交互。
   從用戶角度看,熱插拔是內核和用戶空間之間,通過調用用戶空間程序(如hotplug、udev 和 mdev)的交互。 當需要通知用戶內核發生了某種熱插拔事件時,內核才調用這個用戶空間程序。
現在的計算機系統,要求 Linux 內核能夠在硬件從系統中增刪時,可靠穩定地運行。這就對設備驅動作者增加了壓力,因為在他們必須處理一個毫無征兆地突然出現或消失的設備。


熱插拔工具
當用戶向系統添加或刪除設備時,內核會產生一個熱插拔事件,並在 /proc/sys/kernel/hotplug 文件里查找處理設備連接的用戶空間程序。這個用戶空間程序主要有

hotplug:這個程序是一個典型的 bash 腳本, 只傳遞執行權給一系列位於 /etc/hot-plug.d/ 目錄樹的程序。hotplug 腳本搜索所有的有 .hotplug 后綴的可能對這個事件進行處理的程序並調用它們, 並傳遞給它們許多不同的已經被內核設置的環境變量。(基本已被淘汰,具體內容請參閱《LDD3》)

udev :用於linux2.6.13或更高版本的內核上,為用戶空間提供使用固定設備名的動態/dev目錄的解決方案。它通過在 sysfs 的 /class/ 和/block/ 目錄樹中查找一個稱為 dev 的文件,以確定所創建的設備節點文件的主次設備號。所以要使用udev,驅動必須為設備在sysfs中創建類接口及其dev屬性文件,方法和sculld模塊中創建dev屬性相同。 udev的資料網上十分豐富,我就不在這廢話了,給出以下鏈接有興趣的自己研究:
UDEV Primer》(英文),地址: http://webpages.charter.net/decibelshelp/LinuxHelp_UDEVPrimer.html
 
《udev規則編寫》(luofuchong翻譯),地址: http://www.cnitblog.com/luofuchong/archive/2007/12/18/37831.html
 
 
《udev-FAQ 中文翻譯》地址: http://gnawux.bokee.com/3225765.html
 
 
 
 
在《LFS》中也有介紹udev的使用,很值得參考!下載地址: http://lfs.osuosl.org/lfs/downloads/stable/
 
 
mdev:一個簡化版的udev,是busybox所帶的程序,十分適合嵌入式系統。
 

因為hotplug現在也在被慢慢地淘汰,udev不再依賴hotplug了,所以這里不再介紹;

udev較mdev復雜,不太適合嵌入式使用。(本人也有做udev的實驗,交叉編譯是通過了,但是使用上有問題,沒有實現其功能。也許是我的文件系統沒做好,以后有時間再研究和寫記錄。有成功高人的通知一聲,交流一下經驗。^_^謝謝!);

mdev簡單易用,比較適合嵌入式系統,實驗成功。以下詳細介紹mdev的使用。

 

================================

 

 

設備節點的創建,是通過sysfs接口分析dev文檔取得設備節點號,這個很顯而易見。那么udevd是通過什么機制來得知內核里模塊的變化情況,如何得知設備的插入移除情況呢?當然是通過hotplug機制了,那 hotplug又是怎么實現的?或說內核是如何通知用戶空間一個事件的發生的呢?
答案是通過netlink socket通訊,在內核和用戶空間之間傳遞信息。
內核調用kobject_uevent函數發送netlink message給用戶空間,這部分工作通常無需驅動去自己處理,在統一設備模型里面,在子系統這一層面,已將這部分代碼處理好了,包括在設備對應的特定的 Kobject創建和移除的時候都會發送相應add和remove消息,當然前提是您在內核中配置了hotplug的支持。
Netlink socket作為一種內核和用戶空間的通信方式,不但僅用在hotplug機制中,同樣還應用在其他很多真正和網絡相關的內核子系統中。
Udevd通過標准的socket機制,創建socket連接來獲取內核廣播的uevent事件 並解析這些uevent事件

Udevtrigger的工作機制
運行udevd以后,使用udevtrigger的時候,會把內核中已存在的設備的節點創建出來,那么他是怎么做到這一點的? 分析udevtrigger的代碼能夠看出:
udevtrigger通過向/sysfs 文檔系統下現有設備的uevent節點寫"add"字符串,從而觸發uevent事件,使得udevd能夠接收到這些事件,並創建buildin的設備驅動的設備節點連同任何已insmod的模塊的設備節點。
所以,我們也能夠手工用命令行來模擬這一過程:
/ # echo "add" > /sys/block/mtdblock2/uevent
/ # 
/ # UEVENT[178.415520] add      /block/mtdblock2 (block)
但是,進一步看代碼,您會發現,實際上,不管您往uevent里面寫什么,都會觸發add事件,這個從kernel內部對uevent屬性的實現函數能夠看出來,默認的實現是:
static ssize_t store_uevent(struct device *dev, struct device_attribute *attr,
                         const char *buf, size_t count)
{
       kobject_uevent(&dev->kobj, KOBJ_ADD);
       return count;
}
所以不管寫的內容是什么,都是觸發add操作,真遺憾,我還想通過這個屬性實驗remove的操作。 不知道這樣限制的原因是什么。
而udevstart的實現方式和udevtrigger就不同了,他基本上是重復實現了udevd里面的機制,通過遍歷sysfs,自己完成設備節點的創建,不通過udevd來完成。
      udevd創建每一個節點的時候,都會fork出一個新的進程來單獨完成這個節點的創建工作。
      Uevent_seqnum 用來標識當前的uevent事件的序號(已產生了多少uevent事件),您能夠通過如下操作來查看:
$ cat /sys/kernel/uevent_seqnum
2673

      udev的工作原理 當系統內核發現安裝或者卸載了某一個硬件設備時,內核會執行hotplug,以便讓hotplug去安裝或卸載該硬件的驅動程序;hotplug在處理完硬件的驅動程序后,就會去呼叫執行udevd,以便讓udevd可以產生或者刪除硬件的設備文件。 接着udevd會通過libsysfs讀取sys文件系統,以便取得該硬件設備的信息;然后再向namedev查詢該外部設備的設備文件信息,例如文件的名稱、權限等。最后,udevd就依據上述的結果,在/dev/目錄中自動建立該外部設備的設備文件,同時在/etc/udev/rules.d下檢查有無針對該設備的使用權限

 

 

====================

1.kobject, 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變量。

2.uevent內核部分

uevent是sysfs向用戶空間發出的消息。比如,device_add函數中,會調用kobject_uevent(&dev->kobj, KOBJ_ADD); 這里kobj是發消息的kobj,KOBJ_ADD是發出的事件。uevent的事件在kobject_action中定義:

enum kobject_action {

       KOBJ_ADD,

       KOBJ_REMOVE,

       KOBJ_CHANGE,

       KOBJ_MOVE,

       KOBJ_ONLINE,

       KOBJ_OFFLINE,

       KOBJ_MAX

};

 

int kobject_uevent(struct kobject *kobj, enum kobject_action action)

{

       return kobject_uevent_env(kobj, action, NULL);

}

 

kobject_uevent_env:

       由kobject的parent向上查找,直到找到一個kobject包含kset。

       如果kset中有filter函數,調用filter函數,看看是否需要過濾uevent消息。

       如果kset中有name函數,調用name函數得到subsystem的名字;否則,subsystem的名字是kset中kobject的名字。

       分配一個kobj_uevent_env,並開始填充env環境變量:

       增加環境變量ACTION=<action name>

       增加環境變量DEVPATH=<kobj’s path>

       增加環境變量SUBSYSTEM=<subsystem name>

       增加環境變量kobject_uevent_env中參數envp_ext指定的環境變量。

       調用kset的uevent函數,這個函數會繼續填充環境變量。

       增加環境變量SEQNUM=<seq>,這里seq是靜態變量,每次累加。

       調用netlink發送uevent消息。

       調用uevent_helper,最終轉換成對用戶空間sbin/mdev的調用。

3.uevent用戶空間部分

uevent的用戶空間程序有兩個,一個是udev,一個是mdev。

udev通過netlink監聽uevent消息,它能完成兩個功能:

       1.自動加載模塊

       2.根據uevent消息在dev目錄下添加、刪除設備節點。

另一個是mdev,mdev在busybox的代碼包中能找到,它通過上節提到的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}"

所以,當收到uevent的add事件后,shell能自動加載在MODALIAS中定義的模塊。

 

mdev的模塊自動加載過程與之類似,它的配置文件在/etc/mdev.conf中。例如:

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

這條規則指的是:當收到的環境變量中含有MODALIAS,那么加載MODALIAS代表的模塊。

mdev的詳細說明在busybox的docs/mdev.txt中。

4.uevent在設備驅動模型中的應用

在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中,主要是規定了要想發送uevent,dev必須有class或者bus。

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

dev_uevent函數:

       如果dev有設備號,添加環境變量MAJOR與MINOR。

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

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

       如果有bus,調用bus的uevent函數。

       如果有class,調用class的uevent函數。

如果有dev->type,調用dev->type->uevent函數。

 

一般在bus的uevent函數中,都會添加MODALIAS環境變量,設置成dev的名字。這樣,uevent傳到用戶空間后,就可以通過對MODALIAS的匹配自動加載模塊。這樣的bus例子有platform和I2C等等。

==========================

 

熱插拔(hotplug,打這個詞的時候我常常想到熱干面)不一定非要指類似U盤那樣的插入拔出,此處的熱插拔廣義上講,是指一個設備加入系統,內核如何通知用戶空間。舉個簡單的例子,如果你的電腦中有塊PCI網卡,針對該網卡的驅動程序以內核模塊的形式被編譯(obj-m),那么Linux系統在啟動過程中是如何自動加載該網卡的驅動模塊呢?大家都知道現在udev負責干這事,其實除了udev,還可以有其他的手法,你自己就可以這樣做。

我們先討論udev,udev最關鍵的東西是當系統發現一個設備時,它要能夠被通知該事件,一旦它知道了這件事,那么余下的事情就都好說了,無非是個如何查找模塊並加載的過程。所以我們看到,這里的關鍵是熱插拔事件的通知機制。Linux的設備模型為此提供了非常完美的支持,其原理其實發源於kset這一層,對此在《深入Linux設備驅動程序內核機制》一書中有詳細的描述,雖然這部分看起來蠻復雜,貌似挺能嚇唬住一些新手,其實說白了,要點就是通過sysfs建立關系,溝通內核與用戶空間,然后就是uevent,也就是下面要說的熱插拔事件。

當然設備驅動程序一般不會和這些太底層的kobject/kset家伙打交道,因為更高層次的device,bus和driver把kobject/kset那一層的細節實現都給封裝了起來。所以設備熱插拔的uevent事件最終的源頭來自於device_add,本帖這里肯定不會討論device與driver如何綁定那一攤子事情。下面看看device_add的源碼,是如何實現uevent機制的:

  1.     <drivers/base/core.c>
  2.     int device_add(struct device *dev)
  3.     {
  4.           ...
  5.           kobject_uevent(&dev->kobj, KOBJ_ADD);
  6.           ...
  7.     }
復制代碼

熱插拔的核心實現就那一個函數調用,這里device_add對應的是KOBJ_ADD,那么移除設備自然對應KOBJ_REMOVE了。kobject_uevent函數最終調用的是kobject_uevent_env,后者才是真正干事的伙計。
下面給出kobject_uevent_env函數的核心框架:

  1.     int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
  2.                            char *envp_ext[])
  3.     {
  4.             ...
  5.     #if defined(CONFIG_NET)
  6.             /* send netlink message */
  7.             ...
  8.     #endif
  9.             /* call uevent_helper, usually only enabled during early boot */
  10.             if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
  11.                     char *argv [3];
  12.                     argv [0] = uevent_helper;
  13.                     argv [1] = (char *)subsystem;
  14.                     argv [2] = NULL;
  15.                     retval = add_uevent_var(env, "HOME=/");
  16.                     if (retval)
  17.                             goto exit;
  18.                     retval = add_uevent_var(env,
  19.                                             "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
  20.                     if (retval)
  21.                             goto exit;
  22.                     retval = call_usermodehelper(argv[0], argv,
  23.                                                  env->envp, UMH_WAIT_EXEC);
  24.             }
  25.             ...
  26.     }
復制代碼

怎么樣,夠簡潔吧,其實看實際的代碼比這要郁悶地多,不過骨架清晰就行了。代碼中的netlink message就不用多說了吧,給udev發通知用(有時間的話可以分析分析udev的代碼)。本帖重點討論后半段的if (uevent_helper[0] && !kobj_usermode_filter(kobj))代碼,這里的核心調用是call_usermodehelper,這個函數最有意思的地方就在於在內核空間調用用戶空間的程序,它的詳細實現機制在書中已經講得很多,這里就不再贅述了。call_usermodehelper在kobject_uevent_env函數中要調用的用戶空間程序由uevent_helper[0]來指定,所以如果我們能控制這個uevent_helper[0],就能接收到設備加入系統移出系統等事件。那個if中的kobj_usermode_filter條件一般都會滿足(除非這是個特別注意個人隱私的設備,那就不好說了,人家偷偷加入系統就是不想讓你知道你也沒有辦法,但是udev還是能知道的)。

下面看看uevent_helper[0]來自何處:

  1.     <lib/kobject_uevent.c>
  2.     char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;
復制代碼

貌似要通過內核配置來指定,我看了一下我系統中Linux目錄下的.config文件,找到了下面這行:

  1. <linux-3.1.6/.config>
  2. #
  3. # Generic Driver Options
  4. #
  5. CONFIG_UEVENT_HELPER_PATH=""
復制代碼

丫的,居然沒指定,那么uevent_helper[0]="",這樣的話我們在kobject_uevent_env函數中的那個if語句就沒法滿足了,看來要重新配置再編譯內核了。不過想想sysfs這么強大,內核開發的那幫人好歹給留個用戶空間的接口出來吧,一查看還真有:

<kernel/ksysfs.c>

  1.     static ssize_t uevent_helper_store(struct kobject *kobj,
  2.                                        struct kobj_attribute *attr,
  3.                                        const char *buf, size_t count)
  4.     {
  5.             if (count+1 > UEVENT_HELPER_PATH_LEN)
  6.                     return -ENOENT;
  7.             memcpy(uevent_helper, buf, count);
  8.             uevent_helper[count] = '\0';
  9.             if (count && uevent_helper[count-1] == '\n')
  10.                     uevent_helper[count-1] = '\0';
  11.             return count;
  12.     }
復制代碼

尼瑪,爽得簡直是一塌糊塗,雖然俺那台馬力強勁的機器編個全新的內核不過幾分鍾的事情,但是哪里有上面這個方法爽啊。馬上進入到/sys/kernel目錄下ls一把,截屏如下(點擊放大):

uevent1.png 

有個uevent_helper文件不是?那么我們現在可以把我們用戶空間的程序給打進去了,我打算做個最簡單的腳本/sbin/myhotplug,這個腳本只干一件事,在/home/dennis目錄下生成一個hotplug文件:

</sbin/myhotplug>

  1.     #!/bin/sh
  2.     cd /home/dennis
  3.     touch hotplug
復制代碼

然后把這個腳本程序的文件名給打入到內核空間的uevent_helper[0]上:

  1.     root@build-server:/sys/kernel# echo "/sbin/myhotplug" > uevent_helper
  2.     root@build-server:/sys/kernel# cat uevent_helper
  3.     /sbin/myhotplug
復制代碼

好了,現在檢查一下你的/home/dennis目錄下面有沒有hotplug這個文件,有的話就刪掉,否則怎么知道是新生成的呢。現在,找個U盤插到你的電腦里,然后再看一下/home/dennis目錄,有個hotplug文件對吧?如果你現在刪除這個文件,再把U盤給拔了,你會再次發現這個文件。這意味着什么,意味着你可以輕而易舉地捕捉到設備加入/移出系統等事件,如果你的腳本足夠智能,那么你就會想到很多很有創意的玩法對吧?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM