Linux設備模型之kobject

阿輝原創,轉載請註明出處linux

參考文檔:LDD3-ch1四、內核文檔Documentation/kobject.txt,本文中使用到的代碼均摘自Linux-3.4.75程序員

--------------------------------------------------------------------------------------------------------------------數據結構

簡要介紹

  隨着Linux內核的發展壯大,其支持的設備也愈來愈多,但一直沒有一個很好的方法來管理慢慢增多的設備驅動。爲了可以在內核中提供統一的機制來對設備進行分類,同時在更高的功能層面上描述這些設備,並使得這些設備對用戶空間可見。從2.6開始,Linux內核引入一個新的設備模型來對系統結構作通常性的抽象描述,能夠用於支持不一樣的任務需求,並提供了用戶空間訪問的接口。架構

  對於驅動程序的做者來講,通常是不須要深刻了解Linux設備模型的細節,而只須要理解設備模型的基本架構,懂得怎麼使用設備模型提供的接口便可。由於Linux設備模型的代碼已經處理了大部分模型相關的內容,而且目前看來,處理的還不錯。可是,總體來講,理解Linux設備模型的內在實現原理對於學習內核驅動程序或者本身實現驅動程序大有好處,它可讓你對總體的把握,從一個宏觀的角度來看問題。函數

  接下來我會經過一系列的文章來介紹Linux設備模型,以從下到上的方式,從設備模型的底層數據結構講起,並會逐步介紹如何經過底層的數據結構搭建Linux的設備模型,本文主要介紹Linux設備模型中的基礎數據結構kobject學習

kobject簡介

  kobject是Linux設備模型的基礎結構,其地位相似於面向對象中的基類,經過派生出其餘的類來豐富Linux設備模型的結構,如device、kset等。具體方法就是將kobject結構嵌入到上層的數據結構中,這樣若是使用了該上層結構,只要訪問kboject成員就能夠得到kboject結構。一樣,若知道kobject結構的指針,能夠經過container_of宏來找回包含該kobject的結構。ui

  kobject結構定義於include/linux/kobject.h,以下:this

 1 struct kobject {
 2                 const char                           *name;  
 3                 struct list_head entry;
 4                 struct kobject                    *parent;
 5                 struct kset                           *kset;
 6                 struct kobj_type              *ktype;
 7                 struct sysfs_dirent          *sd;
 8                 struct kref                           kref;
 9                 unsigned int state_initialized:1;
10                 unsigned int state_in_sysfs:1;
11                 unsigned int state_add_uevent_sent:1;
12                 unsigned int state_remove_uevent_sent:1;
13                 unsigned int uevent_suppress:1;
14 };

name: kobject的名稱,它將以一個目錄的形式出如今sysfs文件系統中atom

entry:list_head入口,用於將該kobject連接到所屬kset的鏈表spa

parent:kobject結構的父節點

kset:kobject所屬的kset

ktype:kobject相關的操做函數和屬性。

sd:kobject對應的sysfs目錄

kref:kobject的引用計數,本質上是atomic_t變量

state_initialize:爲1表明kobject已經初始化過了

state_in_sysfs:kobject是否已經在sysfs文件系統創建入口

 

以下是struct device結構嵌入kobject結構的簡單例子

1 struct device {
2 3                 struct kobject kobj;
4                 const char                           *init_name; /* initial name of the device */
5                 const struct device_type *type;
6 7                 void       (*release)(struct device *dev);
8 };

  kobject結構在sysfs中的入口是一個目錄,所以添加一個kobject的動做也會致使在sysfs中新建一個對應kobject的目錄,目錄名是kobject.name。該入口目錄位於kobject的parent指針的目錄當中,若parent指針爲空,則將parent設置爲該kobject中的kset對應的kobject(&kobj->kset->kobj)。若是parent指針和kset都是NULL,此時sysfs的入口目錄將會在頂層目錄下(/sys)被建立,不過不建議這麼作。詳細的建立目錄過程能夠看後面介紹的kobject_init_and_add函數的介紹。

   Note:sysfs的簡要介紹請查看 內核文檔翻譯中的sysfs - The filesystem for exporting kernel objects 這篇文章

kobject初始化

  按照LDD3-ch14的建議,須要對kobject作清零初始化,而後再使用,不然可能會出現一些奇怪的錯誤,一般使用memset實現。

  kobject經常使用的操做函數以下:

1 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
2 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, ...)
4 struct kobject *kobject_create(void)
5 struct kobject *kobject_create_and_add(const char *name, struct kobject *parent)
6 int kobject_set_name(struct kobject *kobj, const char *fmt, ...)//設置kobject名稱

  能夠經過kobject_init初始化kobject,而後再經過kobject_add將這個kobject添加到kset中。或者也能夠直接經過kobject_init_and_add 函數完成初始化和添加的任務,查看Linux源碼,它只是兩個函數的組合而已,目前我看過的驅動源碼中,大部分的實現都是經過kobject_init_and_add函數來實現的。

  kobject_create_and_add和前兩種實現的差異只是多了一步分配kobject的過程,其餘的內容都同樣,典型的應用能夠看linux電源管理源碼中power_kobj的生成(kernel/power/main.c)。

  咱們從kobject_init_and_add函數開始分析kobject的初始化過程,這個函數在lib/kobject.c中定義,以下:

 1 /**
 2  * kobject_init_and_add - initialize a kobject structure and add it to the kobject hierarchy
 3  * @kobj: pointer to the kobject to initialize
 4  * @ktype: pointer to the ktype for this kobject.
 5  * @parent: pointer to the parent of this kobject.
 6  * @fmt: the name of the kobject.
 7  *
 8  * This function combines the call to kobject_init() and
 9  * kobject_add().  The same type of error handling after a call to
10  * kobject_add() and kobject lifetime rules are the same here.
11  */
12 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
13              struct kobject *parent, const char *fmt, ...)
14 {
15     va_list args;
16     int retval;
17 
18     kobject_init(kobj, ktype);
19 
20     va_start(args, fmt);
21     retval = kobject_add_varg(kobj, parent, fmt, args);
22     va_end(args);
23     return retval;
24 } 

  能夠看出來,該函數分爲兩部分:首先經過kobject_init初始化kobject結構,而後利用kobject_add_varg將kobject添加到設備模型的體系結構中去。咱們先來看看kobject_init函數

 1 /**
 2  * kobject_init - initialize a kobject structure
 3  * @kobj: pointer to the kobject to initialize
 4  * @ktype: pointer to the ktype for this kobject.
 5  *
 6  * This function will properly initialize a kobject such that it can then
 7  * be passed to the kobject_add() call.
 8  *
 9  * After this function is called, the kobject MUST be cleaned up by a call
10  * to kobject_put(), not by a call to kfree directly to ensure that all of
11  * the memory is cleaned up properly.
12  */
13 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
14 {
15     char *err_str;
16     if (!kobj) {
17         err_str = "invalid kobject pointer!";
18         goto error;
19     }
20     if (!ktype) {
21         err_str = "must have a ktype to be initialized properly!\n";
22         goto error;
23     }
24     if (kobj->state_initialized) {
25         /* do not error out as sometimes we can recover */
26         printk(KERN_ERR "kobject (%p): tried to init an initialized "
27                "object, something is seriously wrong.\n", kobj);
28         dump_stack();
29     }
30     kobject_init_internal(kobj);
31     kobj->ktype = ktype;
32     return;
33 
34 error:
35     printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
36     dump_stack();
37 }

  主要是調用kobject_init_internal並設置kobj->ktype,固然要保證傳遞給kobject_init的kob、ktype參數不爲空

  Kobject_inint_internal函數的定義一樣在lib/kobject.c,以下:

 1 static void kobject_init_internal(struct kobject *kobj)
 2 {
 3     if (!kobj)
 4         return;
 5     kref_init(&kobj->kref);
 6     INIT_LIST_HEAD(&kobj->entry);
 7     kobj->state_in_sysfs = 0;
 8     kobj->state_add_uevent_sent = 0;
 9     kobj->state_remove_uevent_sent = 0;
10     kobj->state_initialized = 1;
11 }

  這裏是對kobject成員變量的初始化,初始爲默認的狀態;kref_init函數只是經過atomic_set將kobj->kref->refcount設置爲1

   kobject_add_varg函數一樣定義於lib/kobject.c文件中:

 1 static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,
 2                 const char *fmt, va_list vargs)
 3 {
 4     int retval;
 5 
 6     retval = kobject_set_name_vargs(kobj, fmt, vargs);
 7     if (retval) {
 8         printk(KERN_ERR "kobject: can not set name properly!\n");
 9         return retval;
10     }
11     kobj->parent = parent;
12     return kobject_add_internal(kobj);
13 }

該函數調用kobject_set_name_vargs解析可變參數並設置kobject.name的值,而後設置kobj->parent,最後經過kobject_add_internal添加kobject

 kobject_add_internal函數定義於lib/kobject.c,主要做用是設置kobject的父節點、kset並建立kobject在sysfs中的目錄

 1 static int kobject_add_internal(struct kobject *kobj)
 2 {
 3     int error = 0;
 4     struct kobject *parent;
 5 
 6     if (!kobj)
 7         return -ENOENT;
 8 
 9     if (!kobj->name || !kobj->name[0]) {
10         WARN(1, "kobject: (%p): attempted to be registered with empty "
11              "name!\n", kobj);
12         return -EINVAL;
13     }
14 
15     parent = kobject_get(kobj->parent);
16 
17     /* join kset if set, use it as parent if we do not already have one */
18     if (kobj->kset) {
19         if (!parent)
20             parent = kobject_get(&kobj->kset->kobj);
21         kobj_kset_join(kobj);
22         kobj->parent = parent;
23     }
24 
25     pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
26          kobject_name(kobj), kobj, __func__,
27          parent ? kobject_name(parent) : "<NULL>",
28          kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
29 
30     error = create_dir(kobj);
31     if (error) {
32         kobj_kset_leave(kobj);
33         kobject_put(parent);
34         kobj->parent = NULL;
35 
36         /* be noisy on error issues */
37         if (error == -EEXIST)
38             WARN(1, "%s failed for %s with "
39                  "-EEXIST, don't try to register things with "
40                  "the same name in the same directory.\n",
41                  __func__, kobject_name(kobj));
42         else
43             WARN(1, "%s failed for %s (error: %d parent: %s)\n",
44                  __func__, kobject_name(kobj), error,
45                  parent ? kobject_name(parent) : "'none'");
46     } else
47         kobj->state_in_sysfs = 1;
48 
49     return error;
50 }

  這個函數首先判斷kobject.name是否有成功設置,接下來的代碼,判斷kobject是否設置了父節點,若沒有父節點而且若kobj->kset存在,就將kobj->kset設置爲當前kobject的父節點,並增長引用計數。最後,經過create_dir(kobj)建立在sysfs中的節點。若當前kobject的kobj->parent不存在而且kobj->kset爲空,則會在/sys目錄下爲該kobject建立一個子目錄。

  create_dir函數會調用sysfs_create_dir爲kobject建立sysfs文件系統中的目錄。該函數定義於kobject.c,kernel中有幾個該函數的同名函數,參數類型不一樣,莫要搞混了

 1 static int create_dir(struct kobject *kobj)
 2 {
 3     int error = 0;
 4     if (kobject_name(kobj)) {
 5         error = sysfs_create_dir(kobj);
 6         if (!error) {
 7             error = populate_dir(kobj);
 8             if (error)
 9                 sysfs_remove_dir(kobj);
10         }
11     }
12     return error;
13 }

kobject引用計數

  Kobject的一個重要屬性在於它的引用計數,只要kobject的引用計數不爲0,kobject對象就必須存在。Kobject中保存引用計數的變量時kref,它本質上是atomic_t變量。驅動模型底層對引用計數控制的函數有

1 struct kobject *kobject_get(struct kobject *kobj)
2 void kobject_put(struct kobject *kobj)

  kobject_get用於增長kobject的引用計數,而且返回kobject指針,調用該函數的話必須檢查返回值,不然可能會引用到已被銷燬的kobject,形成競爭的發生。

 1 /**
 2  * kobject_get - increment refcount for object.
 3  * @kobj: object.
 4  */
 5 struct kobject *kobject_get(struct kobject *kobj)
 6 {
 7     if (kobj)
 8         kref_get(&kobj->kref);
 9     return kobj;
10 }

Kobject_put用於減小kobject的引用計數

 1 /**
 2  * kobject_put - decrement refcount for object.
 3  * @kobj: object.
 4  *
 5  * Decrement the refcount, and if 0, call kobject_cleanup().
 6  */
 7 void kobject_put(struct kobject *kobj)
 8 {
 9     if (kobj) {
10         if (!kobj->state_initialized)
11             WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
12                    "initialized, yet kobject_put() is being "
13                    "called.\n", kobject_name(kobj), kobj);
14         kref_put(&kobj->kref, kobject_release);
15     }
16 }

當引用計數爲0的時候,須要調用release函數將kobject釋放掉,對於每個kobject,都必須有一個release函數來釋放kobject結構。Linux設備模型中默認的release函數不在kobject對象中,而是在kobj_type這個結構中,kobj_type結構定義以下:

1 struct kobj_type {
2     void (*release)(struct kobject *kobj);
3     const struct sysfs_ops *sysfs_ops;
4     struct attribute **default_attrs;
5     const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
6     const void *(*namespace)(struct kobject *kobj);
7 };

此時release函數的調用路徑以下,就不一一展開介紹了

kobject_put->kref_put->kref_sub->kobject_release->kobject_cleanup->(kobj_type->release)

kobj_type

  這個結構在以前介紹kobject時有介紹到它的release成員,接下來咱們對它的其餘成員作一個詳細介紹

sysfs_ops:是struct sysfs_ops類型的常指針,用於實現kobject中屬性(struct attribute)的操做,定義於include/linux/sysfs.h文件中,以下:
1 struct sysfs_ops {
2     ssize_t    (*show)(struct kobject *, struct attribute *,char *);
3     ssize_t    (*store)(struct kobject *,struct attribute *,const char *, size_t);
4     const void *(*namespace)(struct kobject *, const struct attribute *);
5 };

  當用戶空間讀取屬性的時候,便會調用到屬性的show函數;而store函數則用於從用戶空間獲取新的屬性值並保持,注意應用程序應該要有該屬性的些權限才能夠調用到store函數,而且最好在store函數中檢查下用戶空間寫入值的合法性。

   default_attrs:保存了kobject默認的屬性列表,屬性列表中的每一個屬性都會在kobject目錄下生成一個對應的屬性文件,屬性結構在lib/kobject.h中定義,以下:

1 struct attribute {
2     const char        *name;
3     umode_t            mode;
4 #ifdef CONFIG_DEBUG_LOCK_ALLOC
5     struct lock_class_key    *key;
6     struct lock_class_key    skey;
7 #endif
8 };

  name:屬性名,其對應屬性文件的名字,

  mode:訪問模式,用於指定用戶空間訪問屬性文件的權限

  關於用戶空間讀寫屬性文件是怎麼調用到sysfs_ops->show和sysfs_ops->store的原理,主要涉及到的是sysfs內容,目前的話,只要記住讀寫屬性文件會調用到sysfs_ops->show和sysfs_ops->store便可。

  除了kobject的默認屬性列表,程序員還能夠感受須要添加或者刪除kobject的屬性。能夠經過以下的函數添加kobject屬性:

 1 /**
 2  *    sysfs_create_file - create an attribute file for an object.
 3  *    @kobj:    object we're creating for. 
 4  *    @attr:    attribute descriptor.
 5  */
 6 int sysfs_create_file(struct kobject * kobj, const struct attribute * attr)
 7 {
 8     BUG_ON(!kobj || !kobj->sd || !attr);
 9 
10     return sysfs_add_file(kobj->sd, attr, SYSFS_KOBJ_ATTR);
11 }

只須要填充attribute結構並傳遞給sysfs_create_file便可,若是該函數返回0,說明建立屬性成功,不然將返回對應的錯誤碼。

 

能夠經過以下的函數來刪除屬性:

 1 /**
 2  *    sysfs_remove_file - remove an object attribute.
 3  *    @kobj:    object we're acting for.
 4  *    @attr:    attribute descriptor.
 5  *
 6  *    Hash the attribute name and kill the victim.
 7  */
 8 void sysfs_remove_file(struct kobject * kobj, const struct attribute * attr)
 9 {
10     const void *ns;
11 
12     if (sysfs_attr_ns(kobj, attr, &ns))
13         return;
14 
15     sysfs_hash_and_remove(kobj->sd, ns, attr->name);
16 }

  這個函數調用以後,屬性文件將不會再出如今kobjectsysfs入口中錄中

kobject實例

  本想本身寫一個kobject使用的例子來和你們分享,不想前兩天翻譯內核、kobject.txt文檔發現內核中有現成的kobject例子,就在samples/kobject/目錄下的kobject-sample.c文件中。

  這個例子的做用是在/sys/kernel目錄下新建一個kobject-example的目錄,並在該目錄下生成baz、bar、foo這三個屬性文件。詳細代碼能夠在內核源碼目錄下找到。

  使用該例子須要在kernel的配置中選擇CONFIG_SAMPLE_KOBJECT,並把它編譯成模塊,詳細步驟以下:

make menuconfig

  kernel hacking->Sample kernel code->build kernel object

  選擇編譯成模塊便可

 

注意build module的kernel版本要和目前使用的kernel版本一致,不然可能在insmod時會出錯,我住Ubuntu中試的時候就出現過由於kernel版本不對致使insmod錯誤

 insmod成功後,就會發如今/sys/kernel目錄下多了一個kobject_example子目錄,目錄中包含三個屬性文件bar、baz、foo,該模塊的使用以下所示:

相關文章
相關標籤/搜索