Linux內核大量使用面向對象的設計思想,通過追蹤源碼,我們甚至可以使用面向對象語言常用的UML類圖來分析Linux設備管理的"類"之間的關系。這裡以4.8.5內核為例從kobject,kset,kobj_type的分析入手,進而一探內核對於設備的管理方式
這個宏幾乎是linux數據結構的基礎,Linux中的鏈表與傳統的鏈表不同,其鏈表的節點本身並不包含任何數據,任何想要插入到鏈表的數據只需要包含一個事先寫好的節點
//include/linux/types.h
184 struct list_head {
185 struct list_head *next, *prev;
186 };
但是,使用這種通用的鏈表的第一個問題就是如何根據一個list_head成員來找到相應的數據,Linux社區的大神們早就找到了相應的方法,就是利用下面這個container_of宏,只需要輸入成員指針ptr,包含該成員的結構體類型type,以及該成員在結構體中名字name就可以返回包含ptr的type類型的結構首地址,這個宏充分利用了C語言直接操作內存的特性。需要注意的是,如果單純為了得到地址只需要ptr-&((type* 0)->member)
,內核的寫法其實還利用了編譯器的類型檢查機制做了一份校驗工作,即如果傳入的ptr類型和type->member的類型不匹配,會報錯,
//include/linux/kernel.h
14 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
830 #define container_of(ptr, type, member) ({ \
831 const typeof( ((type *)0)->member ) *__mptr = (ptr); \
832 (type *)( (char *)__mptr - offsetof(type,member) );})
Linux內核中有大量的驅動,而這些驅動往往具有類似的結構,根據面向對象的思想,我們就可以將這些共同的部分提取為父類,這個父類就是kobject,也就是驅動編程中使用的.ko文件的由來,下面這張圖是我根據內核源碼的kobject繪制的簡單的UML圖,從中可以看出,kobject包含了大量的設備必須的信息,而三大類設備驅動都需要包含這個kobject結構,也就是"繼承"自kobject。一個kobject對象就對應sys目錄中的一個設備。
內核源碼中的kobject結構定義如下
//include/linux/kobject.h
63 struct kobject {
64 const char *name;
65 struct list_head entry;
66 struct kobject *parent;
67 struct kset *kset;
68 struct kobj_type *ktype;
69 struct kernfs_node *sd; /* sysfs directory entry */
70 struct kref kref;
71 #ifdef CONFIG_DEBUG_KOBJECT_RELEASE
72 struct delayed_work release;
73 #endif
74 unsigned int state_initialized:1;
75 unsigned int state_in_sysfs:1;
76 unsigned int state_add_uevent_sent:1;
77 unsigned int state_remove_uevent_sent:1;
78 unsigned int uevent_suppress:1;
79 };
這個結構中,name(64)表示kobject對象的名字,對應sysfs下的一個目錄。entry(65)是kobject中插入的head_list結構,parent(66)是指向當前kobject父對象的指針,體現在sys結構中就是包含當前kobject對象的目錄對象,kset(67)表示當前kobject對象所屬的集合,ktype(68)表示當前kobject的類型。sd(69)用於表示VFS文件系統的目錄項,是設備與文件之間的橋梁。kref(70)是對kobject的引用計數,當引用計數為0是,就回調之前注冊的release方法釋放該對象。state_initialized:1(74)初始化標志位,在對象初始化時被置位,表示對象是否已經被初始化。state_in_sysfs:1(75)表示kobject對象在sysfs中的狀態,在對應目錄中被創建則置1,否則為0。state_add_uevent_sent:1(76)是添加設備的uevent事件是否發送標志,添加設備時會向用戶空間發送uevent事件,請求新增設備。state_remove_uevent_sent:1(76)是刪除設備的uevent事件是否發送標志,刪除設備時會向用戶空間發送uevent事件,請求卸載設備
4.8.5的內核在lib/koject.c等源碼中定義了一系列對kobject操作的函數,這裡只列出最簡單的幾個
187 static void kobject_init_internal(struct kobject *kobj)
188 {
189 if (!kobj)
190 return;
191 kref_init(&kobj->kref);
192 INIT_LIST_HEAD(&kobj->entry);
193 kobj->state_in_sysfs = 0;
194 kobj->state_add_uevent_sent = 0;
195 kobj->state_remove_uevent_sent = 0;
196 kobj->state_initialized = 1;
197 }
325 void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
326 {
327 char *err_str;
...
344 kobject_init_internal(kobj);
345 kobj->ktype = ktype;
346 return;
...
351 }
//添加kobject到內核
200 static int kobject_add_internal(struct kobject *kobj)
201 {
202 int error = 0;
203 struct kobject *parent;
...
214 parent = kobject_get(kobj->parent);
215
216 /* join kset if set, use it as parent if we do not already have one */
217 if (kobj->kset) {
218 if (!parent)
219 parent = kobject_get(&kobj->kset->kobj);
220 kobj_kset_join(kobj);
221 kobj->parent = parent;
222 }
223
224 pr_debug("kobject: '%s' (%p): %s: parent: '%s', set: '%s'\n",
225 kobject_name(kobj), kobj, __func__,
226 parent ? kobject_name(parent) : "<NULL>",
227 kobj->kset ? kobject_name(&kobj->kset->kobj) : "<NULL>");
228
229 error = create_dir(kobj);
...
246 kobj->state_in_sysfs = 1;
247
248 return error;
249 }
354 static __printf(3, 0) int kobject_add_varg(struct kobject *kobj,
355 struct kobject *parent,
356 const char *fmt, va_list vargs)
357 {
358 int retval;
...
365 kobj->parent = parent;
366 return kobject_add_internal(kobj);
367 }
394 int kobject_add(struct kobject *kobj, struct kobject *parent,
395 const char *fmt, ...)
396 {
397 va_list args;
398 int retval;
...
410 va_start(args, fmt);
411 retval = kobject_add_varg(kobj, parent, fmt, args);
412 va_end(args);
413
414 return retval;
415 }
429 int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,
430 struct kobject *parent, const char *fmt, ...)
431 {
432 va_list args;
433 int retval;
434
435 kobject_init(kobj, ktype);
436
437 va_start(args, fmt);
438 retval = kobject_add_varg(kobj, parent, fmt, args);
439 va_end(args);
440
441 return retval;
442 }
569 void kobject_del(struct kobject *kobj)
570 {
571 struct kernfs_node *sd;
572
573 if (!kobj)
574 return;
575
576 sd = kobj->sd;
577 sysfs_remove_dir(kobj);
578 sysfs_put(sd);
579
580 kobj->state_in_sysfs = 0;
581 kobj_kset_leave(kobj);
582 kobject_put(kobj->parent);
583 kobj->parent = NULL;
584 }
//將kobject對象的引用計數加1,同時返回該對象指針。
//include/linux/kref.h
40 static inline void kref_get(struct kref *kref)
41 {
42 /* If refcount was 0 before incrementing then we have a race
43 * condition when this kref is freeing by some other thread right now.
44 * In this case one should use kref_get_unless_zero()
45 */
46 WARN_ON_ONCE(atomic_inc_return(&kref->refcount) < 2);
47 }
//lib/kobject.c
591 struct kobject *kobject_get(struct kobject *kobj)
592 {
593 if (kobj) {
...
598 kref_get(&kobj->kref);
599 }
600 return kobj;
601 }
//將kobject對象的引用計數加1,如果減為零就釋放
//include/linux/kref.h
67 static inline int kref_sub(struct kref *kref, unsigned int count,
68 void (*release)(struct kref *kref))
69 {
70 WARN_ON(release == NULL);
71
72 if (atomic_sub_and_test((int) count, &kref->refcount)) {
73 release(kref);
74 return 1;
75 }
76 return 0;
77 }
96 static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
97 {
98 return kref_sub(kref, 1, release);
99 }
//lib/kobject.c
684 void kobject_put(struct kobject *kobj)
685 {
686 if (kobj) {
...
691 kref_put(&kobj->kref, kobject_release);
692 }
693 }
kset表示一組kobject的集合,這些kobject可以是不同或相同的類型(ktype)。sysfs中的設備組織結構很大程度上都是根據kset進行組織的,比如在平台設備模型中,當我們注冊一個設備或驅動到平台總線,其實是將對應的kobject掛接到platform總線的kset上,每種總線都是維護兩條鏈表(兩個kset),一條用於鏈接掛接在上面的驅動(驅動kset),一條用於鏈接掛接在上面的設備(設備kset)。
//include/linux/kobject.h
168 struct kset {
169 struct list_head list;
170 spinlock_t list_lock;
171 struct kobject kobj;
172 const struct kset_uevent_ops*uevent_ops;
173 };
list_head(169)還是那個用來掛在鏈表上的結構。kobj(171)是歸屬於該kset的所有的kobject的共有parent,這個parent就是體現內核設備組織結構的關鍵
//include/linux/kobject.h
116 struct kobj_type {
117 void (*release)(struct kobject *kobj);
118 const struct sysfs_ops *sysfs_ops;
119 struct attribute **default_attrs;
120 const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
121 const void *(*namespace)(struct kobject *kobj);
122 };
//include/linux/sysfs.h
29 struct attribute {
30 const char *name;
31 umode_t mode;
32 #ifdef CONFIG_DEBUG_LOCK_ALLOC
33 bool ignore_lockdep:1;
34 struct lock_class_key *key;
35 struct lock_class_key skey;
36 #endif
37 };
209 struct sysfs_ops {
210 ssize_t (*show)(struct kobject *, struct attribute *, char *);
211 ssize_t (*store)(struct kobject *, struct attribute *, const char *, si
212 };
這個結構主要是表征kobject的類型,其中,release(117)是一個釋放kobject對象的接口,有點像面向對象中的析構。sysfs_ops(118)是操作kobject的方法集。由此可見,對同一類型的kobject操作會回調同一個kobj_type的方法
//include/linux/kobject.h
197 static inline struct kobj_type *get_ktype(struct kobject *kobj)
198 {
199 return kobj->ktype;
200 }
從這個函數中可以看出,4.8.5提取kobject的ktype的時候直接提取kobject的,我還測試過3.14版本的,也是這種寫法,不過網上還有下面的這種get_ktype的實現,還沒找到具體是哪個版本,顯然,這個版本中kset中的ktype這個類型優先於 kobject 自身中的 ktype 。因此在典型的應用中, 在 struct kobject 中的 ktype 成員被設為 NULL, 而 kset 中的ktype是實際被使用的。
static inline struct kobj_type * get_ktype(struct kobject * k)
{
if (k->kset && k->kset->ktype)
return k->kset->ktype;
else
return k->ktype;
}
kobject,kset是Linux設備管理中的基本結構體,但在實際操作中我們幾乎不會實際操作這些結構,因為他們本身並不具有針對某一個具體設備或驅動的信息,在Linux內核中,��兩個結構都是被包含具體的設備結構中,比如cdev,gendisk等,從面向對象的角度考慮,就是每一類設備都可以看作這兩個結構的子類。
通過上面的分析,我們可以看出這三者之間的關系,並畫出下面的結構框圖,sysfs中的上目錄結構就是根據kset之間的數據組織方式進行呈現的。