天天看點

Linux裝置模型(四) uevent

熱插拔事件:在linux系統中,當系統配置發生變化時,如添加kset到系統或移動kobject,一個通知會從核心空間發送到使用者空間,這就是熱插拔事件。

熱插拔事件的産生通常是由在總線驅動程式層的邏輯所控制。

熱插拔事件會導緻使用者空間中的處理程式(如udev,mdev)被調用,這些處理程式會通過加載驅動程式,建立裝置節點等來響應熱插拔事件。

比如,當U盤通過USB線纜插入到系統時。熱插拔事件會導緻對/sbin/hotplug程式的調用,該程式通過加載驅動程式,建立裝置節點,挂裝分區,或者其他正确的動作來響應。

uevent事件是kobject的一部分,用于在kobject狀态發生改變時,例如增加、移除等,通知使用者空間程式。使用者空間程式收到這樣的事件後,會做相應的處理。

在上邊kset結構體中就有通知事件uevent,裝置模型中任何裝置狀态發生改變時需要上報,會觸發uevent提供的接口。

而在Linux系統,可執行檔案的執行,依賴于環境變量,環境變量的作用是為執行使用者空間程式設定運作環境。

是以

kobj_uevent_env

用于組織此次事件上報時的環境變量,

kset_uevent_ops->uevent

就是設定這個環境變量到使用者空間

struct 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);//傳遞熱插拔程式的環境變量 
}; 

filter() 函數讓 kset 代碼決定是否将事件傳遞給使用者空間。用于過濾掉不需要導出到使用者空間的事件
name 該接口可以傳回kset的名稱。如果一個kset沒有合法的名稱,則其下的所有Kobject将不允許上報uvent
uevent() 函數用于設定使用者熱插拔處理程式的環境變量
           

環境變量參數的結構體

#define UEVENT_NUM_ENVP         32 /* number of env pointers */
#define UEVENT_BUFFER_SIZE      2048 /* buffer for the variables */
struct kobj_uevent_env {
    char *envp[UEVENT_NUM_ENVP];
    int envp_idx;
    char buf[UEVENT_BUFFER_SIZE];
    int buflen;
};
           

當核心中打開

CONFIG_HOTPLUG

這個宏,也就是支援熱插拔後,會有對應的這幾個函數被定義

kobject_uevent

在後邊上層容器(裝置、驅動、總線)中會被調用到,其中在

kset_register

中就有調用

/* 
include/linux/kobject.h
lib/kobject_event.c 
*/
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, char *envp[]);

int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)

int kobject_action_type(const char *buf, size_t count, enum kobject_action *type);

kobject_uevent  不添加環境變量的上報
kobject_uevent_env  以envp為環境變量,上報一個指定action的uevent。

add_uevent_var  以格式化字元的形式(類似printf、printk等),将環境變量copy到env指針中。
kobject_action_type  将enum kobject_action類型的Action,轉換為字元串。

enum kobject_action {
    KOBJ_ADD,
    KOBJ_REMOVE,
    KOBJ_CHANGE,
    KOBJ_MOVE,
    KOBJ_ONLINE,
    KOBJ_OFFLINE,
    KOBJ_MAX
};
ADD/REMOVE      kobject(或上層資料結構)的添加/移除事件。
ONLINE/OFFLINE  kobject(或上層資料結構)的上線/下線事件,其實是是否使能。
CHANGE          kobject(或上層資料結構)的狀态或者内容發生改變。
MOVE            kobject(或上層資料結構)更改名稱或者更改Parent(意味着在sysfs中更改了目錄結構)。
CHANGE          如果裝置驅動需要上報的事件不再上面事件的範圍内,或者是自定義的事件,可以使用該event,并攜帶相應的參數。
           

代碼參考核心源碼 lib/kobject_event.c

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[])
{
    ...
    /* default keys */
    retval = add_uevent_var(env, "ACTION=%s", action_string);
    if (retval)
        goto exit;
    retval = add_uevent_var(env, "DEVPATH=%s", devpath);
    if (retval)
        goto exit;
    retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    if (retval)
        goto exit;
    ...

    /*
     * Mark "add" and "remove" events in the object to ensure proper
     * events to userspace during automatic cleanup. If the object did
     * send an "add" event, "remove" will automatically generated by
     * the core, if not already done by the caller.
     */
    if (action == KOBJ_ADD)
        kobj->state_add_uevent_sent = 1;
    else if (action == KOBJ_REMOVE)
        kobj->state_remove_uevent_sent = 1;

    /* we will send an event, so request a new sequence number */
    spin_lock(&sequence_lock);
    seq = ++uevent_seqnum;
    spin_unlock(&sequence_lock);
    retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq);
    if (retval)
        goto exit;

#if defined(CONFIG_NET)
    /* send netlink message */
    ...
#endif
    ...
    /* call uevent_helper, usually only enabled during early boot */
    if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
        char *argv [3];

        argv [0] = uevent_helper;
        argv [1] = (char *)subsystem;
        argv [2] = NULL;
        retval = add_uevent_var(env, "HOME=/");
        if (retval)
            goto exit;
        retval = add_uevent_var(env,
                    "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
        if (retval)
            goto exit;

        retval = call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC);
    }

exit:
    kfree(devpath);
    kfree(env);
    return retval;
}
           

顯然 uevent 的機制,就是設定環境變量,然後調用使用者空間程式 mdev 進行更新裝置。

uevent子產品準備好上報事件的格式後,在

kobject_uevent

中有兩種方法把事件上報到使用者空間:

一種是通過kmod子產品,直接調用使用者空間的可執行檔案;

一種是通過netlink通信機制(需要核心使能

CONFIG_NET

),将事件從核心空間傳遞給使用者空間。

這部分源碼就在

kobject_uevent

函數裡

從源碼分析來看,在利用Kmod向使用者空間上報event事件時,會調用核心的接口

call_usermodehelper

,配置好環境變量,然後直接執行使用者空間的可執行程式 mdev 。

uevent_helper為指定的執行檔案路徑

總結:

kobject_uevent的工作

1、将 device 的 kobject 的PATH 、name、主次裝置号等等設定到環境變量裡
2、調用使用者空間 mdev,自動建立裝置節點
           

它的作用會在上層容器,device和driver的kset對象中展現比較重要,也正是這個函數接口,會調用mdev自動建立裝置節點

對于熱插拔事件的調用:

當具體對象有事件發生時,相應的操作函數中(如device_add()),會調用事件消息接口kobject_uevent()。
在該接口中,首先會添加一些共性的消息(路徑、子系統名等),然後會回調kset -> uevent_ops -> uevent(kset, kobj, env)來添加該對象特有的事件消息(如device對象的裝置号、裝置名、驅動名、DT資訊)
一切準備完畢,就會通過兩種可能的方法向使用者空間發送消息并執行相關程式:1.netlink廣播;2. uevent_helper程式。