作者:李强,华清远见嵌入式培训中心讲师。
kobjec是linux在2.6中新引进的统一的设备管理模型,他的主要目的就是对linux的2.6系统所有的设备进行统一的管理,在以前的内核中并没有独立的数据结构让内核对整体的系统做配置和管理。尽管缺乏此类的信息,但是很多时候系统还是能正常工作,然后随着设备越来越多,系统越来越复杂,以及需要支持更多诸如电源管理等新的特征需要,新的内核版本明确提出了需要统一管理设备的要求:需要有一个对系统结构整体统一抽象的描述。
2.6内核中的kobjec提供了这样的抽象,在内核中使用了多种不同的任务,其中包括:
1、电源管理和系统关机
解释:在处理完所有与之相关的设备连接之前,是不能关闭电源的,所以通过这个结构,可以找到所有与之相连的设备,并且依次处理直至最后一个,然后关闭电源或者关机。
2、用户空间的通信
解释:sysfs与设备模型紧密相关,并且向外界展示了他所描述的结构,通过这个文件系统,我们可以了解当前系统的设备情况,并且可以修改在权限范围内的系统参数。
3、热插拔设备
解释:当前linux系统中会出现越来越多的热插拔设备,外围设备可以根据用户的需要进行安装与卸载,这种机制的实现就是通过这种模型来实现的。
4、设备类型
解释:对设备进行分类,在高于设备的角度上去管理设备。
5、生命周期
一般来说,kobject是隐藏在系统背后的,而不需要我们程序员做过多的关注,我们相信系统会帮助我们做好我们所需要的一切,但是有较少的时候,kobject也会从后台溜出来,然给我们对其进行操作。
比如有时候会用到kobject的引用计数,通过sysfs与用户空间通信等等之类的功能。
为了便于我们理解,我们采用从底层向上层讲述的方法来向用户展现下关于此模块的基本内容。
=============================== KOBJECT:
定义在<linux/kobject.h> struct kobject { const char * k_name; /* 指向对象名称的指针。*/ char name[KOBJ_NAME_LEN]; /* 设备名称,最长为20。*/ struct kref kref; /* 引用计数 */ struct list_head entry; /* 列表头 */ struct kobject * parent; /* 父对象 */ struct kset * kset; /* kobject集合 */ struct kobj_type * ktype /* kobject类型 */ struct dentry * dentry; /* sysfs的目录项 */ wait_queue_head_t poll; /* 等待队列*/ };
kojbect很少进行单独使用,他一般都是代表其他对象完成服务,也就是说一般都是把高级对象连接到设备模型上去。
把kobject嵌入到其他数据结构中去,是其最常用的方法之一。
看下我们在字符设备中使用过的cdev结构:
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; };
一般在设备模型中,我们都是知道其kobj来获取整个设备的指针,那就是通过宏:
比如我们知道kobj的指针值kj,需要获取cdev指针值:
struct cdev *device = container_of(kj, struct cdev, kobj);
在使用cdev的时候需要对其进行初始化,我们参考下:
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); cdev->kobj.ktype = &ktype_cdev_default; kobject_init(&cdev->kobj); // cdev->ops = fops; }
1、memset整个数据结构,包括其kobject。 2、初始化链表。 3、设置cdev->kobj.ktype = &ktype_cdev_default。 4、初始化kobject 5、设置fop数据结构。
几个主要函数介绍:
void kobject_init(struct kobject * kobj) { if (!kobj) /* 参数有效值判断 */ return; kref_init(&kobj->kref); /* 引用计数初始化,参考后面具体实现。 */ INIT_LIST_HEAD(&kobj->entry); /* 初始化列表 */ init_waitqueue_head(&kobj->poll); /* 初始化等待队列 */ kobj->kset = kset_get(kobj->kset); /* 设置其所属的集合kset,参考后面具体实现。*/ }
void kref_init(struct kref *kref) { atomic_set(&kref->refcount,1); /* 计数器加1 */ smp_mb(); /* 保证内存访问的串行话,防止并发。 */ }
/* 设置kobject的name。 * 程序从两个方面来实现: * 1,如果长度小于限制的值的时候,直接赋值就可以了,因为本身为指针,而非空间。 * 2,如果长度大于限制的值的时候,首先需要申请足够的内存空间,然后kobj->k_name指向所申请空间。 */ int kobject_set_name(struct kobject * kobj, const char * fmt, ...) { int error = 0; int limit = KOBJ_NAME_LEN; int need; va_list args; char * name;
/* * First, try the static array */ va_start(args,fmt); need = vsnprintf(kobj->name,limit,fmt,args); va_end(args); if (need < limit) name = kobj->name; else { /* * Need more space? Allocate it and try again */ limit = need + 1; name = kmalloc(limit,GFP_KERNEL); if (!name) { error = -ENOMEM; goto Done; } va_start(args,fmt); need = vsnprintf(name,limit,fmt,args); va_end(args);
/* Still? Give up. */ if (need >= limit) { kfree(name); error = -EFAULT; goto Done; } }
/* Free the old name, if necessary. */ if (kobj->k_name && kobj->k_name != kobj->name) kfree(kobj->k_name);
/* Now, set the new name */ kobj->k_name = name; Done: return error; }
对kobjec计数操作:
struct kobject * kobject_get(struct kobject * kobj) { if (kobj) kref_get(&kobj->kref); return kobj; }
void kref_get(struct kref *kref) { WARN_ON(!atomic_read(&kref->refcount)); /* 对CPU预处理。 */ atomic_inc(&kref->refcount); /* 计数器加一。 */ smp_mb__after_atomic_inc(); /* 原则操作之后串行操作。*/ }
void kobject_put(struct kobject * kobj) { if (kobj) kref_put(&kobj->kref, kobject_release); }
int kref_put(struct kref *kref, void (*release)(struct kref *kref)) { WARN_ON(release == NULL); WARN_ON(release == (void (*)(struct kref *))kfree);
if (atomic_dec_and_test(&kref->refcount)) { /* 减一之后做判断,如果为0,返回真,否则返回假。*/ release(kref); /* 计数器值为0,释放掉整个kobjct,参考kobject_release()具体操作。*/ return 1; } return 0; }
static void kobject_release(struct kref *kref) { kobject_cleanup(container_of(kref, struct kobject, kref)); } void kobject_cleanup(struct kobject * kobj) { struct kobj_type * t = get_ktype(kobj); struct kset * s = kobj->kset; struct kobject * parent = kobj->parent;
pr_debug("kobject %s: cleaning up\n",kobject_name(kobj)); if (kobj->k_name != kobj->name) /* 设备名和对象名不一致时,释放掉对象名。 */ kfree(kobj->k_name); kobj->k_name = NULL;
if (t && t->release) /* 根据在初始化的时候所设置的设备类型并与之相关的设备删除函数,删掉对于的kobj。 */ t->release(kobj); if (s) kset_put(s); /* kset集合中计数器减一。*/ kobject_put(parent); /* 父对象计数器减一。*/ }
===============================
KSET & KTYPE
通常内核使用kobject将内核中的所有对象连接起来组成一个分层的结构体系,从而与模型话的子系统相匹配,有两种机制用于连接:parent,set
kobject结构中有一个parent成员中,保存了另外一个kobject结构的指针,这个结构指针表示分层结构中上一层的节点。
比如一个kobject结构表示了一个USB的设备,他的parent指针可能指向了一个表示USB集线器的对象,而USB设备是插在此集线器上的。
对于parent来说,最重要的用途就是在sysfs分层结构中定位对象。
对于kset来说,其关心的是对象的集合。
对于ktype来说,其关心的是对象的类型。
kset是kobject的顶层容器,实际上每个kset内部包含了自己的kobject,并且可以用多种处理kobject的方法来处理kset。
kset总是在sysfs出现,但是kobjec则不必在sysfs中表示。
struct kset { struct kobj_type * ktype; // kset->kobj所属的类型。 struct list_head list; // kset所属的list。 spinlock_t list_lock; struct kobject kobj; // kset->kobj struct kset_uevent_ops * uevent_ops; };
struct kset_uevent_ops { int (*filter)(struct kset *kset, struct kobject *kobj); /* filter 函数让 kset 代码决定是否将事件传递给用户空间。如果 filter 返回 0,将不产生事件。 */ const char *(*name)(struct kset *kset, struct kobject *kobj); /* 当调用用户空间的热插拔程序时,相关子系统的名字将作为唯一的参数传递给它。name 函数负责返回合适的字符串传递给用户空间的热插拔程序。 */ int (*uevent)(struct kset *kset, struct kobject *kobj, char **envp, int num_envp, char *buffer, int buffer_size); };
热插拔脚本想得到的任何其他参数都通过环境变量传递。uevent 函数的作用是在调用热插拔脚本之前将参数添加到环境变量中。函数原型: int (*uevent)(struct kset *kset, struct kobject *kobj, /* 产生事件的目标对象 */ char **envp, /* 一个保存其他环境变量定义(通常为 NAME=value 的格式)的数组 */ int num_envp, /* 环境变量数组中包含的变量个数(数组大小)*/ char *buffer, int buffer_size /* 环境变量被编码后放入的缓冲区的指针和字节数(大小)*/ /* 若需要添加任何环境变量到 envp, 必须在最后的添加项后加一个 NULL 入口,使内核知道数组的结尾 */ ); /* 返回值正常应当是 0,若返回非零值将终止热插拔事件的产生 */
/* 添加一个kobj到系统中 */
int kobject_add(struct kobject * kobj) { return kobject_shadow_add(kobj, NULL); }
int kobject_shadow_add(struct kobject * kobj, struct dentry *shadow_parent) { int error = 0; struct kobject * parent;
if (!(kobj = kobject_get(kobj))) /* 增加计数 */ return -ENOENT; if (!kobj->k_name) /* 设置kojb的k_name。 */ kobj->k_name = kobj->name; if (!*kobj->k_name) { pr_debug("kobject attempted to be registered with no name!\n"); WARN_ON(1); kobject_put(kobj); return -EINVAL; } /* 把其父对象计数加一。 */ parent = kobject_get(kobj->parent);
pr_debug("kobject %s: registering. parent: %s, set: %s\n", kobject_name(kobj), parent ? kobject_name(parent) : "<NULL>", kobj->kset ? kobj->kset->kobj.name : "<NULL>" );
if (kobj->kset) { spin_lock(&kobj->kset->list_lock);
if (!parent) /* 把包含kobj对象的kset的本身所具有的kobj计数器加一。 */ parent = kobject_get(&kobj->kset->kobj);
/* 把kobj的entry添加到kset的list中去。*/ list_add_tail(&kobj->entry,&kobj->kset->list); spin_unlock(&kobj->kset->list_lock); /* 重置其parent。 */ kobj->parent = parent; } /* 产生在sysfs中相对应的目录。 */ error = create_dir(kobj, shadow_parent); if (error) { /* unlink does the kobject_put() for us */ unlink(kobj); kobject_put(parent);
/* be noisy on error issues */ if (error == -EEXIST) printk(KERN_ERR "kobject_add failed for %s with " "-EEXIST, don't try to register things with " "the same name in the same directory.\n", kobject_name(kobj)); else printk(KERN_ERR "kobject_add failed for %s (%d)\n", kobject_name(kobj), error); dump_stack(); }
return error; }
int kobject_register(struct kobject * kobj) { int error = -EINVAL; if (kobj) { /* 先对kobject进行初始化 */ kobject_init(kobj); /* 把其添加到系统中去 */ error = kobject_add(kobj); if (!error) kobject_uevent(kobj, KOBJ_ADD); } return error; }
void kobject_del(struct kobject * kobj) { if (!kobj) return; /* 删掉sysfs目录项 */ sysfs_remove_dir(kobj); unlink(kobj); }
/* 从kset列表中删除kobj对象。 */ static void unlink(struct kobject * kobj) { if (kobj->kset) { spin_lock(&kobj->kset->list_lock); /* 从列表中把其删掉 */ list_del_init(&kobj->entry); spin_unlock(&kobj->kset->list_lock); } kobject_put(kobj); }
void kset_init(struct kset * k) { kobject_init(&k->kobj); INIT_LIST_HEAD(&k->list); /* 初始化kset->list */ spin_lock_init(&k->list_lock); }
int kset_add(struct kset * k) { /* 添加kset->kobj到系统中去 */ return kobject_add(&k->kobj); }
nt kset_register(struct kset * k) { if (!k) return -EINVAL; kset_init(k); return kset_add(k); }
static inline struct kset * kset_get(struct kset * k) { return k ? to_kset(kobject_get(&k->kobj)) : NULL; }
static inline void kset_put(struct kset * k) { kobject_put(&k->kobj); }
struct kobj_type { void (*release)(struct kobject *); struct sysfs_ops * sysfs_ops; struct attribute ** default_attrs; };
struct sysfs_ops { ssize_t (*show)(struct kobject *, struct attribute *,char *); ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t); }; struct attribute { const char * name; struct module * owner; mode_t mode; };
===============================
SUBSYSTEM 子系统是对内核中一些高级部分的表述,子系统通常显示在sysfs分层结构的顶层。 一个驱动编写者几乎不会创建一个子系统,其经常创建的是一个新类,而非子系统。
比较新的内核中,几乎看不到subsystem的结构定义,取而代之的是kset。
void subsystem_init(struct kset *s) { kset_init(s); }
void subsystem_init(struct kset *s) { kset_init(s); } void subsystem_unregister(struct kset *s) { kset_unregister(s); }
/* 用此宏定义来创建一个subsys */ #define decl_subsys(_name,_type,_uevent_ops) \ struct kset _name##_subsys = { \ .kobj = { .name = __stringify(_name) }, \ .ktype = _type, \ .uevent_ops =_uevent_ops, \ }
static inline struct kset *subsys_get(struct kset *s) { if (s) return kset_get(s); return NULL; }
static inline void subsys_put(struct kset *s) { kset_put(s); }
|