设备模型: 一、概述 从2.6内核引入了sysfs文件系统,与proc, devfs, devpty同类别,属于虚拟的文件系统。目的是展示设备驱动模型中各组件的层次关系,第一层目录:block, device, bus, drivers, class, power, firmware. block 块设备;devices 系统所有的设备并根据设备挂接的总线类型组织成层次结构;bus 系统所有的总线类型;drivers 内核中所有已经注册的设备驱动程序;class 系统中的设备类型(如网卡 设备、声卡设备、输入设备等)。在/sys/bus下和/sys/bus/下也会有设备文件,但那只是符号链接,它指向/sys/devices/下的真实设备。此即为Linux设备模型。 总线(bus)、设备(device)、驱动(driver)3个数据结构构成了设备的上层建筑,而kobject、kset、kobj_type(ktype)这三个数据结构构成了设备模型的经济基础。内核引入内核这概念最主要的目的无非就是为了省电,便于管理。Linux设备模型的目的是:为内核建立起一个统一的设备模型,从而有一个对系统结构的一般性抽象描述。设备模型提供了这个抽象. 现在它用在内核来支持不同的任务, 包括: 1、电源管理,根据设备的层次关系,当系统进入睡眠的时候,不需要一个一个设备的关,只需要关一个总线设备,接在总线下的设备就都会关掉。 2、sysfs虚拟文件系统的实现与设备模型的紧密相关,并向外界展示它所表述的结构。向用户空间提供系统信息、改变操作参数的接口正越来越多地通过 sysfs,也就是设备模型来完成。 3、关于热插拔,这跟扫描有关系,比如说,你把一个设备直接接在USB上,系统就会去扫描设备,并且在USB总线上寻找匹配的设备驱动,最后初始化设备,等待用户使用。 4、设备模型的实现需要创建一系列机制来处理对象的生命周期、对象间的关系和对象在用户空间的表示。Linux设备模型是一个复杂的数据结构。但对模型的大部分来说,Linux设备模型代码会处理好这些关系,而不是把他们强加于驱动作者。模型隐藏于交互的背后,与设备模型的直接交互通常由总线级的逻辑和其他的内核子系统处理。所以许多驱动作者可完全忽略设备模 型, 并相信设备模型能处理好他所负责的事。 在具体实现方面分两个层次: 一是底层数据结构来实现基本对象及其层次关系:kobjects和ksets。 二是基于这两个底层数据结构上实现的设备模型:总线,设备,驱动。 二、底层数据结构:kobject,kset 1)Kobject 1.概念 Kobject实现基本的面向对象管理机制,是构成设备模型的核心结构。它与sysfs文件系统紧密相连,在内核中注册每个kobject对象对应sysfs文件系统中的一个目录。 内核通过kobject 结构将各个对象连接起来组成一个分层的结构体系,可以把kobject理解为面向对象的基类,确切的说kobject也可以理解为所有驱动对象的基类,作为基类的kobject并不关心自己是如何实现的,所以,在内核中,没有用kobject直接定义的变量,kobject只是作为一个抽象的基类而存在,而由于Linux内核是C编写的,通过查看device_driver、devic等结构体中内嵌了kobject结构体。 2.结构,定义在 <linux/kobject.h> struct kobject { const char * k_name;/*指向设备名称的指针 */ char name[KOBJ_NAME_LEN];/*kobject 的名字数组,设备名称*/ struct kref kref;/*kobject 的引用计数*/ struct list_head entry;/*kobject 之间的双向链表,与所属的kset形成环形链表*/ struct kobject * parent;/*在sysfs分层结构中定位对象,指向上一级kset中的struct kobject kobj*/ struct kset * kset;/*指向所属的kset*/ struct kobj_type * ktype;/*负责对该kobject类型进行跟踪的struct kobj_type的指针*/ struct dentry * dentry;/*sysfs文件系统中与该对象对应的文件节点路径指针*/ wait_queue_head_t poll;/*等待队列头*/ }; 3.功能 这个在层次上处理最顶层的kobject结构提供了所有模型需要的最基本的功能: (1)引用计数:跟踪对象生命周期的一种方法是使用引用计数。当没有内核代码持有该对象的引用时, 该对象将结束自己的有效生命期并可被删除。 (2)sysfs表示 每个sys/下的对象对应着一个kobject。 (3)热拔插事件处理。 处理设备的热拔插事件。 4.相关函数: void kobjet_init(struct kobject*kobj)//初始化Kobject int kobject_add(struct kobject*kobj)//将Kobject对象注册到linux系统,如果失败则返回一个错误码. struct kobject *kobject_get(struct kobject *kobj);/*若成功,递增 kobject 的引用计数并返回一个指向 kobject 的指针,否则返回 NULL。必须始终测试返回值以免产生竞态*/ void kobject_put(struct kobject *kobj);/*递减引用计数并在可能的情况下释放这个对象*/ int kobject_init_and_add(structkobject *kobj, kobj_type *ktype, struct kobject *parent, const *fmt…) //初始化并注册kobject,kobject传入要初始化的Kobject对象,ktype将在后面介绍到,parent指向上级的kobject对象,如果指定位NULL,将在/sys的顶层创建一个目录。*fmt为kobject对象的名字。 kobject的ktype对象是一个指向kobject_type结构的指针,该结构记录了kobject对象的一些属性。每个kobject都需要对应一个相应的kobj_type结构。 struct kobj_type{ void (*release)(struct kobject *kobj); structsysfs_ops *sysfs_ops; structattribute **default_attrs; }; release方法用于释放kobject占用的资源,当kobject引用计数为0时被调用。 kobje_type的attribute成员: struct attribute{ char*name;//属性文件名 structmodule *owner; mode_tmode; } struct attribute(属性):对应于kobject的目录下一个文件,name就是文件名。 kobje_type的struct sysfs_ops成员: struct sysfs_ops { ssize_t (*show)(structkobejct *, struct attribute *, char *name); ssize_t (*store)(structkobejct *, struct attribute *, char *name); } show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态; store:当用户写属性文件时,该函数被调用,用于存储用户存入的属性值。 5.kobject的初始化: kobject的初始化较为复杂,但是必须的步骤如下: (1)将整个kobject清零,通常使用memset函数。 (2)调用void kobject_init(struct kobject *kobj)函数,设置结构内部一些成员。所做的一件事情是设置kobject的引用计数为1。会调用kref_init(&kobj->kref);将引用计数设为1. static inline void kref_init(struct kref *kref) { atomic_set(&kref->refcount, 1); } (3)设置kobject的名字 int kobject_set_name(struct kobject *kobj, const char *format, ...); (4)直接或间接设置其它成员:ktype、kset和parent。 (重要) 6.实例 #include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/stat.h> MODULE_AUTHOR("David Xie"); MODULE_LICENSE("Dual BSD/GPL"); void obj_test_release(struct kobject *kobject); ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf); ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count); struct attribute test_attr = { .name = "kobj_config", .mode = S_IRWXUGO, }; static struct attribute *def_attrs[] = { &test_attr, NULL, }; struct sysfs_ops obj_test_sysops = { .show = kobj_test_show, .store = kobj_test_store, }; struct kobj_type ktype = { .release = obj_test_release, .sysfs_ops=&obj_test_sysops, .default_attrs=def_attrs, }; void obj_test_release(struct kobject *kobject) { printk("eric_test: release .\n"); } ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf) { printk("have show.\n"); printk("attrname:%s.\n", attr->name); sprintf(buf,"%s\n",attr->name); return strlen(attr->name)+2; } ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count) { printk("havestore\n"); printk("write: %s\n",buf); return count; } struct kobject kobj; static int kobj_test_init() { printk("kboject test init.\n"); kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test"); return 0; } static int kobj_test_exit() { printk("kobject test exit.\n"); kobject_del(&kobj); return 0; } module_init(kobj_test_init); module_exit(kobj_test_exit); 在/sys目录下创建了kobject_test目录 在kobject_test目录下有kobj_config文件 读kobject_config文件则调用了show函数。并在用户空间显示了show返回的kobject对象名字。 写kobject_config文件调用了store函数。 2)kset 1.概念 set是具有相同类型的kobject的集合,在sysfs中体现成一个目录,在内核中用kset数据结构表示。 一个 kset 的主要功能是容纳; 它可被当作顶层的给 kobjects 的容器类. 实际上, 每个 kset 在内部容纳它自己的 kobject, 并且它可以, 在许多情况下, 如同一个 kobject 相同的方式被对待. 值得注意的是 ksets 一直在 sysfs 中出现; 一旦一个 kset 已被建立并且加入到系统, 会有一个 sysfs 目录给它. kobjects 没有必要在 sysfs 中出现, 但是每个是 kset 成员的 kobject 都出现在那里. 通俗的讲,kobject建立一级的子目录,里面只能包含文件;kset可以为kobject建立多级的层次性的父目录。 2.结构体 如果这个 kobject 是一个 kset 的成员, kset 会提供kobj_type 指针。通常情况下,kobject只需要在叶节点里使用,上层的节点要使用kset。 struct kset { struct kobj_type * ktype; /*指向该kset对象类型的指针*/ struct list_head list;/*用于连接该kset中所有kobject以形成环形链表的链表头*/ spinlock_t list_lock;/*用于避免竞态的自旋锁*/ struct kobject kobj; /*嵌入的kobject*/ struct kset_uevent_ops * uevent_ops; //指向热插拔操作表的指针 }; 包含在kset中的所有的kobject被组织成一个上相的循环链表list域是该链表的头指针,ktype域指向一个kobj_type结构,被盖kset中的所有kobject共享,表示这些对象的类型,kset数据结构还内嵌了一个kobject对象,所有属于这个kset的kobject对象的parent域指向这个内嵌的对象,此外kset还依赖于kobj维护引用计数:这就明了了,kset的引用计数其实就是其内嵌对象kobject对象的的引用计数,具有相同类型的kobject集合在一起组成了kset,许多kset集合在一起组成了子系统subsystem。 kset 在一个标准的内核链表中保存了它的子节点,在大部分情况下, 被包含的 kobjects 在它们的 parent 成员中保存指向 kset内嵌的 kobject的指针,关系如下: (1)ksets 有类似于kobjects初始化和设置接口。 (2)ksets 还有一个指针指向 kobj_type 结构来描述它包含的 kobject,这个类型优先于 kobject 自身中的 ktype 。因此在典型的应用中, 在 struct kobject 中的 ktype 成员被设为 NULL, 而 kset 中的ktype是实际被使用的。 (3)在新的内核里, kset 不再包含一个子系统指针struct subsystem * subsys, 而且subsystem已经被kset取代。 (4)子系统是对整个内核中一些高级部分的表述。子系统通常出现在 sysfs分层结构中的顶层,内核子系统包括 block_subsys(/sys/block 块设备)、 devices_subsys(/sys/devices 核心设备层)以及内核已知的用于各种总线的特定子系统。对于新的内核已经不再有subsystem数据结构了,用kset代替了。每个 kset 必须属于一个子系统,子系统成员帮助内核在分层结构中定位 kset 。 3. 在 sysfs 中创建kobject的入口是kobject_add的工作的一部分,只要调用 kobject_add 就会在sysfs 中显示,还有些知识值得记住: (1)kobjects 的 sysfs 入口始终为目录, kobject_add 的调用将在sysfs 中创建一个目录,这个目录包含一个或多个属性(文件); (2)分配给 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目录名,出现在 sysfs 层次的相同部分的 kobjects 必须有唯一的名字. 分配给 kobjects 的名字也应当是合法的文件名字: 它们不能包含非法字符(如:斜线)且不推荐使用空白。 (3)sysfs 入口位置对应 kobject 的 parent 指针。若 parent 是 NULL ,则它被设置为嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是 NULL, 则sysfs 入口目录在顶层目录,通常不推荐。 4.Kset操作: int kset_register(struct kset*kset)//注册kset void kset_unregister(struct kset*kset)//注销kset 5.实例 #include <linux/device.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> #include <linux/sysfs.h> #include <linux/stat.h> #include <linux/kobject.h> MODULE_AUTHOR("David Xie"); MODULE_LICENSE("Dual BSD/GPL"); struct kset kset_p; struct kset kset_c; int kset_filter(struct kset *kset, struct kobject *kobj) { printk("Filter: kobj %s.\n",kobj->name); return 1; } const char *kset_name(struct kset *kset, struct kobject *kobj) { static char buf[20]; printk("Name: kobj %s.\n",kobj->name); sprintf(buf,"%s","kset_name"); return buf; } int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env) { int i = 0; printk("uevent: kobj %s.\n",kobj->name); while( i < env->envp_idx){ printk("%s.\n",env->envp[i]); i++; } return 0; } struct kset_uevent_ops uevent_ops = { .filter = kset_filter, .name = kset_name, .uevent = kset_uevent, }; int kset_test_init() { printk("kset test init.\n"); kobject_set_name(&kset_p.kobj,"kset_p"); kset_p.uevent_ops = &uevent_ops; kset_register(&kset_p); kobject_set_name(&kset_c.kobj,"kset_c"); kset_c.kobj.kset = &kset_p; kset_register(&kset_c); return 0; } int kset_test_exit() { printk("kset test exit.\n"); kset_unregister(&kset_p); kset_unregister(&kset_c); return 0; } module_init(kset_test_init); module_exit(kset_test_exit); 可以看出当kset加载时,在/sys下创建了一个kset_p,在kset_p下面创建了kset_c,当kset模块被加载和卸载时都产生了热插拔事件。 /*****************************************************************************************************/ int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct kobject *parent, const char *fmt, ...) { va_list args; int retval; //初始化kobject kobject_init(kobj, ktype); va_start(args, fmt); //为kobjcet设置名称,在sysfs中建立相关信息 retval = kobject_add_varg(kobj, parent, fmt, args); va_end(args); return retval; } //上面的流程主要分为两部份。一部份是kobject的初始化。在这一部份,它将kobject与给定的ktype关联起来。初始化kobject中的各项结构。 //另一部份是kobject的名称设置。空间层次关系的设置,具体表现在sysfs文件系统中. void kobject_init(struct kobject *kobj, struct kobj_type *ktype) { char *err_str; if (!kobj) { err_str = "invalid kobject pointer!"; goto error; } if (!ktype) { err_str = "must have a ktype to be initialized properly!\n"; goto error; } if (kobj->state_initialized) {//标志为1表示kobject已经初始化过 printk(KERN_ERR "kobject (%p): tried to init an initialized ""object, something is seriously wrong.\n", kobj); dump_stack(); } kobject_init_internal(kobj); kobj->ktype = ktype;//将kobject与给定的ktype关联起来 return; error: printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str); dump_stack(); } static void kobject_init_internal(struct kobject *kobj) { if (!kobj) return; kref_init(&kobj->kref);//初始化kobject的计数器 INIT_LIST_HEAD(&kobj->entry); kobj->state_in_sysfs = 0; kobj->state_add_uevent_sent = 0; kobj->state_remove_uevent_sent = 0; kobj->state_initialized = 1;//表示kobject已经初始化过 } //另一部份是kobject的名称设置。空间层次关系的设置,具体表现在sysfs文件系统中. static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,const char *fmt, va_list vargs) { va_list aq; int retval; va_copy(aq, vargs); //设置kobject的名字。即kobject的name成员 retval = kobject_set_name_vargs(kobj, fmt, aq); va_end(aq); if (retval) { printk(KERN_ERR "kobject: can not set name properly!\n"); return retval; } //设置kobject的parent。在上面的例子中,我们没有给它指定父结点 kobj->parent = parent; //在sysfs中添加kobjcet信息 return kobject_add_internal(kobj); } //设置好kobject->name后,转入kobject_add_internal()。在sysfs中创建空间结构.代码如下: static int kobject_add_internal(struct kobject *kobj) { int error = 0; struct kobject *parent; if (!kobj) return -ENOENT; //如果kobject的名字为空.退出 if (!kobj->name || !kobj->name[0]) { pr_debug("kobject: (%p): attempted to be registered with empty ""name!\n", kobj); WARN_ON(1); return -EINVAL; } //取kobject的父结点 parent = kobject_get(kobj->parent); //如果kobject的父结点没有指定,就将kset->kobject做为它的父结点 if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); kobj_kset_join(kobj); kobj->parent = parent; } //在sysfs中创建kobject的相关元素 error = create_dir(kobj); if (error) { //v如果创建失败。减少相关的引用计数 kobj_kset_leave(kobj); kobject_put(parent); kobj->parent = NULL; /* be noisy on error issues */ if (error == -EEXIST) printk(KERN_ERR "%s failed for %s with ""-EEXIST, don't try to register things with ""the same name in the same directory.\n",__FUNCTION__, kobject_name(kobj)); else printk(KERN_ERR "%s failed for %s (%d)\n",__FUNCTION__, kobject_name(kobj), error); dump_stack(); } else //如果创建成功。将state_in_sysfs建为1。表示该object已经在sysfs中了 kobj->state_in_sysfs = 1; return error; } //这段代码比较简单,它主要完成kobject父结点的判断和选定,然后再调用create_dir()在sysfs创建相关信息。该函数代码如下: static int create_dir(struct kobject *kobj) { int error = 0; if (kobject_name(kobj)) { error = sysfs_create_dir(kobj);//为kobject创建目录 if (!error) { error = populate_dir(kobj);//为kobject->ktype中的属性创建文件 if (error) sysfs_remove_dir(kobj); } } return error; } //我们在上面的示例中看到的/sys下的eric_test目录,以及该目录下面的eric_xiao的这个文件就是这里被创建的。我们先看一下kobject所表示的目录创建过程。这是在sysfs_create_dir()中完成的。代码如下: int sysfs_create_dir(struct kobject * kobj) { struct sysfs_dirent *parent_sd, *sd; int error = 0; BUG_ON(!kobj); /*如果kobject的parnet存在。就在目录点的目录下创建这个目录。如果没有父结点不存在,就在/sys下面创建结点。在上面的流程中,我们可能并没有为其指定父结点,也没有为其指定kset。*/ if (kobj->parent) parent_sd = kobj->parent->sd; else parent_sd = &sysfs_root; //在sysfs中创建目录 error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd); if (!error) kobj->sd = sd; return error; } //接着看为kobject->ktype中的属性创建文件。这是在populate_dir()中完成的。代码如下: static int populate_dir(struct kobject *kobj) { struct kobj_type *t = get_ktype(kobj); struct attribute *attr; int error = 0; int i; if (t && t->default_attrs) { for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) { error = sysfs_create_file(kobj, attr); if (error) break; } } return error; } //这段代码比较简单。它遍历ktype中的属性。然后为其建立文件。请注意:文件的操作最后都会回溯到ktype->sysfs_ops的show和store这两个函数中. /****************************************************************************************************/ //创建好了kset之后,会调用kset_register().这个函数就是kset操作的核心代码了.如下: int kset_register(struct kset *k) { int err; if (!k) return -EINVAL; kset_init(k); err = kobject_add_internal(&k->kobj); if (err) return err; kobject_uevent(&k->kobj, KOBJ_ADD); return 0; } //在kset_init()里会初始化kset中的其它字段.然后调用kobject_add_internal()为其内嵌的kobject结构建立空间层次结构.之后因为添加了kset.会产生一个事件.这个事件是通过用户空间的hotplug程序处理的.这就是kset明显不同于kobject的地方.详细研究一下这个函数.这对于我们研究hotplug的深层机理是很有帮助的.它的代码如下; int kobject_uevent(struct kobject *kobj, enum kobject_action action) { return kobject_uevent_env(kobj, action, NULL); } //之后,会调用kobject_uevent_env().这个函数中的三个参数含义分别为:引起事件的kobject.事件类型(add,remove,change,move,online,offline等).第三个参数是要添加的环境变量. //代码篇幅较长,我们效仿情景分析上面的做法.分段分析如下: 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; struct kset_uevent_ops *uevent_ops; u64 seq; int i = 0; int retval = 0; pr_debug("kobject: '%s' (%p): %s\n",kobject_name(kobj), kobj, __FUNCTION__); /* search the kset we belong to */ 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!\n", kobject_name(kobj), kobj, __FUNCTION__); return -EINVAL; } //因为对事件的处理函数包含在kobject->kset-> uevent_ops中.要处理事件,就必须要找到上层的一个不为空的kset.上面的代码就是顺着kobject->parent找不到一个不为空的kset.如果不存在这样的kset.就退出 kset = top_kobj->kset; uevent_ops = kset->uevent_ops; /* skip the event, if the filter returns zero. */ 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!\n",kobject_name(kobj), kobj, __FUNCTION__); return 0; } /* originating subsystem */ 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!\n", kobject_name(kobj), kobj, __FUNCTION__); return 0; } //找到了不为空的kset.就跟kset-> uevent_ops->filter()匹配.看这个事件是否被过滤.如果没有被过滤掉.就会调用kset-> uevent_ops->name()得到子系统的名称,如果不存在kset-> uevent_ops->name().就会以kobject->name做为子系统名称. /* environment buffer */ env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL); if (!env) return -ENOMEM; /* complete object path */ devpath = kobject_get_path(kobj, GFP_KERNEL); if (!devpath) { retval = -ENOENT; goto exit; } /* default keys */ retval = add_uevent_var(env, "ACTION=%s", action_string); if (retval) goto exit; retval = add_uevent_var(env, "DEVPATH=%s", devpath); if (retval) goto exit; retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem); if (retval) goto exit; /* keys passed in from the caller */ if (envp_ext) { for (i = 0; envp_ext[i]; i++) { retval = add_uevent_var(env, envp_ext[i]); if (retval) goto exit; } } //接下来,就应该设置为调用hotplug设置环境变量了.首先,分配一个struct kobj_uevent_env结构用来存放环境变量的值.然后调用kobject_get_path()用来获得引起事件的kobject在sysfs中的路径.再调用add_uevent_var()将动作代表的字串,kobject路径,子系统名称填充到struct kobj_uevent_env中,如果有指定环境变量,也将其添加进去. kobject_get_path()和add_uevent_var()都比较简单.这里不再详细分析了.请自行查看源代码 /* let the kset specific function add its stuff */ if (uevent_ops && uevent_ops->uevent) { retval = uevent_ops->uevent(kset, kobj, env); if (retval) { pr_debug("kobject: '%s' (%p): %s: uevent() returned " "%d\n", kobject_name(kobj), kobj, __FUNCTION__, retval); goto exit; } } if (action == KOBJ_ADD) kobj->state_add_uevent_sent = 1; else if (action == KOBJ_REMOVE) kobj->state_remove_uevent_sent = 1; /* we will send an event, so request a new sequence number */ spin_lock(&sequence_lock); seq = ++uevent_seqnum; spin_unlock(&sequence_lock); retval = add_uevent_var(env, "SEQNUM=%llu", (unsigned long long)seq); if (retval) goto exit; //在这里还会调用kobject->kset-> uevent_ops->uevent().让产生事件的kobject添加环境变量.最后将事件序列添加到环境变量中去. #if defined(CONFIG_NET) /* send netlink message */ if (uevent_sock) { struct sk_buff *skb; size_t len; /* 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, "%s@%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; netlink_broadcast(uevent_sock, skb, 0, 1, GFP_KERNEL); } } #endif /* call uevent_helper, usually only enabled during early boot */ if (uevent_helper[0]) { char *argv [3]; argv [0] = 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; call_usermodehelper(argv[0], argv, env->envp, UMH_WAIT_EXEC); } exit: kfree(devpath); kfree(env); return retval; } //忽略一段选择编译的代码.再后就是调用用户空间的hotplug了.添加最后两个环境变量.HOME和PATH.然后调用hotplug.以子系统名称为参数.