Linux通常把设备对象抽象为,linux 设备模型(1)
設備模型(一)
一、概述
從2.6內核引入了sysfs文件系統,與proc, devfs, devpty同類別,屬于虛擬的文件系統。目的是展示設備驅動模型中各組件的層次關系,第一層目錄:block, device, bus, drivers, class, power, firmware.
block 塊設備;devices 系統所有的設備并根據設備掛接的總線類型組織成層次結構;bus 系統所有的總線類型;drivers 內核中所有已經注冊的設備驅動程序;class 系統中的設備類型(如網卡 設備、聲卡設備、輸入設備等)。在/sys/bus下和/sys/bus/下也會有設備文件,但那只是符號鏈接,它指向/sys/devices/下的真實設備。此即為Linux設備模型。
總線(bus)、設備(device)、驅動(driver)3個數據結構構成了設備的上層建筑,而kobject、kset、kobj_type(ktype)這三個數據結構構成了設備模型的經濟基礎。內核引入內核這概念最主要的目的無非就是為了省電,便于管理。Linux設備模型的目的是:為內核建立起一個統一的設備模型,從而有一個對系統結構的一般性抽象描述。設備模型提供了這個抽象. 現在它用在內核來支持不同的任務, 包括:
1.電源管理,根據設備的層次關系,當系統進入睡眠的時候,不需要一個一個設備的關,只需要關一個總線設備,接在總線下的設備就都會關掉。
2.sysfs虛擬文件系統的實現與設備模型的緊密相關,并向外界展示它所表述的結構。向用戶空間提供系統信息、改變操作參數的接口正越來越多地通過 sysfs,也就是設備模型來完成。
3.關于熱插拔,這跟掃描有關系,比如說,你把一個設備直接接在USB上,系統就會去掃描設備,并且在USB總線上尋找匹配的設備驅動,最后初始化設備,等待用戶使用。
4.設備模型的實現需要創建一系列機制來處理對象的生命周期、對象間的關系和對象在用戶空間的表示。Linux設備模型是一個復雜的數據結構。但對模型的大部分來說,Linux設備模型代碼會處理好這些關系,而不是把他們強加于驅動作者。模型隱藏于交互的背后,與設備模型的直接交互通常由總線級的邏輯和其他的內核子系統處理。所以許多驅動作者可完全忽略設備模型, 并相信設備模型能處理好他所負責的事。
在具體實現方面分兩個層次:
一是底層數據結構來實現基本對象及其層次關系:kobjects和ksets。
二是基于這兩個底層數據結構上實現的設備模型:總線,設備,驅動。
二、底層數據結構:kobject,kset
1)Kobject
1.概念
Kobject實現基本的面向對象管理機制,是構成設備模型的核心結構。它與sysfs文件系統緊密相連,在內核中注冊每個kobject對象對應sysfs文件系統中的一個目錄。
內核通過kobject 結構將各個對象連接起來組成一個分層的結構體系,可以把kobject理解為面向對象的基類,確切的說kobject也可以理解為所有驅動對象的基類,作為基類的kobject并不關心自己是如何實現的,所以,在內核中,沒有用kobject直接定義的變量,kobject只是作為一個抽象的基類而存在,而由于Linux內核是C編寫的,通過查看device_driver、device等結構體中內嵌了kobject結構體。
2.結構,定義在
struct kobject {
const char * k_name;/*指向設備名稱的指針 */
char name[KOBJ_NAME_LEN];/*kobject 的名字數組,設備名稱*/
struct kref kref;/*kobject 的引用計數*/
struct list_head entry;/*kobject 之間的雙向鏈表,與所屬的kset形成環形鏈表*/
struct kobject * parent;/*在sysfs分層結構中定位對象,指向上一級kset中的struct kobject kobj*/
struct kset * kset;/*指向所屬的kset*/
struct kobj_type * ktype;/*負責對該kobject類型進行跟蹤的struct kobj_type的指針*/
struct dentry * dentry;/*sysfs文件系統中與該對象對應的文件節點路徑指針*/
wait_queue_head_t poll;/*等待隊列頭*/
};
2.1 kobj_type結構
//kobject的ktype對象是一個指向kobject_type結構的指針,該結構記錄了kobject對象的一些屬性。每個kobject都需要對應一個相應的kobj_type結構。
struct kobj_type{
void (*release)(struct kobject *kobj);//release方法用于釋放kobject占用的資源,當kobject引用計數為0時被調用。
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;//對應于kobject的目錄下一個文件,name就是文件名。
};
2.1.1 kobje_type的attribute結構
struct attribute{
char*name;//屬性文件名
struct module *owner;//所屬的模塊
mode_t mode;//屬性文件的操作模式(可讀,可寫...)
}
2.1.2 kobje_type的struct sysfs_ops結構
struct sysfs_ops
{
ssize_t (*show)(structkobejct *, struct attribute *, char *name);//當用戶讀屬性文件時,該函數被調用,該函數將屬性值存入buffer中返回給用戶態;
ssize_t (*store)(structkobejct *, struct attribute *, char *name);//當用戶寫屬性文件時,該函數被調用,用于存儲用戶存入的屬性值。
}
3.這個在層次上處理最頂層的kobject結構提供了所有模型需要的最基本的功能:
(1)引用計數:跟蹤對象生命周期的一種方法是使用引用計數。當沒有內核代碼持有該對象的引用時, 該對象將結束自己的有效生命期并可被刪除。
(2)sysfs表示每個sys/下的對象對應著一個kobject。
(3)熱拔插事件處理。處理設備的熱拔插事件。
在 sysfs 中創建kobject的入口是kobject_add的工作的一部分,只要調用 kobject_add 就會在sysfs 中顯示,還有些知識值得記住:
(1)kobjects 的 sysfs 入口始終為目錄, kobject_add 的調用將在sysfs 中創建一個目錄,這個目錄包含一個或多個屬性(文件);
(2)分配給 kobject 的名字( 用 kobject_set_name ) 是 sysfs 中的目錄名,出現在 sysfs 層次的相同部分的 kobjects 必須有唯一的名字. 分配給 kobjects 的名字也應當是合法的文件名字: 它們不能包含非法字符(如:斜線)且不推薦使用空白。
(3)sysfs 入口位置對應 kobject 的 parent 指針。若 parent 是 NULL ,則它被設置為嵌入到新 kobject 的 kset 中的 kobject;若 parent 和 kset 都是 NULL, 則sysfs 入口目錄在頂層目錄,通常不推薦。
4.相關函數:
void kobjet_init(struct kobject*kobj)//初始化Kobject
int kobject_add(struct kobject*kobj)//將Kobject對象注冊到linux系統,如果失敗則返回一個錯誤碼.
struct kobject *kobject_get(struct kobject *kobj);/*若成功,遞增 kobject 的引用計數并返回一個指向 kobject 的指針,否則返回 NULL。必須始終測試返回值以免產生競態*/
void kobject_put(struct kobject *kobj);/*遞減引用計數并在可能的情況下釋放這個對象*/
int kobject_init_and_add(structkobject *kobj, kobj_type *ktype, struct kobject *parent, const *fmt…)
//初始化并注冊kobject,kobject傳入要初始化的Kobject對象,ktype將在后面介紹到,parent指向上級的kobject對象,如果指定位NULL,將在/sys的頂層創建一個目錄。*fmt為kobject對象的名字。
5.實例
#include
#include
#include
#include
#include
#include
#include
void obj_test_release(struct kobject *kobject);
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);
struct attribute test_attr = {
.name = "kobj_config",
.mode = S_IRWXUGO,
};
static struct attribute *def_attrs[] = {
&test_attr,
NULL,
};
struct sysfs_ops obj_test_sysops =
{
.show = kobj_test_show,
.store = kobj_test_store,
};
struct kobj_type ktype =
{
.release = obj_test_release,
.sysfs_ops=&obj_test_sysops,
.default_attrs=def_attrs,
};
void obj_test_release(struct kobject *kobject)
{
printk("eric_test: release .\n");
}
ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)
{
printk("have show.\n");
printk("attrname:%s.\n", attr->name);
sprintf(buf,"%s\n",attr->name);
return strlen(attr->name)+2;
}
ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)
{
printk("havestore\n");
printk("write: %s\n",buf);
return count;
}
struct kobject kobj;
static int kobj_test_init()
{
printk("kboject test init.\n");
kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");
return 0;
}
static int kobj_test_exit()
{
printk("kobject test exit.\n");
kobject_del(&kobj);
return 0;
}
module_init(kobj_test_init);
module_exit(kobj_test_exit);
在/sys目錄下創建了kobject_test目錄,在kobject_test目錄下有kobj_config文件。
讀kobject_config文件則調用了show函數。并在用戶空間顯示了show返回的kobject對象名字。寫kobject_config文件調用了store函數。
2)kset
1.概念
Kset是具有相同類型的kobject的集合,在sysfs中體現成一個目錄,在內核中用kset數據結構表示。
一個kset的主要功能是容納;它可被當作頂層的給kobjects的容器類.實際上,每個kset在內部容納它自己的 kobject,并且它可以,在許多情況下,如同一個kobject相同的方式被對待.值得注意的是ksets一直在sysfs中出現,一旦一個 kset已被建立并且加入到系統, 會有一個sysfs目錄給它.kobjects沒有必要在sysfs中出現, 但是每個是 kset 成員的 kobject 都出現在那里.
通俗的講,kobject建立一級的子目錄里面只能包含文件,kset可以為kobject建立多級的層次性的父目錄。
2.結構體
如果這個 kobject 是一個kset的成員, kset會提供kobj_type指針。通常情況下kobject只需要在葉節點里使用,上層的節點要使用kset。
struct kset {
struct kobj_type * ktype; /*指向該kset對象類型的指針*/
struct list_head list;/*用于連接該kset中所有kobject以形成環形鏈表的鏈表頭*/
spinlock_t list_lock;/*用于避免競態的自旋鎖*/
struct kobject kobj; /*嵌入的kobject*/
struct kset_uevent_ops * uevent_ops; //指向熱插拔操作表的指針
};
包含在kset中的所有的kobject被組織成一個上相的循環鏈表list域是該鏈表的頭指針,ktype域指向一個kobj_type結構,被該kset中的所有kobject共享,表示這些對象的類型,kset數據結構還內嵌了一個kobject對象,所有屬于這個kset的kobject對象的parent域指向這個內嵌的對象,此外kset還依賴于kobj維護引用計數:這就明了,kset的引用計數其實就是其內嵌對象kobject對象的的引用計數,具有相同類型的kobject集合在一起組成了kset,許多kset集合在一起組成了子系統subsystem。
kset 在一個標準的內核鏈表中保存了它的子節點,在大部分情況下, 被包含的 kobjects 在它們的 parent 成員中保存指向 kset內嵌的 kobject的指針,關系如下:
(1)ksets 有類似于kobjects初始化和設置接口。
(2)ksets 還有一個指針指向kobj_type結構來描述它包含的kobject,這個類型優先于kobject自身中的ktype。因此在典型的應用中, 在 struct kobject中的ktype成員被設為 NULL, 而kset中的ktype是實際被使用的。
(3)在新的內核里, kset不再包含一個子系統指針struct subsystem * subsys, 而且subsystem已經被kset取代。
(4)子系統是對整個內核中一些高級部分的表述。子系統通常出現在sysfs分層結構中的頂層,內核子系統包括 block_subsys(/sys/block 塊設備)、 devices_subsys(/sys/devices 核心設備層)以及內核已知的用于各種總線的特定子系統。對于新的內核已經不再有subsystem數據結構了,用kset代替了。每個 kset 必須屬于一個子系統,子系統成員幫助內核在分層結構中定位kset。
3.Kset操作:
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
int kset_register(struct kset*kset)//注冊kset
void kset_unregister(struct kset*kset)//注銷kset
4.實例
#include
#include
#include
#include
#include
#include
#include
#include
struct kset kset_p;
struct kset kset_c;
int kset_filter(struct kset *kset, struct kobject *kobj)
{
printk("Filter: kobj %s.\n",kobj->name);
return 1;
}
const char *kset_name(struct kset *kset, struct kobject *kobj)
{
static char buf[20];
printk("Name: kobj %s.\n",kobj->name);
sprintf(buf,"%s","kset_name");
return buf;
}
int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env)
{
int i = 0;
printk("uevent: kobj %s.\n",kobj->name);
while( i < env->envp_idx){
printk("%s.\n",env->envp[i]);
i++;
}
return 0;
}
struct kset_uevent_ops uevent_ops =
{
.filter = kset_filter,
.name = kset_name,
.uevent = kset_uevent,
};
int kset_test_init()
{
printk("kset test init.\n");
kobject_set_name(&kset_p.kobj,"kset_p");
kset_p.uevent_ops = &uevent_ops;
kset_register(&kset_p);
kobject_set_name(&kset_c.kobj,"kset_c");
kset_c.kobj.kset = &kset_p;
kset_register(&kset_c);
return 0;
}
int kset_test_exit()
{
printk("kset test exit.\n");
kset_unregister(&kset_p);
kset_unregister(&kset_c);
return 0;
}
module_init(kset_test_init);
module_exit(kset_test_exit);
可以看出當kset加載時,在/sys下創建了一個kset_p目錄,在kset_p下面創建了kset_c目錄,當kset模塊被加載和卸載時都產生了熱插拔事件。
三、代碼分析
/*****************************************************************************************************/
1.kobject對象的初始化以及添加到sysfs文件系統中
/*****************************************************************************************************/
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct kobject *parent, const char *fmt, ...)
{
va_list args;
int retval;
//初始化kobject
kobject_init(kobj, ktype);
va_start(args, fmt);
//為kobjcet設置名稱,在sysfs中建立相關信息
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args);
return retval;
}
//上面的流程主要分為兩部份。一部份是kobject的初始化。在這一部份,它將kobject與給定的ktype關聯起來。初始化kobject中的各項結構。
//另一部份是kobject的名稱設置。空間層次關系的設置,具體表現在sysfs文件系統中.
void kobject_init(struct kobject *kobj, struct kobj_type *ktype)
{
char *err_str;
if (!kobj) {//指針不能為空
err_str = "invalid kobject pointer!";
goto error;
}
if (!ktype) {//kobj_type指針也不能為空
err_str = "must have a ktype to be initialized properly!\n";
goto error;
}
if (kobj->state_initialized) {//標志為1表示kobject已經初始化過
printk(KERN_ERR "kobject (%p): tried to init an initialized ""object, something is seriously wrong.\n", kobj);
dump_stack();
}
kobject_init_internal(kobj);//初始化
kobj->ktype = ktype;//將kobject與給定的ktype關聯起來
return;
error:
printk(KERN_ERR "kobject (%p): %s\n", kobj, err_str);
dump_stack();
}
static void kobject_init_internal(struct kobject *kobj)
{
if (!kobj)
return;
kref_init(&kobj->kref);//初始化kobject的計數器,計數器設為1
INIT_LIST_HEAD(&kobj->entry);//初始化鏈表
kobj->state_in_sysfs = 0;
kobj->state_add_uevent_sent = 0;
kobj->state_remove_uevent_sent = 0;
kobj->state_initialized = 1;//表示kobject已經初始化過
}
static inline void kref_init(struct kref *kref)
{
atomic_set(&kref->refcount, 1);
}
//另一部份是kobject的名稱設置。空間層次關系的設置,具體表現在sysfs文件系統中.
static int kobject_add_varg(struct kobject *kobj, struct kobject *parent,const char *fmt, va_list vargs)
{
va_list aq;
int retval;
va_copy(aq, vargs);
//設置kobject的名字。即設置kobject的name成員
retval = kobject_set_name_vargs(kobj, fmt, aq);
va_end(aq);
if (retval) {
printk(KERN_ERR "kobject: can not set name properly!\n");
return retval;
}
//設置kobject的parent。
kobj->parent = parent;
//在sysfs中添加kobjcet信息
return kobject_add_internal(kobj);
}
//設置好kobject->name后,轉入kobject_add_internal()。在sysfs中創建空間結構.
static int kobject_add_internal(struct kobject *kobj)
{
int error = 0;
struct kobject *parent;
if (!kobj)
return -ENOENT;
//如果kobject的名字為空.退出
if (!kobj->name || !kobj->name[0]) {
pr_debug("kobject: (%p): attempted to be registered with empty ""name!\n", kobj);
WARN_ON(1);
return -EINVAL;
}
//取kobject的父結點,并遞增父kobject的計數
parent = kobject_get(kobj->parent);
//如果kobject的父結點沒有指定,而且kobj->kset已設定,就將kobj->kset->kobject做為它的父結點
if (kobj->kset) {
if (!parent)
parent = kobject_get(&kobj->kset->kobj);//遞增計數,返回kset->kobject
kobj_kset_join(kobj);//遞增kobj對象中的kset計數,并且將kobj->kset連接到kobj->entry隊列中
kobj->parent = parent;//設定父kobject
}
//在sysfs中創建kobject的相關信息
error = create_dir(kobj);
if (error) {
//v如果創建失敗。減少相關的引用計數
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",__FUNCTION__, kobject_name(kobj));
else
printk(KERN_ERR "%s failed for %s (%d)\n",__FUNCTION__, kobject_name(kobj), error);
dump_stack();
} else
//如果創建成功。將state_in_sysfs建為1。表示該object已經在sysfs中了
kobj->state_in_sysfs = 1;
return error;
}
static int create_dir(struct kobject *kobj)
{
int error = 0;
if (kobject_name(kobj)) {//kobject名稱不為空
error = sysfs_create_dir(kobj);//為kobject創建目錄
if (!error) {
error = populate_dir(kobj);//創建目錄成功,為kobject->ktype中的屬性創建屬性文件
if (error)
sysfs_remove_dir(kobj);
}
}
return error;
}
//kobject所表示的目錄創建過程,這是在sysfs_create_dir()中完成的。
int sysfs_create_dir(struct kobject * kobj)
{
struct sysfs_dirent *parent_sd, *sd;
int error = 0;
BUG_ON(!kobj);
/*如果kobject的parnet存在。就在目錄點的目錄下創建這個目錄。如果沒有父結點不存在,就在/sys下面創建結點。在上面的流程中,我們可能并沒有為其指定父結點,也沒有為其指定kset。*/
if (kobj->parent)
parent_sd = kobj->parent->sd;
else
parent_sd = &sysfs_root;
//在sysfs中創建目錄,參見sysfs文件系統解析
error = create_dir(kobj, parent_sd, kobject_name(kobj), &sd);
if (!error)
kobj->sd = sd;
return error;
}
//為kobject->ktype中的屬性創建文件,在populate_dir()中完成的。
static int populate_dir(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);//得到kobject的kobj_type結構
struct attribute *attr;
int error = 0;
int i;
if (t && t->default_attrs) {
for (i = 0; (attr = t->default_attrs[i]) != NULL; i++) {//遍歷ktype中的屬性
error = sysfs_create_file(kobj, attr);//在sysfs文件系統kobject目錄下創建屬性文件,參見sysfs文件系統解析
if (error)
break;
}
}
return error;
}
/****************************************************************************************************/
2.kset對象的初始化以及添加到sysfs文件系統中
/****************************************************************************************************/
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
{
struct kset *kset;
int error;
//創建一個kset容器
kset = kset_create(name, uevent_ops, parent_kobj);
if (!kset)
return NULL;
//注冊創建的kset容器
error = kset_register(kset);
if (error) {
kfree(kset);
return NULL;
}
return kset;
}
static struct kset *kset_create(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
{
struct kset *kset;
int retval;
kset = kzalloc(sizeof(*kset), GFP_KERNEL);//為kset分配內存空間
if (!kset)
return NULL;
//設置kset中kobject的名字
retval = kobject_set_name(&kset->kobj, name);
if (retval) {
kfree(kset);
return NULL;
}
kset->uevent_ops = uevent_ops;//設置uevent操作集
kset->kobj.parent = parent_kobj;//設置父對象
kset->kobj.ktype = &kset_ktype;//設置容器操作集
kset->kobj.kset = NULL;//設置父容器為空
return kset;
}
//創建好了kset之后,會調用kset_register().這個函數就是kset操作的核心代碼了.
int kset_register(struct kset *k)
{
int err;
if (!k)
return -EINVAL;
//繼續初始化
kset_init(k);
//向sysfs文件系統添加該容器,即為k內嵌的kobject結構建立空間層次結構,代碼見上
err = kobject_add_internal(&k->kobj);
if (err)
return err;
//因為添加了kset,會產生一個事件,這個事件是通過用戶空間的hotplug程序處理的,這就是kset明顯不同于kobject的地方.
kobject_uevent(&k->kobj, KOBJ_ADD);//參見udev原理
return 0;
}
void kset_init(struct kset *k)
{
kobject_init_internal(&k->kobj);//初始化kset中的kobject結構
INIT_LIST_HEAD(&k->list);//初始化鏈表
spin_lock_init(&k->list_lock);
}
總結
以上是生活随笔為你收集整理的Linux通常把设备对象抽象为,linux 设备模型(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux多线程编写哲学家,Linux系
- 下一篇: linux 本地做yum源,linux—