linux 設備驅動模型-Uevent

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

1. Uevent的功能

Uevent是Kobject的一部分,用於在Kobject狀態發生改變時,例如增加、移除等,通知用戶空間程序。用戶空間程序收到這樣的事件後,會做相應的處理。

2. Uevent在kernel中的位置

下面圖片描述了Uevent模塊在內核中的位置:

linux 設備驅動模型-Uevent 科技 第1張

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

/* include/linux/kobject.h, line 50 */enum kobject_action { KOBJ_ADD,KOBJ_REMOVE, KOBJ_CHANGE, KOBJ_MOVE,KOBJ_ONLINE, KOBJ_OFFLINE,KOBJ_MAX };

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.2 內部做到

#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”, 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!”, 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!”, 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!”, 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!”, 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”, 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, “%[email protected]%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會執行, 表明事件已經上報給用戶空間。

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