2.3 內核對象及集合
Linux驅動模型的基礎是內核對象。它將總線類型、設備、驅動等都看作是內核對象。表示內核對象的結構是kobject,相當于Linux驅動模型的“基類”。kobject結構中各個域的描述如表2-2所示。
表2-2 kobject結構中的域(來自文件include/linux/kobject.h)
在繼續之前,我們有必要介紹Linux內核中用于實現循環雙鏈表的list_head結構。在Linux內核中,有大量的數據結構需要用到雙循環鏈表,例如進程、文件、模塊、頁面等。若采用雙循環鏈表的傳統實現方式,需要為這些數據結構維護各自的鏈表,并且為每個鏈表都要設計插入、刪除等操作函數。
在傳統的雙循環鏈表實現中,如果創建某種數據結構的雙循環鏈表,通常采用的辦法是在這個數據結構的類型定義中加入兩個(指向該數據結構對象的)指針next和prev。例如:
typedef struct foo { … struct foo *prev; struct foo *next; … } foo_t;
這種方式下,由于用來維持鏈表的next和prev指針指向對應類型的對象,因此一種數據結構的鏈表操作函數不能用于操作其他數據結構的鏈表。圖2-3給出了對應的節點結構、空的雙循環鏈表和非空的雙循環鏈表示意圖。

圖2-3 傳統方式下的雙循環鏈表結構
而Linux內核采用了一種(數據結構)類型無關的雙循環鏈表實現方式。其思想是將指針prev和next從具體的數據結構中提取出來構成一種通用的“雙鏈表”數據結構list_head(參見文件include/linux/list.h)。list_head被作為一個成員嵌入到要拉鏈的數據結構(被成為宿主數據結構)中。這樣,只需要一套通用的鏈表操作函數就可以將list_head成員作為“連接件”,把宿主數據結構鏈接起來。將連接件轉換為宿主結構,使用的是前面介紹過的contain_of宏,如圖2-4所示。

圖2-4 Linux內核中的雙循環鏈表結構
在Linux內核中的雙循環鏈表實現方式下:
? 鏈表結構作為一個成員嵌入到宿主數據結構內;
? 可以將鏈表結構放在宿主結構內的任何地方;
? 可以為鏈表結構取任何名字;
? 宿主結構可以有多個鏈表結構。
回到kobject,有些成員或方法是內核對象類型特定的,也就是說,對該類型的所有內核對象,這些成員和方法是相同的。其中一個明顯的例子就是前面提到的release方法,雖然不同類型的對象的release方法不同,但同一類型的對象的release方法相同(只不過它以不同的對象實例為參數)。其他的例子還有該類型內核對象的默認屬性,以及該類型內核對象的屬性讀/寫實現方法。這些類型特定的域,被提取出來,定義在內核對象類型結構kobj_type中。kobj_type結構中的域及其描述如表2-3所示。
表2-3 kobj_type結構中的域(來自文件include/linux/kobject.h)
在某種程度上,kset看上去像kobj_type結構的擴展:它表示內核對象的集合。但是,這兩個概念被特意區分開來:kobj_type關注于對象的類型,而kset則強調對象的“聚合”或“集合”。kset結構中的域及其描述如表2-4所示。
表2-4 kset結構中的域(來自文件include/linux/kobject.h)
以面向對象的術語,kset是一個頂層包含類;kset集成了它自己的kobject,本身就可以作為kobject對待。kset包含一系列的kobject,將它們組織成一個鏈表,kset的list域為表頭,被包含的kobject通過entry域鏈入此鏈表,kobject還通過kset域指回到包含它的kset。
前面看到,每個kobject都有一個parent域。大多數情況下,被包含的kobject通過它指向包含它的kset,更精確地說,是kset內嵌的kobject。但實際上,被包含的kobject也有可能將parent指向另外的kobject,或者設置為NULL。kset和kobject的關系圖如圖2-5所示。

圖2-5 kset和kobject的關系圖
雖然在圖2-5中畫出了kobject與kset的關系,但是,需要記住:(1)圖中所有被包含的kobject實際上是嵌入在某個其他類型之內,甚至可能是其他kset之內;(2)也存在不包含于任何kset的kobject,即,它們的kset域為NULL。
通過上面的方式,kobject被組織成層次結構。而kset的存在是為了對層次在它之下的kobject施行相同模式的操作。kset定義了一個uevent操作表,對于一個層次在它之下的kobject,并且層次路徑上沒有其他的kset,如果這個kobject上發生了某種事件,就會調用操作表中的相應函數,以便通知用戶空間。
kset層次下可以包含不同對象類型的kobject。例如,devices_kset下包含類型為dynamic_kobj_ktype的kobject(名字為virtual),類型為kset_ktype的kset(名字為system),以及類型為device_ktype的kobject(對應PCI根總線)。
相同類型的kobject也可以出現在不同的kset下。例如,早期的Linux內核,以及當前版本的內核如果在編譯時指定了CONFIG_SYSFS_DEPRECATED選項,就會把block_class的kset域設為NULL,而其他類的kset域則指向class_kset,盡管它們都是class_ktype類型,
總結一下,kset具有以下功能:
? 作為包含一組對象的容器,kset可以被內核用來跟蹤“所有塊設備”或者“所有PCI設備驅動”;
? 作為一個目錄級的“粘合劑”,將設備模型中的內核對象(以及sysfs)粘在一起。每個kset都內嵌一個kobject,可以作為其他kobject的父親,通過這種方式構造設備模型層次;
? kset可以支持kobject的“熱插拔”,影響熱插拔事件被報告給用戶空間的方式。
2.3.1 創建或初始化內核對象
在使用內核對象之前,必須創建或初始化kobject結構體。對應的函數分別是kobject_create或kobject_init。
如果使用者已經為kobject自行分配了空間,則只需要調用kobject_init。例如,驅動模型中的device結構體內嵌了一個內核對象,在分配device結構體的空間時,也為內嵌內核對象準備了空間,這種情況下,只需要調用kobject_init。
否則,使用者需要調用kobject_create,這個函數先為kobject分配空間,接著調用kobject_init。
創建kobject的代碼當然需要初始化該對象。某些域通過(強制)調用kobject_init函數來建立,如程序2-1所示:
程序2-1 函數kobject_init代碼(摘自文件lib/kobject.c)
kobject_init() 270void kobject_init(struct kobject *kobj, struct kobj_type *ktype) 271{ 272 char *err_str; 273 274 if (!kobj) { 275 err_str = "invalid kobject pointer!"; 276 goto error; 277 } 278 if (!ktype) { 279 err_str = "must have a ktype to be initialized properly!\n"; 280 goto error; 281 } 282 if (kobj->state_initialized) { 283 /* do not error out as sometimes we can recover */ 284 printk(KERN_ERR "kobject (%p): tried to init an initialized " 285 "object, something is seriously wrong.\n", kobj); 286 dump_stack(); 287 } 288 289 kobject_init_internal(kobj); 290 kobj->ktype = ktype; 291 return; 292 293error: 294 printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str); 295 dump_stack(); 296}
要正確創建kobject,ktype是必須的,因為每個kobject都必須有相關的kobj_type。函數首先進行一些必要的檢查。第274~281行確保傳入的內核對象指針不為NULL。內核對象原則上不應該被重復初始化,但萬一出現這樣的情況,我們也不準備退出,而是打印錯誤消息后,讓程序繼續執行(第282行~286行)。初始化的工作是調用kobject_init_internal函數設置內核對象的一些域(第289行),然后將內核對象關聯到指定的對象類型(第290行),如程序2-2所示。
程序2-2 函數kobject_init_internal代碼(摘自文件lib/kobject.c)
kobject_init()→kobject_init_internal() 145static void kobject_init_internal(struct kobject *kobj) 146{ 147 if (!kobj) 148 return; 149 kref_init(&kobj->kref); 150 INIT_LIST_HEAD(&kobj->entry); 151 kobj->state_in_sysfs = 0; 152 kobj->state_add_uevent_sent = 0; 153 kobj->state_remove_uevent_sent = 0; 154 kobj->state_initialized = 1; 155}
kobject_init_internal設置內核對象的一些域,主要包括:
? 初始化內核對象的內嵌引用計數;
? 初始化內核對象用于鏈接到kset的連接件;
? 當前內核對象還沒有被添加到sysfs文件系統,此外,也沒有向用戶空間發送任何uevent事件,因此對應域都置為0;
? 最后,這個函數執行完,就意味著內核對象已經初始化好,設置其state_initialized域。
2.3.2 將內核對象添加到sysfs文件系統
在調用kobject_init后,要向sysfs注冊這個kobject,必須調用kobject_add函數。如果kobject結構體不準備在sysfs層次中使用,就不要調用kobject_add函數,kobject_add代碼如程序2-3所示。
程序2-3 函數kobject_add代碼(摘自文件lib/kobject.c)
kobject_add() 338int kobject_add(struct kobject *kobj, struct kobject *parent, 339 const char *fmt, ...) 340{ 341 va_list args; 342 int retval; 343 344 if (!kobj) 345 return -EINVAL; 346 347 if (!kobj->state_initialized) { 348 printk(KERN_ERR "kobject '%s' (%p): tried to add an " 349 "uninitialized object, something is seriously wrong.\n", 350 kobject_name(kobj), kobj); 351 dump_stack(); 352 return -EINVAL; 353 } 354 va_start(args, fmt); 355 retval = kobject_add_varg(kobj, parent, fmt, args); 356 va_end(args); 357 358 return retval; 359}
顯然,能夠添加到sysfs的內核對象必須是已經初始化過的,第347~353行進行驗證。
然后,函數在第354~356行調用kobject_add_varg真正進行處理,這里也是一個可變數目參數到固定數目參數的轉化,如程序2-4所示。
程序2-4 函數kobject_add_varg代碼(摘自文件lib/kobject.c)
kobject_add()→kobject_add_varg 299static int kobject_add_varg(struct kobject *kobj, struct kobject *parent, 300 const char *fmt, va_list vargs) 301{ 302 int retval; 303 304 retval = kobject_set_name_vargs(kobj, fmt, vargs); 305 if (retval) { 306 printk(KERN_ERR "kobject: can not set name properly!\n"); 307 return retval; 308 } 309 kobj->parent = parent; 310 return kobject_add_internal(kobj); 311}
前面說的可變參數是用于內核對象名字的,在第304行調用kobject_set_name_vargs函數設置它。然后將內核對象的parent域設置為傳入的父內核對象的指針,最后調用kobject_add_internal函數,其代碼如程序2-5所示。
程序2-5 函數kobject_add_internal代碼(摘自文件lib/kobject.c)
kobject_add()→kobject_add_varg()→kobject_add_internal 158static int kobject_add_internal(struct kobject *kobj) 159{ 160 int error = 0; 161 struct kobject *parent; 162 163 if (!kobj) 164 return -ENOENT; 165 166 if (!kobj->name || !kobj->name[0]) { 167 WARN(1, "kobject: (%p): attempted to be registered with empty " 168 "name!\n", kobj); 169 return -EINVAL; 170 } 171 172 parent = kobject_get(kobj->parent); 173 174 /* join kset if set, use it as parent if we do not already have one */ 175 if (kobj->kset) { 176 if (!parent) 177 parent = kobject_get(&kobj->kset->kobj); 178 kobj_kset_join(kobj); 179 kobj->parent = parent; 180 } 181 182 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n", 183 kobject_name(kobj), kobj, __func__, 184 parent ? kobject_name(parent) : "<NULL>", 185 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>"); 186 187 error = create_dir(kobj); 188 if (error) { 189 kobj_kset_leave(kobj); 190 kobject_put(parent); 191 kobj->parent = NULL; 192 193 /* be noisy on error issues */ 194 if (error == -EEXIST) 195 printk(KERN_ERR "%s failed for %s with " 196 "-EEXIST, don't try to register things with " 197 "the same name in the same directory.\n", 198 __func__, kobject_name(kobj)); 199 else 200 printk(KERN_ERR "%s failed for %s (%d)\n", 201 __func__, kobject_name(kobj), error); 202 dump_stack(); 203 } else 204 kobj->state_in_sysfs = 1; 205 206 return error; 207}
我們在前面剛剛為內核對象設置了名字和父對象,這里需要再確認一下。在第166~170行,如果名字為NULL或者為空字符串,則返回錯誤。
第175~180行的意思是如果設置了內核對象的kset域,并且內核對象的parent域為空,則將它重新設置為kset域的內嵌內核對象。內核對象的parent域將決定內核對象在sysfs樹中的位置。因此,在這個函數調用之前,存在以下三種可能。
? 內核對象的parent域和kset域都已設置,則內核對象在sysfs樹中的位置由其parent域決定,它將被放置在內核對象的父對象所對應的目錄下。
? 如果內核對象的parent域為NULL,而kset域已設置,在這里會把kset域的內嵌內核對象賦值給內核對象的parent域,因此kset決定了內核對象在sysfs樹中的位置,它將放置在kset的內嵌內核對象所對應的目錄下。
? 如果內核對象的parent域與kset域均為NULL,則內核對象將被放置在sysfs文件系統樹的根目錄下。
上面的敘述也暗含了一點:如果kobject和特定的kset關聯,則在調用kobject_add函數之前必須給kobj->kset賦值。
第187行調用create_dirs(其代碼如程序2-6所示)執行實際的工作,我們在下面將介紹。如果從create_dirs返回后,發現錯誤,則回滾前面的操作,并根據錯誤類型輸出適當的內核消息。如果成功,那么在第204行將內核對象描述符的state_in_sysfs域設為1,表示它已經添加到sysfs.
程序2-6 函數create_dir代碼(摘自文件lib/kobject.c)
kobject_add()→kobject_add_varg()→kobject_add_internal()→create_dir() 47static int create_dir(struct kobject *kobj) 48{ 49 int error = 0; 50 if (kobject_name(kobj)) { 51 error = sysfs_create_dir(kobj); 52 if (!error) { 53 error = populate_dir(kobj); 54 if (error) 55 sysfs_remove_dir(kobj); 56 } 57 } 58 return error; 59}
前面說過,將kobject添加到sysfs的主要工作是為它創建對應的目錄,并在這個目錄下創建屬性對應的文件。這兩點分別調用sysfs_create_dir和populate_dir函數(其代碼如程序2-7所示)。這兩部分的工作應該是“原子性”的:如果后者出錯,則應該回滾前者已經做過的操作。
sysfs_create_dir函數在介紹sysfs文件系統的內部樹構建API時會有介紹,我們現在只需要知道,它為內核對象創建目錄。如果內核對象的parent域不為空,則會在其父內核對象的對應目錄下創建之;否則在sysfs文件系統的根目錄下創建。
程序2-7 函數populate_dir代碼(摘自文件lib/kobject.c)
kobject_add()→kobject_add_varg()→kobject_add_internal()→create_dir()→populate_dir() 30static int populate_dir(struct kobject *kobj) 31{ 32 struct kobj_type *t = get_ktype(kobj); 33 struct attribute *attr; 34 int error = 0; 35 int i; 36 37 if (t && t->default_attrs) { 38 for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) { 39 error = sysfs_create_file(kobj, attr); 40 if (error) 41 break; 42 } 43 } 44 return error; 45}
polulate_dir的邏輯也比較直觀,它逐個處理內核對象所屬對象類型的默認屬性,對每個屬性,調用sysfs_create_file函數在內核對象的目錄下創建以屬性名為名字的文件。
調用kobject_add函數將內核對象添加到sysfs文件系統,只會為默認屬性自動創建文件。如果調用者定義了其他的屬性,則需要加上專門的代碼(一般還是調用sysfs_create_file等函數)來添加對應的文件,我們在各個子系統實現中會看到很多這樣的例子。
需要補充的是,Linux內核封裝了兩個輔助函數方便調用:在創建或初始化內核對象后,一次性地將內核對象添加到sysfs文件系統。這兩個函數是:
? struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
? int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...)。
這里的參數對照上面描述的kobject_create、kobject_init和kobject_add函數來理解。
2.3.3 創建、初始化、添加內核對象集
對于初始化和設置,kset有一套和kobject非常接近的接口,存在下面的一些函數。
? void kset_init(struct kset *kset)
初始化內核對象集,調用者已經為該內核對象集分配好空間。
? static struct kset *kset_create(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj)
為內核對象集分配空間,并初始化之。
? int kset_add(struct kset *kset)
將內核對象集添加到sysfs文件系統中。
? int kset_register(struct kset *kset)
初始化內核對象集,并一次性地將它添加到sysfs文件系統,調用者已經為該內核對象集分配好空間。
? struct kset *kset_create_and_add(const char *name, const struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj)
為內核對象集分配空間,初始化之,并一次性地將它添加到sysfs文件系統。
2.3.4 發送內核對象變化事件到用戶空間
在內核對象被注冊到kobject核心后,需要聲明它已經被創建好,這可以調用kobject_uevent函數。事實上,當內核對象發生了特定事件,需要通知到用戶空間時,都需要調用kobject_uevent函數,代碼如程序2-8所示。
程序2-8 函數kobject_uevent()代碼(摘自文件lib/kobject_uevent.c)
kobject_uevent() 281int kobject_uevent(struct kobject *kobj, enum kobject_action action) 282{ 283 return kobject_uevent_env(kobj, action, NULL); 284}
kobject_uevent函數有兩個參數:kobj為當事者內核對象,即發生事件的內核對象;而action為發生的事件,取值如下。
? KOBJ_ADD/KOBJ_REMOVE,內核對象被添加/刪除,例如熱插拔了一個設備。
? KOBJ_CHANGE,內核對象的屬性發生了改變,例如分區表發生了變化。
? KOBJ_MOVE,內核對象的位置發生了移動,設備名改變也被歸到這類事件。
? KOBJ_ONLINE/KOBJ_OFFLINE,內核對象在線/離線,最常見的例子是CPU。
該函數直接調用kobject_uevent_env(代碼如程序2-9所示)執行具體的操作,在調用時添加第三個參數,設為NULL,表示沒有外部的環境數據。
程序2-9 函數kobject_uevent_env()代碼(摘自文件lib/kobject_uevent.c)
kobject_uevent()→kobject_uevent_env() 90int kobject_uevent_env(struct kobject *kobj, enum kobject_action action, 91 char *envp_ext[]) 92{ 93 struct kobj_uevent_env *env; 94 const char *action_string = kobject_actions[action]; 95 const char *devpath = NULL; 96 const char *subsystem; 97 struct kobject *top_kobj; 98 struct kset *kset; 99 const struct kset_uevent_ops *uevent_ops; 100 u64 seq; 101 int i = 0; 102 int retval = 0; 103 104 pr_debug("kobject: '%s' (%p): %s\n", 105 kobject_name(kobj), kobj, __func__); 106 107 /* search the kset we belong to */ 108 top_kobj = kobj; 109 while (!top_kobj->kset && top_kobj->parent) 110 top_kobj = top_kobj->parent; 111 112 if (!top_kobj->kset) { 113 pr_debug("kobject: '%s' (%p): %s: attempted to send uevent " 114 "without kset!\n", kobject_name(kobj), kobj, 115 __func__); 116 return -EINVAL; 117 } 118 119 kset = top_kobj->kset; 120 uevent_ops = kset->uevent_ops; 121 122 /* skip the event, if uevent_suppress is set*/ 123 if (kobj->uevent_suppress) { 124 pr_debug("kobject: '%s' (%p): %s: uevent_suppress " 125 "caused the event to drop!\n", 126 kobject_name(kobj), kobj, __func__); 127 return 0; 128 } 129 /* skip the event, if the filter returns zero. */ 130 if (uevent_ops && uevent_ops->filter) 131 if (!uevent_ops->filter(kset, kobj)) { 132 pr_debug("kobject: '%s' (%p): %s: filter function " 133 "caused the event to drop!\n", 134 kobject_name(kobj), kobj, __func__); 135 return 0; 136 } 137 138 /* originating subsystem */ 139 if (uevent_ops && uevent_ops->name) 140 subsystem = uevent_ops->name(kset, kobj); 141 else 142 subsystem = kobject_name(&kset->kobj); 143 if (!subsystem) { 144 pr_debug("kobject: '%s' (%p): %s: unset subsystem caused the " 145 "event to drop!\n", kobject_name(kobj), kobj, 146 __func__); 147 return 0; 148 } 149 150 /* environment buffer */ 151 env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); 152 if (!env) 153 return -ENOMEM; 154 155 /* complete object path */ 156 devpath = kobject_get_path(kobj, GFP_KERNEL); 157 if (!devpath) { 158 retval = -ENOENT; 159 goto exit; 160 } 161 162 /* default keys */ 163 retval = add_uevent_var(env, "ACTION=%s", action_string); 164 if (retval) 165 goto exit; 166 retval = add_uevent_var(env, "DEVPATH=%s", devpath); 167 if (retval) 168 goto exit; 169 retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); 170 if (retval) 171 goto exit; 172 173 /* keys passed in from the caller */ 174 if (envp_ext) { 175 for (i = 0; envp_ext[i]; i++) { 176 retval = add_uevent_var(env, "%s", envp_ext[i]); 177 if (retval) 178 goto exit; 179 } 180 } 181 182 /* let the kset specific function add its stuff */ 183 if (uevent_ops && uevent_ops->uevent) { 184 retval = uevent_ops->uevent(kset, kobj, env); 185 if (retval) { 186 pr_debug("kobject: '%s' (%p): %s: uevent() returned " 187 "%d\n", kobject_name(kobj), kobj, 188 __func__, retval); 189 goto exit; 190 } 191 } 192 193 /* 194 * Mark "add" and "remove" events in the object to ensure proper 195 * events to userspace during automatic cleanup. If the object did 196 * send an "add" event, "remove" will automatically generated by 197 * the core, if not already done by the caller. 198 */ 199 if (action == KOBJ_ADD) 200 kobj->state_add_uevent_sent = 1; 201 else if (action == KOBJ_REMOVE) 202 kobj->state_remove_uevent_sent = 1; 203 204 /* we will send an event, so request a new sequence number */ 205 spin_lock(&sequence_lock); 206 seq = ++uevent_seqnum; 207 spin_unlock(&sequence_lock); 208 retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq); 209 if (retval) 210 goto exit; 211 212#if defined(CONFIG_NET) 213 /* send netlink message */ 214 if (uevent_sock) { 215 struct sk_buff *skb; 216 size_t len; 217 218 /* allocate message with the maximum possible size */ 219 len = strlen(action_string) + strlen(devpath) + 2; 220 skb = alloc_skb(len + env->buflen, GFP_KERNEL); 221 if (skb) { 222 char *scratch; 223 224 /* add header */ 225 scratch = skb_put(skb, len); 226 sprintf(scratch, "%s@%s", action_string, devpath); 227 228 /* copy keys to our continuous event payload buffer */ 229 for (i = 0; i < env->envp_idx; i++) { 230 len = strlen(env->envp[i]) + 1; 231 scratch = skb_put(skb, len); 232 strcpy(scratch, env->envp[i]); 233 } 234 235 NETLINK_CB(skb).dst_group = 1; 236 retval = netlink_broadcast(uevent_sock, skb, 0, 1, 237 GFP_KERNEL); 238 /* ENOBUFS should be handled in userspace */ 239 if (retval == -ENOBUFS) 240 retval = 0; 241 } else 242 retval = -ENOMEM; 243 } 244#endif 245 246 /* call uevent_helper, usually only enabled during early boot */ 247 if (uevent_helper[0]) { 248 char *argv [3]; 249 250 argv [0] = uevent_helper; 251 argv [1] = (char *)subsystem; 252 argv [2] = NULL; 253 retval = add_uevent_var(env, "HOME=/"); 254 if (retval) 255 goto exit; 256 retval = add_uevent_var(env, 257 "PATH=/sbin:/bin:/usr/sbin:/usr/bin"); 258 if (retval) 259 goto exit; 260 261 retval = call_usermodehelper(argv[0], argv, 262 env->envp, UMH_WAIT_EXEC); 263 } 264 265exit: 266 kfree(devpath); 267 kfree(env); 268 return retval; 269}
Linux內核以環境數據的形式向用戶空間報告內核對象變化。環境數據包含多個環境變量/值對,每一對都是variable=value的形式,各對之間用“\0”字符分隔。以下是一個USB設備插入到系統中時,內核報告的環境變量/值內容(為閱讀方便,已經將“\0”字符替換為換行符)。
ACTION=add DEVPATH=/devices/pci0000:00/0000:00:0b.1/usb1/1-8 SUBSYSTEM=usb MAJOR=189 MINOR=9 DEVTYPE=usb_device DEVICE=/proc/bus/usb/001/010 PRODUCT=58f/6335/102 TYPE=0/0/0 BUSNUM=001 DEVNUM=010 SEQNUM=2882
環境數據為Linux系統支持設備熱插拔至關重要,因為熱插拔需要由內核和用戶空間配合完成。內核先檢測到設備狀態變化,進行內核部分的處理,例如構建相應的數據結構等,用戶空間也需要做一些準備,主要是建立用戶賴以訪問該設備的“渠道”。例如udevd服務程序捕獲來自內核的設備事件,從環境數據獲得事件的詳細信息,它知道有USB設備被加入到系統,匹配預先定義的規則(用戶策略),就可以在/dev目錄下為它生成設備節點(使用mknod),或加載驅動程序(使用modprobe),等等。
內核表示環境數據的結構是kobj_uevent_env,其結構中的域如表2-5所示。這個結構既記錄環境數據構造的中間過程,又保存環境數據構造的最終結果。
表2-5 kobj_uevent_env結構中的域(來自文件include/linux/kobject.h)
環境數據內容被記錄在一個緩沖區buf,最多可容納2048個字節。環境數據內容被分為多個段,如圖2-6所示,每段為“variable=value\0”的形式,每段的起始地址被記錄在環境變量/值數組envp,該數組最多有32項。envp_idx和buflen分別為數組當前索引和內容當前長度,是動態變化的。每次將一個環境變量/值對添加到環境數據中時,執行以下步驟。
? 環境變量/值被填入到緩沖區buf中bufflen指示的位置,以“\0”結尾。
? 更新內容當前長度bufflen。
? 數組envp中envp_idx指示的當前項被填入環境變量/值的起始位置地址。
? 更新數組當前索引envp_idx。

圖2-6 uevent環境數據
kobject_uevent_env函數第107~120行從kobject開始沿層次向上搜索,找到最近的kset。這個kset中定義了一個操作表(kset_uevent_ops結構),kset_uevent_ops結構中的域如表2-6所示,其中的回調函數決定了在這個內核對象事件被報告用戶空間的方式,以及向用戶空間報告的內容。因此顯然,如果找不到這樣的kset,那么就沒有辦法向用戶空間發送事件。
表2-6 kset_uevent_ops結構中的域(來自文件include/linux/kobject.h)
此外,有幾種情況不需要向用戶空間發送消息,這些情況如下。
? 內核對象本身“抑制”發送,也就是它的uevent_suppress域被設置為1。
? 該事件被kset過濾掉,即kset操作集中的filter回調函數執行結果返回0。
? 子系統名字為空,即kset操作集的name回調函數執行結果返回NULL或kset的名字為NULL。
對于這幾種情況,我們打印一條調試消息后返回。
如果通過上面的檢查,就需要開始準備環境數據的緩沖區了,第151~153行分配緩沖區空間。然后是添加環境變量/值對,在第163~171行依次添加了環境變量ACTION、DEVPATH、SUBSYSTEM和對應的值,第174~180行添加通過參數傳入的外部環境變量/值。
在第183~191行,如果kset定義了uevent回調函數,調用它,這會添加各個子系統特定的環境變量/值。例如,對于devices_kset,該函數的實現是device_uevent_ops,它將加入設備的主設備號、設備的驅動名,以及設備所屬總線特定的、設備所屬類特定的和設備所屬類型特定的環境變量/值。環境變量/值之間用“\0”字符分隔。
環境數據構造完畢,接下來就需要發送到用戶空間了。這有以下兩種方法。
? 如果編譯Linux內核時選擇了CONFIG_NET選項,即將網絡系統編譯進來,那么就可以使用netlink機制,這是一種網絡協議族的實現,通過套接口實現用戶空間和內核的通信。函數最終在第236行調用netlink_broadcast,用戶空間進程(例如udevd)監聽到來自內核的消息后進行相應的處理。
? 第二種方法是使用user_helper機制,這是從Linux內核代碼啟動用戶空間的應用程序(例如/sbin/hotplug腳本)。關于這一機制的細節,請讀者自行分析。我們看到,函數在第261行調用call_usermodehelper函數。