linux設備驅動(8)uevent 詳解


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的內部邏輯解析

source code: include/linux/kobject.h 和 lib/kobject_uevent.c

3.1相關數據結構

kobject.h定義了uevent相關的常量和數據結構,如下:

kobject_action 

 1 /*
 2  * The actions here must match the index to the string array
 3  * in lib/kobject_uevent.c
 4  *
 5  * Do not add new actions here without checking with the driver-core
 6  * maintainers. Action strings are not meant to express subsystem
 7  * or device specific properties. In most cases you want to send a
 8  * kobject_uevent_env(kobj, KOBJ_CHANGE, env) with additional event
 9  * specific variables added to the event environment.
10  */
11 enum kobject_action {
12     KOBJ_ADD,//Kobject(或上層數據結構)的添加/移除事件
13     KOBJ_REMOVE,//Kobject(或上層數據結構)的添加/移除事件
14     KOBJ_CHANGE,//(或上層數據結構)的狀態或者內容發生改變。如果設備驅動需要上報的事件不再上面事件的范圍內,或者是自定義的事件,可以使用該event,並攜帶相應的參數
15     KOBJ_MOVE,//Kobject(或上層數據結構)更改名稱或者更改Parent(意味着在sysfs中更改了目錄結構)。
16     KOBJ_ONLINE,
17     KOBJ_OFFLINE,
18     KOBJ_MAX
19 };

kobj_uevent_env

 1 #define UEVENT_HELPER_PATH_LEN        256
 2 #define UEVENT_NUM_ENVP            32    /* number of env pointers */
 3 #define UEVENT_BUFFER_SIZE        2048    /* buffer for the variables */
 4 
 5 struct kobj_uevent_env {
 6     char *envp[UEVENT_NUM_ENVP];//指針數組,用於保存每個環境變量的地址,數組中每個指針指向下面buf數組中每個環境變量。最多可支持的環境變量數量為UEVENT_NUM_ENVP。
 7     int envp_idx;//用於訪問環境變量指針數組的index
 8     char buf[UEVENT_BUFFER_SIZE];//保存環境變量的buffer,最大為UEVENT_BUFFER_SIZE
 9     int buflen;//環境變量buf的長度
10 };

前面有提到過,在利用Kmod向用戶空間上報event事件時,會直接執行用戶空間的可執行文件。而在Linux系統,可執行文件的執行,依賴於環境變量,因此kobj_uevent_env用於組織此次事件上報時的環境變量。

kset_uevent_ops

1 struct kset_uevent_ops {
2     int (* const filter)(struct kset *kset, struct kobject *kobj);
3     const char *(* const name)(struct kset *kset, struct kobject *kobj);
4     int (* const uevent)(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env);
5 };

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相關API

3.2.1kobject_uevent_env

發送一個環境變量事件。

  1 /**
  2  * kobject_uevent_env - send an uevent with environmental data
  3  *
  4  * @action: action that is happening
  5  * @kobj: struct kobject that the action is happening to
  6  * @envp_ext: pointer to environmental data
  7  *
  8  * Returns 0 if kobject_uevent_env() is completed with success or the
  9  * corresponding error when it fails.
 10  */
 11 int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
 12                char *envp_ext[])
 13 {
 14     struct kobj_uevent_env *env;
 15     const char *action_string = kobject_actions[action];
 16     const char *devpath = NULL;
 17     const char *subsystem;
 18     struct kobject *top_kobj;
 19     struct kset *kset;
 20     const struct kset_uevent_ops *uevent_ops;
 21     int i = 0;
 22     int retval = 0;
 23 #ifdef CONFIG_NET
 24     struct uevent_sock *ue_sk;
 25 #endif
 26  
 27     pr_debug("kobject: '%s' (%p): %s\n",
 28          kobject_name(kobj), kobj, __func__);
 29  
 30     /* search the kset we belong to 知道到該kobj從屬的kset*/
 31     top_kobj = kobj;
 32     while (!top_kobj->kset && top_kobj->parent)
 33         top_kobj = top_kobj->parent;    /* 找的方法很簡單,若它的kset不存在,則查找其父節點的kset是否存在,不存在則繼續查找父父節點.... */
 34  
 35     if (!top_kobj->kset) {    
 36         pr_debug("kobject: '%s' (%p): %s: attempted to send uevent "
 37              "without kset!\n", kobject_name(kobj), kobj,
 38              __func__);
 39         return -EINVAL;   /* 最終還沒找到就報錯 */
 40     }
 41  
 42     kset = top_kobj->kset;        /* 找到與之相關的kset */
 43     uevent_ops = kset->uevent_ops;
 44  
 45     /* skip the event, if uevent_suppress is set*/
 46     if (kobj->uevent_suppress) {        /* uevent_suppress被置位,則忽略上報uevent */
 47         pr_debug("kobject: '%s' (%p): %s: uevent_suppress "
 48                  "caused the event to drop!\n",
 49                  kobject_name(kobj), kobj, __func__);
 50         return 0;
 51     }
 52     /* skip the event, if the filter returns zero. */
 53     if (uevent_ops && uevent_ops->filter)       /* 所屬的篩選函數存在則篩選,返回0表示被篩掉了,不再上報 */
 54         if (!uevent_ops->filter(kset, kobj)) {
 55             pr_debug("kobject: '%s' (%p): %s: filter function "
 56                  "caused the event to drop!\n",
 57                  kobject_name(kobj), kobj, __func__);
 58             return 0;
 59         }
 60  
 61     /* originating subsystem */
 62     if (uevent_ops && uevent_ops->name)
 63         subsystem = uevent_ops->name(kset, kobj);    /* name函數存在,則使用kset返回的kset的name */
 64     else
 65         subsystem = kobject_name(&kset->kobj);       /* 否則用kset里kobj的name做kset的name */
 66     if (!subsystem) {        /* kset的name不存在,也不允許上報 */
 67         pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the "
 68              "event to drop!\n", kobject_name(kobj), kobj,
 69              __func__);
 70         return 0;
 71     }
 72  
 73     /* environment buffer */
 74     env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);    /* 分配一個用於此次環境變量的buffer */
 75     if (!env)
 76         return -ENOMEM;
 77  
 78     /* complete object path */
 79     devpath = kobject_get_path(kobj, GFP_KERNEL);    /* 根據kobj得到它在sysfs中的路徑 */
 80     if (!devpath) {
 81         retval = -ENOENT;
 82         goto exit;
 83     }
 84  
 85     /* default keys 添加當前要上報的行為,path,name到env的buffer中 */
 86     retval = add_uevent_var(env, "ACTION=%s", action_string);
 87     if (retval)
 88         goto exit;
 89     retval = add_uevent_var(env, "DEVPATH=%s", devpath);
 90     if (retval)
 91         goto exit;
 92     retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
 93     if (retval)
 94         goto exit;
 95  
 96     /* keys passed in from the caller */
 97     if (envp_ext) {    /* 傳的外部環境變量要不為空,則解析並添加到env的buffer中 */
 98         for (i = 0; envp_ext[i]; i++) {
 99             retval = add_uevent_var(env, "%s", envp_ext[i]);
100             if (retval)
101                 goto exit;
102         }
103     }
104  
105     /* let the kset specific function add its stuff */
106     if (uevent_ops && uevent_ops->uevent) {    /* 如果uevent_ops中的uevent存在,則調用該接口發送該kobj的env */
107         retval = uevent_ops->uevent(kset, kobj, env);
108         if (retval) {
109             pr_debug("kobject: '%s' (%p): %s: uevent() returned "
110                  "%d\n", kobject_name(kobj), kobj,
111                  __func__, retval);
112             goto exit;
113         }
114     }
115  
116     /*
117      * Mark "add" and "remove" events in the object to ensure proper
118      * events to userspace during automatic cleanup. If the object did
119      * send an "add" event, "remove" will automatically generated by
120      * the core, if not already done by the caller.
121      */
122     if (action == KOBJ_ADD)    /* 如果action是add或remove的話要更新kobj中的state */
123         kobj->state_add_uevent_sent = 1;
124     else if (action == KOBJ_REMOVE)
125         kobj->state_remove_uevent_sent = 1;
126  
127     mutex_lock(&uevent_sock_mutex);
128     /* we will send an event, so request a new sequence number */
129     /* 每次發送一個事件,都要有它的事件號,該事件號不能重復,u64 uevent_seqnum,把它也作為環境變量添加到buffer最后面 */
130     retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)++uevent_seqnum);
131     if (retval) {
132         mutex_unlock(&uevent_sock_mutex);
133         goto exit;
134     }
135  
136 #if defined(CONFIG_NET)
137     /* send netlink message 如果定義了"CONFIG_NET”,則使用netlink發送該uevent */
138     list_for_each_entry(ue_sk, &uevent_sock_list, list) {
139         struct sock *uevent_sock = ue_sk->sk;
140         struct sk_buff *skb;
141         size_t len;
142  
143         if (!netlink_has_listeners(uevent_sock, 1))
144             continue;
145  
146         /* allocate message with the maximum possible size */
147         len = strlen(action_string) + strlen(devpath) + 2;
148         skb = alloc_skb(len + env->buflen, GFP_KERNEL);
149         if (skb) {
150             char *scratch;
151  
152             /* add header */
153             scratch = skb_put(skb, len);
154             sprintf(scratch, "%s@%s", action_string, devpath);
155  
156             /* copy keys to our continuous event payload buffer */
157             for (i = 0; i < env->envp_idx; i++) {
158                 len = strlen(env->envp[i]) + 1;
159                 scratch = skb_put(skb, len);
160                 strcpy(scratch, env->envp[i]);
161             }
162  
163             NETLINK_CB(skb).dst_group = 1;
164             retval = netlink_broadcast_filtered(uevent_sock, skb,
165                                 0, 1, GFP_KERNEL,
166                                 kobj_bcast_filter,
167                                 kobj);
168             /* ENOBUFS should be handled in userspace */
169             if (retval == -ENOBUFS || retval == -ESRCH)
170                 retval = 0;
171         } else
172             retval = -ENOMEM;
173     }
174 #endif
175     mutex_unlock(&uevent_sock_mutex);
176  
177     /* call uevent_helper, usually only enabled during early boot */
178     if (uevent_helper[0] && !kobj_usermode_filter(kobj)) {
179         char *argv [3];
180         
181         /* 添加helper和 kset的name */
182         argv [0] = uevent_helper;
183         argv [1] = (char *)subsystem;
184         argv [2] = NULL;
185  
186         /* 添加了標准環境變量 (HOME=/,PATH=/sbin:/bin:/usr/sbin:/usr/bin)*/
187         retval = add_uevent_var(env, "HOME=/");
188         if (retval)
189             goto exit;
190         retval = add_uevent_var(env,
191                     "PATH=/sbin:/bin:/usr/sbin:/usr/bin");
192         if (retval)
193             goto exit;
194         /* 調用kmod模塊提供的call_usermodehelper函數,上報uevent。call_usermodehelper的作用,就是fork一個進程,以uevent為參數,執行uevent_helper */
195         retval = call_usermodehelper(argv[0], argv,
196                          env->envp, UMH_WAIT_EXEC);
197     }
198  
199 exit:
200     kfree(devpath);
201     kfree(env);
202     return retval;
203 }

kobject_uevent_env,以envp為環境變量,上報一個指定action的uevent。環境變量的作用是為執行用戶空間程序指定運行環境。整個流程總結如下:

(1)查找kobj本身或者其parent是否從屬於某個kset,如果不是,則報錯返回(注2:由此可以說明,如果一個kobject沒有加入kset,是不允許上報uevent的)
(2)查看kobj->uevent_suppress是否設置,如果設置,則忽略所有的uevent上報並返回(注3:由此可知,可以通過Kobject的uevent_suppress標志,管控Kobject的uevent的上報)
(3)如果所屬的kset有uevent_ops->filter函數,則調用該函數,過濾此次上報(注4:這佐證了3.2小節有關filter接口的說明,kset可以通過filter接口過濾不希望上報的event,從而達到整體的管理效果)
(4)判斷所屬的kset是否有合法的名稱(稱作subsystem,和前期的內核版本有區別),否則不允許上報uevent
(5)分配一個用於此次上報的、存儲環境變量的buffer(結果保存在env指針中),並獲得該Kobject在sysfs中路徑信息(用戶空間軟件需要依據該路徑信息在sysfs中訪問它)
(6)調用add_uevent_var接口(下面會介紹),將Action、路徑信息、subsystem等信息,添加到env指針中
(7)如果傳入的envp不空,則解析傳入的環境變量中,同樣調用add_uevent_var接口,添加到env指針中
(8)如果所屬的kset存在uevent_ops->uevent接口,調用該接口,添加kset統一的環境變量到env指針
(9)根據ACTION的類型,設置kobj->state_add_uevent_sent和kobj->state_remove_uevent_sent變量,以記錄正確的狀態
(10)調用add_uevent_var接口,添加格式為"SEQNUM=%llu”的序列號
(11)如果定義了"CONFIG_NET”,則使用netlink發送該uevent
(12)以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。
3.2.2kobject_uevent

和kobject_uevent_env功能一樣,只是沒有指定任何的環境變量。

還是call kobject_uevent_env(),向用戶空間發送一個事件。

 1 /**
 2 * kobject_uevent - notify userspace by sending an uevent
 3 *
 4 * @action: action that is happening
 5 * @kobj: struct kobject that the action is happening to
 6 *
 7 * Returns 0 if kobject_uevent() is completed with success or the
 8 * corresponding error when it fails.
 9 */
10 int kobject_uevent(struct kobject *kobj, enum kobject_action action)
11 {
12 return kobject_uevent_env(kobj, action, NULL);
13 }

kobject_uevent(),最終被kset_register() 調用,向用戶空間發送一個事件。

 1 int kset_register(struct kset *k)
 2 {
 3     int err;
 4 
 5     if (!k)
 6         return -EINVAL;
 7 
 8     kset_init(k);
 9     err = kobject_add_internal(&k->kobj);
10     if (err)
11         return err;
12     kobject_uevent(&k->kobj, KOBJ_ADD);//向用戶空間發送事件
13     return 0;
14 }

3.2.3add_uevent_var

以格式化字符的形式(類似printf、printk等),將環境變量copy到env指針中。

 1 /**
 2 * add_uevent_var - add key value string to the environment buffer
 3 * @env: environment buffer structure
 4 * @format: printf format for the key=value pair
 5 *
 6 * Returns 0 if environment variable was added successfully or -ENOMEM
 7 * if no space was available.
 8 */
 9 int add_uevent_var(struct kobj_uevent_env *env, const char *format, ...)
10 {
11 va_list args;
12 int len;
13 
14 if (env->envp_idx >= ARRAY_SIZE(env->envp)) {
15 WARN(1, KERN_ERR "add_uevent_var: too many keys\n");
16 return -ENOMEM;
17 }
18 
19 va_start(args, format);
20 len = vsnprintf(&env->buf[env->buflen], 
21 sizeof(env->buf) - env->buflen,
22 format, args);
23 va_end(args);
24 
25 if (len >= (sizeof(env->buf) - env->buflen)) {
26 WARN(1, KERN_ERR "add_uevent_var: buffer size too small\n");
27 return -ENOMEM;
28 }
29 
30 env->envp[env->envp_idx++] = &env->buf[env->buflen];
31 env->buflen += len + 1;
32 return 0;
33 }

3.2.4kobject_action_type

將enum kobject_action類型的Action,轉換為字符串。

 1 /* the strings here must match the enum in include/linux/kobject.h */
 2 static const char *kobject_actions[] = {
 3 [KOBJ_ADD] =    "add",
 4 [KOBJ_REMOVE] =    "remove",
 5 [KOBJ_CHANGE] =    "change",
 6 [KOBJ_MOVE] =    "move",
 7 [KOBJ_ONLINE] =    "online",
 8 [KOBJ_OFFLINE] =    "offline",
 9 };
10 
11 /**
12 * kobject_action_type - translate action string to numeric type
13 *
14 * @buf: buffer containing the action string, newline is ignored
15 * @len: length of buffer
16 * @type: pointer to the location to store the action type
17 *
18 * Returns 0 if the action string was recognized.
19 */
20 int kobject_action_type(const char *buf, size_t count,
21 enum kobject_action *type)
22 {
23 enum kobject_action action;
24 int ret = -EINVAL;
25 
26 if (count && (buf[count-1] == '\n' || buf[count-1] == '\0'))
27 count--;
28 
29 if (!count)
30 goto out;
31 
32 for (action = 0; action < ARRAY_SIZE(kobject_actions); action++) {
33 if (strncmp(kobject_actions[action], buf, count) != 0) /* 把buf和全局變量kobject_actions字符串比較 */
34 continue;
35 if (kobject_actions[action][count] != '\0') /* 不是結束符 */
36 continue;
37 *type = action; /* 比較成功則數字cation就是對應的action */
38 ret = 0;
39 break;
40 }
41 out:
42 return ret;
43 }

說明:怎么指定處理uevent的用戶空間程序(簡稱uevent helper)? 

上面介紹kobject_uevent_env的內部動作時,有提到,Uevent模塊通過Kmod上報Uevent時,會通過call_usermodehelper函數,調用用戶空間的可執行文件(或者腳本,簡稱uevent helper )處理該event。而該uevent helper的路徑保存在uevent_helper數組中。 

char uevent_helper[UEVENT_HELPER_PATH_LEN] = CONFIG_UEVENT_HELPER_PATH;

配置內核.config文件把CONFIG_UEVENT_HELPER_PATH設置為空,則熱插拔的路徑這個全局數組默認是為空的。
可以在編譯內核時,通過CONFIG_UEVENT_HELPER_PATH配置項,靜態指定uevent helper。但這種方式會為每個event fork一個進程,隨着內核支持的設備數量的增多,這種方式在系統啟動時將會是致命的(可以導致內存溢出等)。因此只有在早期的內核版本中會使用這種方式,現在內核不再推薦使用該方式。因此內核編譯時,需要把該配置項留空。 

在系統啟動后,大部分的設備已經ready,可以根據需要,重新指定一個uevent helper,以便檢測系統運行過程中的熱拔插事件。這可以通過把helper的路徑寫入到"/sys/kernel/uevent_helper”文件中實現。實際上,內核通過sysfs文件系統的形式,將uevent_helper數組開放到用戶空間,供用戶空間程序修改訪問,具體可參考"./kernel/ksysfs.c”中相應的代碼,這里不再詳細描述。

參考博文:https://blog.csdn.net/qq_16777851/java/article/details/81395281


免責聲明!

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



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