設備模型的uevent機制


內核模塊的熱插拔事件的通知基於uevent機制。

當kobject的狀態發生改變(如,add, remove等)時,會通知用戶空間,用戶空間接收到事件通知后可以做相應的處理。
uevent把事件上報給用戶空間的兩種途徑:
  1.通過kmod模塊,直接調用用戶空間的可執行程序或腳本。
  2.通過netlink通信機制,將事件從內核空間傳遞到用戶空間。

linux-3.5/include/linux/kobject.h

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

#define UEVENT_HELPER_PATH_LEN      256
#define UEVENT_NUM_ENVP         32  /* number of env pointers */
#define UEVENT_BUFFER_SIZE      2048    /* buffer for the variables */

//在利用kmod模塊向用戶空間上報event事件時,會直接執行用戶空間的可執行文件。而在linux系統中,可執行文件的執行,依賴於環境變量,
//因此kobj_uevent_env用於組織此次事件上報是的環境變量。
struct kobj_uevent_env {
    char *envp[UEVENT_NUM_ENVP];//指針數組,用於保存每個環境變量的地址,最多支持32個環境變量
    int envp_idx;//用戶訪問環境變量數組的索引
    char buf[UEVENT_BUFFER_SIZE];//保存環境變量的buffer
    int buflen;//???
};

struct kset_uevent_ops {
    int (* const filter)(struct kset *kset, struct kobject *kobj);//當任何kobject需要上報uevent時,它所屬的kset可以通過filter借口過濾,阻止不希望上報的uevent。
    const char *(* const name)(struct kset *kset, struct kobject *kobj);//該接口可以返回kset的名稱。如果一個kset沒有合法的名稱,則其下的所有kobject將不允許上報uevent
    int (* const uevent)(struct kset *kset, struct kobject *kobj,
              struct kobj_uevent_env *env);//當任何kobject需要上報uevent時,它所屬的kset可以通過該接口統一為這些event添加環境變量。
              //因為很多時候上報uevent時的環境變量都是相同的,因此可以由kset統一處理,就不需要讓每個Kobject獨自添加了。
};
#if defined(CONFIG_HOTPLUG)
int kobject_uevent(struct kobject *kobj, enum kobject_action action);
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
            char *envp[]);

__printf(2, 3)
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_env ,以 envp 為環境變量,上報一個指定action的uevent。環境變量的作用是為執行用戶空間程序指定運行環境。
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[])
{
    struct kobj_uevent_env *env;
    const char *action_string = kobject_actions[action];
    const char *devpath = NULL;
    const char *subsystem;
    struct kobject *top_kobj;
    struct kset *kset;
    const struct kset_uevent_ops *uevent_ops;
    int i = 0;
    int retval = 0;
#ifdef CONFIG_NET
    struct uevent_sock *ue_sk;
#endif

    pr_debug("kobject: '%s' (%p): %s\n",
         kobject_name(kobj), kobj, __func__);

    /* search the kset we belong to */
    //1.查找當前kobject或其parent是否從屬於某個kset;如果都不從屬於某個kset,則返回錯誤。(說明一個kobject若沒有加入kset,是不會上報uevent的)
    top_kobj = kobj;
    while (!top_kobj->kset && top_kobj->parent)
        top_kobj = top_kobj->parent;

    if (!top_kobj->kset) {
        pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
             "without kset!\n", kobject_name(kobj), kobj,
             __func__);
        return -EINVAL;
    }

    kset = top_kobj->kset;
    uevent_ops = kset->uevent_ops;

    /* skip the event, if uevent_suppress is set*/
    //2.查看kobj->uevent_suppress是否被設置;如果設置了,則忽略所有的uevent上報,並返回0.
    if (kobj->uevent_suppress) {
        pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
                 "caused the event to drop!\n",
                 kobject_name(kobj), kobj, __func__);
        return 0;
    }
    /* skip the event, if the filter returns zero. */
    //3.如果所屬的kset有uevent_ops->filter,則調用該函數,若該函數返回0,則過濾此次上報。(kset 可以通過filter接口過濾不希望上報的event)
    if (uevent_ops && uevent_ops->filter)
        if (!uevent_ops->filter(kset, kobj)) {
            pr_debug("kobject: '%s' (%p): %s: filter function "
                 "caused the event to drop!\n",
                 kobject_name(kobj), kobj, __func__);
            return 0;
        }

    /* originating subsystem */
    //4.判斷所屬的kset是否有合法的名稱,若uevent_ops->name存在就用其返回的名稱作為subsystem;若uevent_ops->name不存在就用kset本身的kobject的名稱作為subsystem;
    //若沒有合法的名稱,則不上報uevent
    if (uevent_ops && uevent_ops->name)
        subsystem = uevent_ops->name(kset, kobj);
    else
        subsystem = kobject_name(&kset->kobj);
    if (!subsystem) {
        pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
             "event to drop!\n", kobject_name(kobj), kobj,
             __func__);
        return 0;
    }

    /* environment buffer */
    //5.分配一個此次上報的用於保存環境變量的buffer,
    env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
    if (!env)
        return -ENOMEM;

    /* complete object path */
    //6.獲得該kobject在sysfs中路徑
    devpath = kobject_get_path(kobj, GFP_KERNEL);
    if (!devpath) {
        retval = -ENOENT;
        goto exit;
    }

    /* default keys */
    //7.添加ACTION到env
    retval = add_uevent_var(env, "ACTION=%s", action_string);
    if (retval)
        goto exit;
    //8.添加DEVPATH(kobject路徑信息)到env
    retval = add_uevent_var(env, "DEVPATH=%s", devpath);
    if (retval)
        goto exit;
    //9.添加SUBSYSTEM到env
    retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    if (retval)
        goto exit;

    /* keys passed in from the caller */
    //10.如果傳入的envp_ext不空,則解析傳入的環境變量中,同樣調用add_uevent_var接口,添加到env指針中
    if (envp_ext) {
        for (i = 0; envp_ext[i]; i++) {
            retval = add_uevent_var(env, "%s", envp_ext[i]);
            if (retval)
                goto exit;
        }
    }

    /* let the kset specific function add its stuff */
    //11.如果 uevent_ops->uevent 存在,調用該接口,添加kset統一的環境變量到env指針
    if (uevent_ops && uevent_ops->uevent) {
        retval = uevent_ops->uevent(kset, kobj, env);
        if (retval) {
            pr_debug("kobject: '%s' (%p): %s: uevent() returned "
                 "%d\n", kobject_name(kobj), kobj,
                 __func__, 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.
     */
    //12.根據ACTION的類型,設置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent變量,以記錄正確的狀態
    if (action == KOBJ_ADD)
        kobj->state_add_uevent_sent = 1;
    else if (action == KOBJ_REMOVE)
        kobj->state_remove_uevent_sent = 1;

    mutex_lock(&uevent_sock_mutex);
    /* we will send an event, so request a new sequence number */
    //13.調用add_uevent_var接口,添加格式為"SEQNUM=%llu”的序列號
    retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
    if (retval) {
        mutex_unlock(&uevent_sock_mutex);
        goto exit;
    }
//14.如果定義了"CONFIG_NET”,則使用netlink發送該uevent
#if defined(CONFIG_NET)
    /* send netlink message */
    list_for_each_entry(ue_sk, &uevent_sock_list, list) {
        struct sock *uevent_sock = ue_sk->sk;
        struct sk_buff *skb;
        size_t len;

        if (!netlink_has_listeners(uevent_sock, 1))
            continue;

        /* allocate message with the maximum possible size */
        len = strlen(action_string) + strlen(devpath) + 2;
        skb = alloc_skb(len + env->buflen, GFP_KERNEL);
        if (skb) {
            char *scratch;

            /* add header */
            scratch = skb_put(skb, len);
            sprintf(scratch, "%s@%s", action_string, devpath);

            /* copy keys to our continuous event payload buffer */
            for (i = 0; i < env->envp_idx; i++) {
                len = strlen(env->envp[i]) + 1;
                scratch = skb_put(skb, len);
                strcpy(scratch, env->envp[i]);
            }

            NETLINK_CB(skb).dst_group = 1;
            retval = netlink_broadcast_filtered(uevent_sock, skb,
                                0, 1, GFP_KERNEL,
                                kobj_bcast_filter,
                                kobj);
            /* ENOBUFS should be handled in userspace */
            if (retval == -ENOBUFS || retval == -ESRCH)
                retval = 0;
        } else
            retval = -ENOMEM;
    }
#endif
    mutex_unlock(&uevent_sock_mutex);

    /* call uevent_helper, usually only enabled during early boot */
    //15.以uevent_helper、 subsystem 以及添加了標准環境變量(HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)的env指針為參數,
    //        調用kmod模塊提供的call_usermodehelper函數,上報uevent。
    if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
        char *argv [3];

        argv [0] = uevent_helper;//在/sys/kernel/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模塊通過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”中相應的代碼。

在/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會執行,
表明事件已經上報給用戶空間。

Q1:用戶空間怎樣去識別上報的事件到底是什么事件?下一步研究


call_usermodehelper函數能夠方便的在內核中直接新建和運行用戶空間的程序,並且該程序有root權限。
call_usermodeheler函數的參數用法和execve函數一致。
call_usermodehelper()->call_usermodehelper_exec()


免責聲明!

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



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