Uevent是Kobject的一部分,用於在Kobject狀態改變時(例如增加、移除等),通知使用者空間程式。使用者空間程式在接收這樣的事件後,會做出相對應的處理。
通常情況下,此機制用於支援熱插拔設備。例如,插入U碟後,與USB相關的驅動程式會動態地建立用於表示該U碟的device結構(其中包括對應的kobject),並告知使用者空間程式。這將使得設備節點能夠動態地被建立在/dev/目錄下。更進一步地,該機制還可以通知其它的應用程序,將該U盤設備掛載到系統中,從而實現對該設備的動態支援。
#下面圖片描述了Uevent模組在核心中的位置:
由此可知,Uevent的機制是比較簡單的,設備模型中任何設備有事件需要回報時,會觸發Uevent提供的介面。 Uevent模組準備好上報事件的格式後,可以透過兩個途徑把事件上報到用戶空間:一種是透過kmod模組,直接呼叫用戶空間的可執行檔;另一種是透過netlink通訊機制,將事件從核心空間傳遞給使用者空間。
註1:有關kmod和netlink,會在其它文章中描述,因此本文就不再詳細說明了。
Uevent的程式碼比較簡單,主要涉及kobject.h和kobject_uevent.c兩個文件,如下:
#kobject.h定義了uevent相關的常數和資料結構,如下:
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,並攜帶對應的參數。
」
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的变量。
”
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独自添加了。
”
通过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」中對應的程式碼,這裡不再詳細描述。
以上是Linux設備模型(3)_Uevent的詳細內容。更多資訊請關注PHP中文網其他相關文章!