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函数。