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