轉自:http://www.wowotech.net/linux_kenrel/uevent.html
1. Uevent的功能
Uevent是Kobject的一部分,用於在Kobject狀態發生改變時,例如增加、移除等,通知使用者空間程式。使用者空間程式收到這樣的事件後,會做相應的處理。
該機制通常是用來支援熱拔插裝置的,例如U盤插入後,USB相關的驅動軟體會動態建立用於表示該U盤的device結構(相應的也包括其中的kobject),並告知使用者空間程式,為該U盤動態的建立/dev/目錄下的裝置節點,更進一步,可以通知其它的應用程式,將該U盤裝置mount到系統中,從而動態的支援該裝置。
2. Uevent在kernel中的位置
下面圖片描述了Uevent模組在核心中的位置:
由此可知,Uevent的機制是比較簡單的,裝置模型中任何裝置有事件需要上報時,會觸發Uevent提供的介面。Uevent模組準備好上報事件的格式後,可以通過兩個途徑把事件上報到使用者空間:一種是通過kmod模組,直接呼叫使用者空間的可執行檔案;另一種是通過netlink通訊機制,將事件從核心空間傳遞給使用者空間。
注1:有關kmod和netlink,會在其它文章中描述,因此本文就不再詳細說明了。
3. Uevent的內部邏輯解析
3.1 Source Code位置
Uevent的程式碼比較簡單,主要涉及kobject.h和kobject_uevent.c兩個檔案,如下:
- include/linux/kobject.h
- lib/kobject_uevent.c
3.2 資料結構描述
kobject.h定義了uevent相關的常量和資料結構,如下:
- kobject_action
- 1: /* include/linux/kobject.h, line 50 */
- 2: enum kobject_action {
- 3: KOBJ_ADD,
- 4: KOBJ_REMOVE,
- 5: KOBJ_CHANGE,
- 6: KOBJ_MOVE,
- 7: KOBJ_ONLINE,
- 8: KOBJ_OFFLINE,
- 9: KOBJ_MAX
- 10: };
kobject_action定義了event的型別,包括:
ADD/REMOVE,Kobject(或上層資料結構)的新增/移除事件。
ONLINE/OFFLINE,Kobject(或上層資料結構)的上線/下線事件,其實是是否使能。
CHANGE,Kobject(或上層資料結構)的狀態或者內容發生改變。
MOVE,Kobject(或上層資料結構)更改名稱或者更改Parent(意味著在sysfs中更改了目錄結構)。
CHANGE,如果裝置驅動需要上報的事件不再上面事件的範圍內,或者是自定義的事件,可以使用該event,並攜帶相應的引數。
- kobj_uevent_env
- 1: /* include/linux/kobject.h, line 31 */
- 2: #define UEVENT_NUM_ENVP 32 /* number of env pointers */
- 3: #define UEVENT_BUFFER_SIZE 2048 /* buffer for the variables */
- 4:
- 5: /* include/linux/kobject.h, line 116 */
- 6: struct kobj_uevent_env {
- 7: char *envp[UEVENT_NUM_ENVP];
- 8: int envp_idx;
- 9: char buf[UEVENT_BUFFER_SIZE];
- 10: int buflen;
- 11: };
前面有提到過,在利用Kmod向用戶空間上報event事件時,會直接執行使用者空間的可執行檔案。而在Linux系統,可執行檔案的執行,依賴於環境變數,因此kobj_uevent_env用於組織此次事件上報時的環境變數。
envp,指標陣列,用於儲存每個環境變數的地址,最多可支援的環境變數數量為UEVENT_NUM_ENVP。
envp_idx,用於訪問環境變數指標陣列的index。
buf,儲存環境變數的buffer,最大為UEVENT_BUFFER_SIZE。
buflen,訪問buf的變數。
- kset_uevent_ops
- 1: /* include/linux/kobject.h, line 123 */
- 2: struct kset_uevent_ops {
- 3: int (* const filter)(struct kset *kset, struct kobject *kobj);
- 4: const char *(* const name)(struct kset *kset, struct kobject *kobj);
- 5: int (* const uevent)(struct kset *kset, struct kobject *kobj,
- 6: struct kobj_uevent_env *env);
- 7: };
kset_uevent_ops是為kset量身訂做的一個數據結構,裡面包含filter和uevent兩個回撥函式,用處如下:
filter,當任何Kobject需要上報uevent時,它所屬的kset可以通過該介面過濾,阻止不希望上報的event,從而達到從整體上管理的目的。
name,該介面可以返回kset的名稱。如果一個kset沒有合法的名稱,則其下的所有Kobject將不允許上報uvent
uevent,當任何Kobject需要上報uevent時,它所屬的kset可以通過該介面統一為這些event新增環境變數。因為很多時候上報uevent時的環境變數都是相同的,因此可以由kset統一處理,就不需要讓每個Kobject獨自添加了。
3.3 內部動作
通過kobject.h,uevent模組提供瞭如下的API(這些API的實現是在"lib/kobject_uevent.c”檔案中):
- 1: /* include/linux/kobject.h, line 206 */
- 2: int kobject_uevent(struct kobject *kobj, enum kobject_action action);
- 3: int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
- 4: char *envp[]);
- 5:
- 6: __printf(2, 3)
- 7: int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...);
- 8:
- 9: int kobject_action_type(const char *buf, size_t count,
- 10: enum kobject_action *type);
kobject_uevent_env,以envp為環境變數,上報一個指定action的uevent。環境變數的作用是為執行使用者空間程式指定執行環境。具體動作如下:
- 查詢kobj本身或者其parent是否從屬於某個kset,如果不是,則報錯返回(注2:由此可以說明,如果一個kobject沒有加入kset,是不允許上報uevent的)
- 檢視kobj->uevent_suppress是否設定,如果設定,則忽略所有的uevent上報並返回(注3:由此可知,可以通過Kobject的uevent_suppress標誌,管控Kobject的uevent的上報)
- 如果所屬的kset有uevent_ops->filter函式,則呼叫該函式,過濾此次上報(注4:這佐證了3.2小節有關filter介面的說明,kset可以通過filter介面過濾不希望上報的event,從而達到整體的管理效果)
- 判斷所屬的kset是否有合法的名稱(稱作subsystem,和前期的核心版本有區別),否則不允許上報uevent
- 分配一個用於此次上報的、儲存環境變數的buffer(結果儲存在env指標中),並獲得該Kobject在sysfs中路徑資訊(使用者空間軟體需要依據該路徑資訊在sysfs中訪問它)
- 呼叫add_uevent_var介面(下面會介紹),將Action、路徑資訊、subsystem等資訊,新增到env指標中
- 如果傳入的envp不空,則解析傳入的環境變數中,同樣呼叫add_uevent_var介面,新增到env指標中
- 如果所屬的kset存在uevent_ops->uevent介面,呼叫該介面,新增kset統一的環境變數到env指標
- 根據ACTION的型別,設定kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent變數,以記錄正確的狀態
- 呼叫add_uevent_var介面,新增格式為"SEQNUM=%llu”的序列號
- 如果定義了"CONFIG_NET”,則使用netlink傳送該uevent
- 以uevent_helper、subsystem以及添加了標準環境變數(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指標為引數,呼叫kmod模組提供的call_usermodehelper函式,上報uevent。
其中uevent_helper的內容是由核心配置項CONFIG_UEVENT_HELPER_PATH(位於./drivers/base/Kconfig)決定的(可參考lib/kobject_uevent.c, line 32),該配置項指定了一個使用者空間程式(或者指令碼),用於解析上報的uevent,例如"/sbin/hotplug”。
call_usermodehelper的作用,就是fork一個程序,以uevent為引數,執行uevent_helper。kobject_uevent,和kobject_uevent_env功能一樣,只是沒有指定任何的環境變數。
add_uevent_var,以格式化字元的形式(類似printf、printk等),將環境變數copy到env指標中。
kobject_action_type,將enum kobject_action型別的Action,轉換為字串。
說明:怎麼指定處理uevent的使用者空間程式(簡稱uevent helper)?
上面介紹kobject_uevent_env的內部動作時,有提到,Uevent模組通過Kmod上報Uevent時,會通過call_usermodehelper函式,呼叫使用者空間的可執行檔案(或者指令碼,簡稱uevent helper )處理該event。而該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”中相應的程式碼,這裡不再詳細描述。 |