kobject sysfs
- 1 kobject
- 2 ktype
- 3 kset
- 4 subsystem
- 5 別混淆了這些結構體
- 6 管理和操作kobject
- 7 引用計數
- kref
- 8 sysfs
- sysfs中添加和刪除kobject
- 向sysfs添加文件
- 9 內核事件層
2.6內核增加了一個引人注目的新特性—同一設備模型。設備模型提供了獨立的機制專門表示設備,并描述在系統中的拓撲結構。
1 kobject
設備模型的核心部分就是kobject,它由struct kobject,定義于頭文件linux/kobject.h中。kobject類似于C#或java這些面向對象語言中的object對象類,提供了諸如計數、名稱和父指針等字段,可以創建對象的層次結構。
struct kobject {char * k_name;char name[KOBJ_NAME_LEN];struct kref kref;struct list_head entry;struct kobject * parent;struct kset * kset;struct kobj_type * ktype;struct dentry * dentry;
};
k_name指針指向kobject名稱,如果名稱長度小于KOBJ_NAME_LEN,那么該kobject的名稱就存放在name數組中,k_name指向數組頭,如果名稱長度大于KOBJ_NAME_LEN,則動態分配一個足夠大的緩沖區來存放kobject的名稱。KOBJ_NAME_LEN當前為20個字節。
parent指針指向kobject的父對象。因此,kobject就會在內核中構造一個對象層次結構,并且可以將多個對象間的關系表項出來。
dentry指針指向dentry結構體,在sysfs中該結構體就表示這個kobject。
kref實現了kobject的引用計數。
kobject通常是嵌入到其他結構體中的,其單獨意義其實并不大。當kobject被嵌入到其他結構體中時,該結構體便擁有了kobject提供的標準功能。
2 ktype
kobject對象中的ktype域,該結構體定義于頭文件linux/kobject.h中
struct kobj_type {void (*release)(struct kobject *);struct sysfs_ops * sysfs_ops;struct attribute ** default_attrs;
};
ktype是為了描述一族kobject所具有的普遍特性。因此,不在需要每個kobject都分別定義自己的特性,而是將這些特性在ktype結構體中一次定義,然后所有“同類”的kobject都能共享一樣的特性。
release指針指向在kobject引用計數減為零時要被調用的析構函數。該函數負責釋放所有的kobject使用的內存和其他相關清理工作。
sysfs_ops變量指向sysfs_ops結構體。該結構體表述了sysfs文件讀寫是的特性。
default_attrs指向一個attribute結構體數組。這些結構體定義了該kobject相關的默認屬性。屬性描述了給定對象的特征,如果該kobject被導出到sysfs中,那么這些屬性都將相應地作為文件而導出。數組中的最后一項必須為NULL。
3 kset
kobject中的kset域,是kobject對象的集合體。把它看成是一個容器,可將所有相關的kobject對象,比如“全部的塊設備”置于一個位置。kset把kobject集中到一個集合中。
kset指針指向kset集合,kset集合由kset結構體表示,定義于頭文件linux/kobject.h中
struct kset {struct subsystem * subsys;struct kobj_type * ktype;struct list_head list;struct kobject kobj;struct kset_hotplug_ops * hotplug_ops;
};
其中ktype指向kset集合中kobject對象的類型。list連接該集合中所有的kobject對象,kobj指向的kobject對象代表了該集合的基類。hotplug_ops指向一個用于處理集合中kobject對象的熱插拔操作的結構體。
subsys指針指向該結構體相關的struct subsystem結構體。
4 subsystem
subsystem在內核中代表高層概念,它是一個或多個kset的大集合。
struct subsystem {struct kset kset;struct rw_semaphore rwsem;
};
雖然subsystem結構體只指向一個kset,但是多個kset可以通過其subsys指針指向一個subsystem。這種單向關系意味著不可能僅僅通過一個subsystem結構體就找到所有的kset。
subsystem中的kset字段指向的是subsystem中默認的kset,任rwsem字段是一個讀寫信號量,該信號量用來對subsystem和它的所有的kset進行并發訪問保護。
5 別混淆了這些結構體
這里最重要的是kobject,它由struct kobject表示。kobject為我們引入了諸如計數、父子關系和對象名稱等基本對象道具,并且是以一個統一方式提供這些功能。不過kobject本身意義不大,需要被嵌入到其他數據結構中。
kobject與一個特定的ktype對象關聯,ktype由struct kobject_type結構體表示,在kobject中ktype字節指向該對象。ktype定義了一些kobject相關的默認特性:析構行為、sysfs行為以及其他一些默認屬性。
kset集合由struct kset結構體表示。kset提供了兩個功能。第一,其中嵌入的kobject作為kobject組合基類。第二,kset將相關的kobject集合在一起。在sysfs中,這些相關的kobject將以獨立的目錄出現在文件系統中。
subsystem表示的更為宏觀,它是包含kset的集合,由struct subsystem結構體表示。sysfs中的根目錄映射的便是subsystem。
這些數據結構的內在關系如下:
6 管理和操作kobject
多數時候,驅動程序開發者并不直接處理kobject,因為kobject是被嵌入到一些特殊類型結構體中的,而且由相關的 設備驅動程序在幕后管理。
使用kobject的第一步需要先來聲明和初始化它。kobject通過函數kobject_init進行初始化,該函數定義在文件linux/kobject.h中:
void kobject_init(struct kobject *kobj);
該函數的唯一參數就是需要初始化的kobject對象,在調用初始化函數前,kobject必須清空。如果kobject未被清空,那么只需要調用memset()即可。
在清理后,就可以安全地初始化parent和kset字段。
初始化后,必須采用kobject_set_name()函數為kobject設置名稱:
int kobject_set_name(struct kobject *kobj,const char *fmt,...);
該函數利用了printf()和printk()風格的可變參數隊列來為kobject設置名稱。
初始化了kobject并設置名稱后,還需要為它設置kset字段以及可能的ktype字段(可選)。如果kset沒有被提供,那么僅需要設置ktype,否則kset中的ktype字段將優先被使用。
7 引用計數
kobject的主要功能之一就是為我們提供了一個同一的引用計數系統。初始化后,kobject的引用計數設置為1,只要引用計數不為0,那么該對象就會繼續保留在內存中。任何包含對象引用的代碼首先要增加該對象的引用計數,當代碼結束后則減少它的引用計數。增加引用計數稱為獲得(get)對象的引用,減少引用計數稱為釋放(put)對象的引用。當引用計數減少到0時,對象便可以被銷毀。
增加一個引用計數可通過kobject_get()函數完成:
struct kobject * kobject_get(struct kobject *kobj);
該函數正常情況下返回一個指向kobject的指針,如果失敗,返回NULL。
減少引用計數是通過kobject_put()完成:
void kobject_put(struct kobject *kobj);
如果對應的kobject的引用計數減少到0,則與該kobject關聯的ktype中的析構函數將被調用。
kref
kobject的引用計數是通過kref結構體實現的,該結構體定義在頭文件linux/kref.h中:
struct kref {atomic_t refcount;
};
其中唯一的字段是用來存放引用計數的原子變量。在使用kref之前,必須先通過kref_init來初始化它:
void kref_init(struct kref *kref)
{atomic_set(&kref->refcount,1);
}
這個函數簡單地將原子變量置為1,所以kref一旦被初始化,它表示的引用計數便固定為1。
要獲得對kref的引用,需要調用kref_get()函數
void kref_get(struct kref *kref)
{WARN_ON(!atomic_read(&kref->refcount));atomic_inc(&kref->refcount);
}
該函數增加引用計數值,它沒有返回值。減少對kref的引用,調用函數kref_put():
void kref_put(struct kref *kref,void (*release)(struct kref *kref))
{WARN_ON(release == NULL);WARN_ON(release == (void (*)(struct kref*))kfree);if(atomic_dec_and_test(&kref->refcount))release(kref);
}
該函數將使引用計數減1,如果減少到0,則調用作為參數提供的release函數,注意WARN_ON()聲明,提供的release函數不能簡單地采用kfree,它必須是一個僅接受一個kref結構體作為參數的特有函數,而且還沒有返回值。
上述的所有函數定義與聲明分別在文件lib/kref.c和文件linux/kref.h中。
8 sysfs
sysfs文件系統是一個處于內存中的虛擬文件系統,它為我們提供了kobject對象層次結構的視圖。幫助用戶能以一個簡單文件系統的方式來觀察系統中各種設備的拓撲結構,借助屬性對象,kobject可以用到處文件的方式,將內核變量提供給用戶讀取或寫入。
sysfs的訣竅是把kobject對象與目錄項緊緊聯系起來,這是通過kobject對象中的dentry域實現的。dentry結構體表示目錄項,通過連接kobject到指定的目錄項上,無疑方便地將kobject映射到該目錄上。從此,把kobject導出形成文件系統就變得如同在內存中構建目錄項一樣簡單。由于kobject被映射到目錄項,同時kobject形成一棵樹,因此sysfs的生成就很簡單了。
sysfs中添加和刪除kobject
僅僅初始化kobject是不能自動將其導出到sysfs中的,想要把kobject導入sysfs,需要用到函數kobject_add():
int kobject_add(struct kobject *kobj);
kobject在sysfs中位置取決于kobject在對象層次結構中的位置。如果kobject的parent指針被設置,那么在sysfs中kobject將被映射為父目錄下的子目錄,如果parent沒有設置,那么kobject將被映射為kset->kobj中的子目錄。如果給定的kobject中parent或kset字段都沒有設置,那么就認為kobject沒有父對象,所以就會被映射成sysfs下的根級目錄。
你不必分別調用kobject_init()和kobject_add(),因為系統提供函數kobject_register()可幫助你一步到位:
int kobject_register(struct kobject *kobj);
該函數即初始化給定的kobject對象,同時又將其加入到對象層次結構體中。
從sysfs中刪除一個kobject對應文件目錄,需要使用函數kobject_del():
void kobject_del(struct kobject *kobj);
類似地,函數kobject_unregister()包含了kobjeect_del和kobject_put二者的功能:
void kobject_unregister(struct kobject *kobj);
上述四個函數都定義與文件lib/kobject.c中,聲明于文件linux/kobject.h中。
向sysfs添加文件
sysfs僅僅是一個漂亮的樹,但是沒有提供實際數據的文件。
默認的文件集合是通過kobject和kset中的ktype字段提供的。因此所有具有相同類型的kobject在它們對應的sysfs目錄下都擁有相同的默認文件集合,kobj_type字段含有一個字段,default_attrs,它是一個attribute結構體數組。這些屬性負責將內核數據映射成sysfs中的文件。
attribute結構體定義在文件linux/sysfs.h中:
struct attribute {char * name; /* 屬性名 */struct module * owner; /* 所屬模塊,如果存在 */mode_t mode; /* 權限 */
};
name提供該屬性的名稱,最終出現在sysfs中的文件名就是它;owner字段在存在所屬模塊的情況下指向其所屬module結構體。如果一個模塊沒有該屬性,那么該字段為NULL。mode表示了sysfs中該文件的權限。sysfs中的所有文件和目錄的uid和gid標志均為0。
sysfs_ops字段描述了如何使用他們,sysfs_ops字段指向了一個定義于文件linux/sysfs.h的同名的結構體:
struct sysfs_ops {ssize_t (*show)(struct kobject *, struct attribute *,char *);ssize_t (*store)(struct kobject *,struct attribute *,const char *, size_t);
};
show方法在讀操作時被調用。它會拷貝由attr提供的屬性值到buffer指定的緩沖區中,緩沖區大小為PAGE_SIZE字節。
store方法在寫操作時調用,它會從buffer中讀取size大小的字節,并將其存放入attr表示的屬性結構體變量中。
- 創建新屬性
int sysfs_create_file(struct kobject *kobj,const struct attribute *attr);
sysfs_create_file來創建新屬性,通過attr參數指向相應的attribute結構體,而kobj則制定了屬性所在的kobject對象。
除了添加文件外,還有可能需要創建符號連接,在sysfs中創建一個符號連接相當簡單:
int sysfs_create_link(struct kobject *kobj,struct kobject *target,char *name);
該函數創建的符號連接名由name指定,連接則由kobj對應的目錄映射到target指定的目錄
- 刪除新屬性
刪除一個屬性需通過函數sysfs_remove_file完成:
sysfs_remove_file(struct kobject *kobj,const struct attribute *attr);
另外由sysfs_create_link()創建的符號連接可通過函數sysfs_remove_link刪除:
void sysfs_remove_link(struct kobject *kobj,char *name);
上述的四個函數在文件linux/kobject.h中聲明,sys_create_file和sysfs_remove_file函數定義于文件fs/sysfs/file.c中;sysfs_create_link和sysfs_remove_link函數定義在文件fs/sysfs/symlink.c中。
9 內核事件層
內核事件層把事件模擬為信號,從明確的kobject發出,所以每個事件源都是一個sysfs路徑。
每個事件都有一個可選的負載,相比傳遞任意一個表示負載的字符串到用戶空間而言,內核事件層使用sysfs屬性代表負載。
在內核代碼中向用戶空間發送信號使用函數kobject_uevent():
int kobject_uevent(struct kobject *kobj,enum kobject_action action struct attribute *attr);
第一個參數指定發送該信號的kobject對象,實際的內核事件將包含該kobject映射到sysfs的路徑。
第二個參數指定來描述該信號的動作。實際的內核事件將包含一個映射成枚舉類型kobject_action的字符串。該函數不是直接提供一個字符串,而是利用一個枚舉變量來提高可重用性和保證類型安全,而且也消除了打字錯誤或別的其他錯誤,該枚舉變量定義于文件linux/kobject_uevent.c中,其形式為KOBJ_foo。當前值包含KOBJ_MOUNT、KOBJ_UNMOUNT、KOBJ_ADD等,這些值分別映射"mount"、“unmonut”、“add”等。
最后一個參數是指向attribute結構體的可選指針,它可看作為事件的“負載”。
該函數分配內存,所以可以睡眠。但在內核中實現了原子和非原子操作兩個版本,其不同在于原子操作使用GFP_ATOMIC標志分配內存。
int kobject_uevent_atomic(struct kobject *kobj,enum kobject_action action,struct attribute *attr);
這兩個函數分別定義和聲明在文件lib/kobject_uevent.c于文件linux/kobject_uevent.h中