在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");