在Linux2.6以后,提出了新的设备模型,新设备模型的核心概念是内核对象与内核集合,并在此基础上,采用面向对象的思想提出了许多新的数据类型,如设备、总线等,以对各类外围设备进行有效的管理。linux
1、引用计数:数组
Linux内核中每个对象都包含有一个引用计数器strut kref,在linux/kref.h文件中:函数
struct kref { atomic_t refcount; };
引用计数器使用原子操做来完成引用计数的加减操做,基本操做以下:atom
void kref_init(struct kref *kref); // 初始化引用计数的值为1 void kref_get(struct kref *kref); // 引用计数加1 int kref_put(struct kref *kref, void (*release) (struct kref *kref)); // 引用计数减1,若是引用计数的值降为0,则调用release方法释放对象
在实际的应用中,支持引用计数的数据类型,会嵌套一个struct kref类型的成员,并提供一个释放函数。在后续的分析内核对象的时候,能够很清楚的看到引用计数是怎么使用的。spa
2、内核对象kobject:
debug
内核对象是设备模型中最基本的数据类型,每个内核对象都对应sysfs文件系统中的一个目录,内核对象的父子关系对应着目录的层次关系。设计
内核对象数据类型在linux/kobject.h头文件声明:指针
struct kobject { const char *name; // 对象名字,即咱们在sysfs文件系统下显示的目录名 struct list_head entry; // 用于连接到集合链表中 struct kobject *parent; //父kobject对象 struct kset *kset; // 对象所属的集合 struct kobj_type *ktype; //对象的属性与访问方法 struct sysfs_dirent *sd; // 对象对应的sysfs目录项 struct kref kref; // 对象的引用计数 unsigned int state_initialized:1; unsigned int state_in_sysfs:1; unsigned int state_add_uevent_sent:1; unsigned int state_remove_uevent_sent:1; };
当咱们要使用一个内核对象的时候,首先是初始化这个内核对象,而后将其添加到内核中。经常使用操做以下:code
// 初始化内核对象 void kobject_init(struct kobject *kobj, struct kobj_type *ktype); // 设置内核对象的名字 int kobject_set_name(struct kobject * kobj, const char * fmt, ...); // 添加到内核中 int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...); // 为了简化上述的3个操做,能够直接使用此接口: int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype, struct kobject *parent, const char *fmt, ...); // 删除kobj内核对象:此操做内部会自动将引用计数减1,若是降为0,调用kobject_put ()方法释放内核对象 void kobject_del(struct kobject *kobj);
此外,还提供了动态建立kobject的接口:对象
struct kobject *kobject_create(void); struct kobject *kobject_create_and_add(const char *name, struct kobject *parent);
内核对象的引用计数操做:
1) 增长引用计数:
struct kobject *kobject_get(struct kobject *kobj) { if (kobj) kref_get(&kobj->kref); //直接调用引用计数的get方法 return kobj; }
2) 减小引用计数:当引用计数减为0时,会自动调用kobject_release()方法
void kobject_put(struct kobject *kobj) { if (kobj) { if (!kobj->state_initialized) WARN(1, KERN_WARNING "kobject: '%s' (%p): is not " "initialized, yet kobject_put() is being " "called.\n", kobject_name(kobj), kobj); kref_put(&kobj->kref, kobject_release); // 直接调用引用计数的put方法 } }
下面简单的分析一下内核对象的注册与释放函数的内部实现:
1. 注册内核对象kobject_add(): 在注册以前,必须先调用kobject_init()函数进行初始化
int kobject_add(struct kobject *kobj, struct kobject *parent, const char *fmt, ...) { va_list args; int retval; if (!kobj) return -EINVAL; if (!kobj->state_initialized) { //内核对象未初始化 printk(KERN_ERR "kobject '%s' (%p): tried to add an " "uninitialized object, something is seriously wrong.\n", kobject_name(kobj), kobj); dump_stack(); return -EINVAL; } va_start(args, fmt); retval = kobject_add_varg(kobj, parent, fmt, args); va_end(args); return retval; } static int kobject_add_varg(struct kobject *kobj, struct kobject *parent, const char *fmt, va_list vargs) { int retval; // 先设置好内核对象的名字 retval = kobject_set_name_vargs(kobj, fmt, vargs); if (retval) { printk(KERN_ERR "kobject: can not set name properly!\n"); return retval; } kobj->parent = parent; return kobject_add_internal(kobj); }
看来内核对象注册的真正操做是在kobject_add_internal()函数内部完成的:
static int kobject_add_internal(struct kobject *kobj) { int error = 0; struct kobject *parent; if (!kobj) return -ENOENT; if (!kobj->name || !kobj->name[0]) { // 必须设置好内核对象的名字,不然会注册失败 WARN(1, "kobject: (%p): attempted to be registered with empty " "name!\n", kobj); return -EINVAL; } parent = kobject_get(kobj->parent); //增长父对象的引用计数 /* join kset if set, use it as parent if we do not already have one */ // 若是没有设置其所属的父对象,则将其所属的内核集合kset做为其父对象 if (kobj->kset) { if (!parent) parent = kobject_get(&kobj->kset->kobj); kobj_kset_join(kobj); kobj->parent = parent; } pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n", kobject_name(kobj), kobj, __func__, parent ? kobject_name(parent) : "<NULL>", kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>"); error = create_dir(kobj); //在sysfs文件系统中建立kobj对应的目录 if (error) { 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", __func__, kobject_name(kobj)); else printk(KERN_ERR "%s failed for %s (%d)\n", __func__, kobject_name(kobj), error); dump_stack(); } else kobj->state_in_sysfs = 1; // 表示成功在sysfs文件系统中建立对应目录 return error; }
大体的代码逻辑都有注释,可见每个kobject类型的内核对象,都会与sysfs文件系统中的sysfs_dirent对象对应起来!这个关系会在后续的分析sysfs文件系统中看到,这里赞不分析。
2. 释放内核对象kobject_put():
在前面已经看到了kobject_put()方法会在内核对象的引用计数为0时,调用kobejct_release()方法进行释放:
static void kobject_release(struct kref *kref) { kobject_cleanup(container_of(kref, struct kobject, kref)); } /* * kobject_cleanup - free kobject resources. * @kobj: object to cleanup */ static void kobject_cleanup(struct kobject *kobj) { struct kobj_type *t = get_ktype(kobj); const char *name = kobj->name; pr_debug("kobject: '%s' (%p): %s\n", kobject_name(kobj), kobj, __func__); // 若是咱们没有为内核对象设置一个release的方法,则会打印下面的这个信息! if (t && !t->release) pr_debug("kobject: '%s' (%p): does not have a release() " "function, it is broken and must be fixed.\n", kobject_name(kobj), kobj); /* send "remove" if the caller did not do it but sent "add" */ if (kobj->state_add_uevent_sent && !kobj->state_remove_uevent_sent) { pr_debug("kobject: '%s' (%p): auto cleanup 'remove' event\n", kobject_name(kobj), kobj); kobject_uevent(kobj, KOBJ_REMOVE); // 发生KOBJ_REMOVE类型的用户态事件 } /* remove from sysfs if the caller did not do it */ if (kobj->state_in_sysfs) { pr_debug("kobject: '%s' (%p): auto cleanup kobject_del\n", kobject_name(kobj), kobj); kobject_del(kobj); //删除内核对象对应的sysfs_dirent目录,减小父对象的引用计数等操做 } if (t && t->release) { pr_debug("kobject: '%s' (%p): calling ktype release\n", kobject_name(kobj), kobj); t->release(kobj); // 回调咱们自定义的释放函数 } /* free name if we allocated it */ if (name) { pr_debug("kobject: '%s': free name\n", name); kfree(name); } }
在这里咱们看到内核对象的释放函数,是在kobj_type结构体中定义的:
struct kobj_type { void (*release)(struct kobject *kobj); //内核对象的释放回调函数 struct sysfs_ops *sysfs_ops; // 属性访问方法 struct attribute **default_attrs; //属性数组,以NULL结束 };
每个属性,采用strut attribute结构体表示:
struct attribute { const char *name; //属性名 struct module *owner;//属性全部者,已再也不使用 mode_t mode;//属性权限 };
每个属性对应于sysfs中的此内核对象目录下的一个文件,文件名记为name,文件权限即为mode。属性既然表现为文件的形式,那么就必定能够读写。当应用程序读写属性文件时,内核将回调由成员sysfs_ops指向的操做:
struct sysfs_ops { ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buf); // read ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buf, size_t size); // write };
当应用程序读取属性文件时,会调用show指向的回调函数。当应用程序写属性文件时,会调用store执行的回调函数。与文件的读read相比,show操做只传入了一个缓冲区地址,并无传递缓冲区的长度。实际上,buf执行的缓冲区是由内核自动分配的,长度是一页内存,通常是4KB。上述的show与store操做的buf,并非用户态内存指针,因此能够直接访问。
在实际的操做中,咱们可能没法在初始化的时候就把全部的属性添加进去,有可能在运行过程当中动态建立属性文件,所以内核提供了动态添加和删除属性接口:
int sysfs_create_file(struct kobject * kobj, const struct attribute * attr); void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr);
使用上述接口,咱们能够在内核代码中动态的为内核对象增长或去除属性。
仔细看struct kobj_type类型,会发现sysfs_ops只提供了show与store操做,即内核对象的全部属性的访问,都会调用到sysfs_ops提供的show与store操做。Linux内核为了可让咱们在定义属性的时候更加的灵活,由为咱们提供了以下的数据类型:
struct kobj_attribute { struct attribute attr; ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr, char *buf); ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count); };
这样子咱们就能够指定每个属性对应的show和store方法。那内核是如何实现的呢?核心就在于container_of宏的灵活使用:
/* default kobject attribute operations */ static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kattr; ssize_t ret = -EIO; // 从attr地址获得其所在的kobj_attribute属性对象的指针 kattr = container_of(attr, struct kobj_attribute, attr); // 调用kobj_attribute属性对象的具体show方法 if (kattr->show) ret = kattr->show(kobj, kattr, buf); return ret; } static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kattr; ssize_t ret = -EIO; // 从attr地址获得其所在的kobj_attribute属性对象的指针 kattr = container_of(attr, struct kobj_attribute, attr); // 调用kobj_attribute属性对象的具体store方法 if (kattr->store) ret = kattr->store(kobj, kattr, buf, count); return ret; } struct sysfs_ops kobj_sysfs_ops = { .show = kobj_attr_show, .store = kobj_attr_store, };
从上述的源码能够看出,咱们能够在定义属性时,把struct attribute嵌套到自定义属性类型中,而后编写一个统一的show和store操做,在统一的show和store操做内部再回调属性的真正show与store方法。固然咱们也是能够直接把struct kobj_attribute嵌套到咱们自定义的属性类型中的。
内核为了方便咱们初始化struct kobj_attribute对象,提供了以下的宏:
#define __ATTR(_name,_mode,_show,_store) { \ .attr = {.name = __stringify(_name), .mode = _mode }, \ .show = _show, \ .store = _store, \ }
这个宏在device, device_driver和bus中都有用到,后面分析总线、设备、驱动的时候会看到其使用。
3、内核集合kset:
内核集合是基于内核对象设计的,它能够收纳内核对象,将收纳的内核对象添加到链表中保存,同时管理其收纳的内核对象所发送的用户态事件。
struct kset { struct list_head list; //用于保存收纳的内核对象 spinlock_t list_lock; //用于保证原子操做上述链表 struct kobject kobj; //内核集合也表示一个内核对象 struct kset_uevent_ops *uevent_ops;//管理用户态事件的发送 };
当咱们要使用一个内核集合对象时,首先是初始化内核集合对象,而后将其注册到内核中,经常使用接口以下:
// 初始化内核集合对象 void kset_init(struct kset *k); // 注册内核集合对象 int kset_register(struct kset *k); //在此接口内部会调用kset_init(), // 动态建立并注册内核集合对象, struct kset *kset_create_and_add(const char *name, struct kset_uevent_ops *uevent_ops, struct kobject *parent_kobj); // 注销已注册的内核集合对象 void kset_unregister(struct kset *k); // 内核集合引用计数加1 static inline struct kset *kset_get(struct kset *k) // 内核集合引用计数减1 static inline void kset_put(struct kset *k); // 经过name查找内核集合中的内核对象 struct kobject *kset_find_obj(struct kset *kset, const char *name);
这里要注意的是,若是咱们使用kset_register()方法注册内核集合,须要在注册前,初始化好uevent_ops和kobj对象的name成员,由于kset_init()方法内部并无初始化此成员。
内核集合的注册实现源码并不复杂,只要理解了kset自己也是一个kobject,就很容易其过程了。
4、一个简单的例子:
在/sys目录下建立一个persons目录,包含有3个子目录 ,结构以下所示,name可读写,sex是只读的。
persons | person-0 | sex | name | person-1 | sex | name | person-2 | sex | name
example:
#include <linux/module.h> #include <linux/kobject.h> #define PERSON_NUMS 3 #define ENTER() printk(KERN_DEBUG "%s() Enter", __func__) #define EXIT() printk(KERN_DEBUG "%s() Exit", __func__) #define ERR(fmt, args...) printk(KERN_ERR "%s()-%d: " fmt "\n", __func__, __LINE__, ##args) #define DBG(fmt, args...) printk(KERN_DEBUG "%s()-%d: " fmt "\n", __func__, __LINE__, ##args) struct person { struct kobject kobj; char name[16]; char sex; }; // call when we kobject_put() to let kref to be 0 static void person_release(struct kobject *kobj) { struct person *per = container_of(kobj, struct person, kobj); ENTER(); kfree(per); EXIT(); } // generic attr show function static ssize_t attr_show(struct kobject *kobj, struct attribute *attr, char *buf) { struct kobj_attribute *kattr; ssize_t ret = -EIO; kattr = container_of(attr, struct kobj_attribute, attr); ENTER(); if (kattr->show) { ret = kattr->show(kobj, kattr, buf); } EXIT(); return ret; } // generic attr store function static ssize_t attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) { struct kobj_attribute *kattr; ssize_t ret = -EIO; kattr = container_of(attr, struct kobj_attribute, attr); ENTER(); if (kattr->store) { ret = kattr->store(kobj, kattr, buf, count); } EXIT(); return ret; } static struct sysfs_ops person_attr_ops = { .show = attr_show, .store = attr_store, }; /* show and store function for spcific attributes, like sex and name. */ static ssize_t sex_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { ENTER(); struct person *per = container_of(kobj, struct person, kobj); ssize_t ret = sprintf(buf, "%c\n", per->sex); EXIT(); return ret; } static ssize_t sex_store(struct kobject *kobj, struct kobj_attribute *kattr, const char *buf, size_t len) { ENTER(); DBG("no prividge"); return -EACCES; // it means no prividge. } static ssize_t name_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf) { ENTER(); struct person *per = container_of(kobj, struct person, kobj); ssize_t len = strlen(per->name); memcpy(buf, per->name, len); EXIT(); return len; } static ssize_t name_store(struct kobject *kobj, struct kobj_attribute *kattr, const char *buf, size_t len) { ENTER(); DBG("buf: %s, len:%u", buf, len); struct person *per = container_of(kobj, struct person, kobj); snprintf(per->name, sizeof(per->name), "%s", buf); EXIT(); return len; } static struct kobj_attribute attr_sex = \ __ATTR(sex, S_IRUGO, sex_show, sex_store); static struct kobj_attribute attr_name = \ __ATTR(name, S_IRUGO | S_IWUGO, name_show, name_store); static struct attribute *person_default_attrs[] = { &attr_sex.attr, &attr_name.attr, NULL, }; static struct kobj_type person_kobj_type = { .release = person_release, .sysfs_ops = &person_attr_ops, .default_attrs = person_default_attrs, }; static struct kset *persons = NULL; /* persons | person-0 | sex | name | person-1 | sex | name | person-2 | sex | name */ static __init int sysfs_demo_init(void) { struct person *per; int i; int err; struct list_head *cur, *next; struct kobject *p_cur; ENTER(); persons = kset_create_and_add("persons", NULL, NULL); if (!persons) { ERR("kset_create_and_add fail"); return -ENOMEM; } for (i = 0; i < PERSON_NUMS; ++i) { per = kzalloc(sizeof(struct person), GFP_KERNEL); if (!per) { ERR("kzalloc fail"); goto _fail; } per->kobj.kset = persons; per->sex = ((i % 2) == 0) ? 'M' : 'F'; snprintf(per->name, sizeof(per->name), "person-%d", i); DBG("name: %s", per->name); err = kobject_init_and_add(&per->kobj, &person_kobj_type, NULL, per->name); if (err) { ERR("kobject_init_and_add fail"); goto _fail; } DBG("kobject_init_and_add success"); kobject_uevent(&per->kobj, KOBJ_ADD); } EXIT(); return 0; _fail: if (persons) { DBG("in fail"); list_for_each_safe(cur, next, &persons->list) { p_cur = container_of(cur, struct kobject, entry); kobject_put(p_cur); } kset_unregister(persons); persons = NULL; } return err; } static __exit void sysfs_demo_exit(void) { struct list_head *cur, *next; struct kobject *p_cur; if (persons) { ENTER(); list_for_each_safe(cur, next, &persons->list) { p_cur = container_of(cur, struct kobject, entry); DBG("kobject_put begin"); kobject_put(p_cur); } kset_unregister(persons); persons = NULL; } EXIT(); } module_init(sysfs_demo_init); module_exit(sysfs_demo_exit); MODULE_LICENSE("GPL");