阿輝原創,轉載請注明出處
參考文檔:LDD3-ch14、內核文檔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的結構。
kobject結構定義于include/linux/kobject.h,如下:
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文件系統中
entry:list_head入口,用于將該kobject鏈接到所屬kset的鏈表
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 to10 * 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 call10 * to kobject_put(), not by a call to kfree directly to ensure that all of11 * 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 else43 WARN(1, "%s failed for %s (error: %d parent: %s)/n",44 __func__, kobject_name(kobj), error,45 parent ? kobject_name(parent) : "'none'");46 } else47 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_ALLOC5 struct lock_class_key *key;6 struct lock_class_key skey;7 #endif8 };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 }這個函數調用之后,屬性文件將不會再出現在kobject在sysfs入口中錄中
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,該模塊的使用如下所示:

新聞熱點
疑難解答