1. 程式人生 > >Linux裝置驅動工程師之路——裝置模型(上)底層模型

Linux裝置驅動工程師之路——裝置模型(上)底層模型

Linux裝置驅動工程師之路——裝置模型(上)底層模型

K-Style

一、重要知識點

         1.Sysfs檔案系統

       Sysfs檔案系統是一種類似於proc檔案系統的特殊檔案系統,它存在於記憶體當中,當系統啟動時由核心掛載於記憶體當中。用於將系統中的裝置組織成層次結構,並向用戶模式程式提供詳細的資料結構資訊。

    2.Linux裝置底層模型

       1)為什麼要使用裝置模型

       隨著系統的拓撲結構越來越複雜,以及要支援諸如電源管理等新特性的要求,於是在2.6的核心中出現了裝置模型。裝置模型其實就是一套資料結構建立起來的模型。核心使用該模型支援了多種不同的任務,包括:

       a.電源管理和系統關機

       裝置模型使作業系統能夠以正確的順序遍歷系統硬體。

       b.與使用者空間通訊

       Sysfs檔案系統向用戶空間提供系統資訊以及改變操作引數的結構。

       c.熱插拔事件

       d.裝置型別

       系統中許多部分對裝置如何連線不感興趣,但是他們需要知道哪些型別裝置時可用的。裝置模型提供了將裝置分類的機制。

       e.物件的生命週期

       上述的許多功能,包括熱插拔支援和sysfs,使得核心中管理物件的工作更為複雜。裝置模型需要創造一套機制管理物件的生命週期。

       2)Kobject

       如果說裝置模型是一套房子的話,Kobject就是構造房子的磚塊。每個註冊的Kobject的都對應與Sysfs檔案系統中的一個目錄。Kobject是組成裝置模型的基本結構。類似於C++的基類,它潛入於更大的物件中——所謂的容器,用來描述裝置模型的元件。如bus,device,drivers都是典型的容器。這些容器就是通過kobject連線起來,形成一個樹狀結構。這個樹狀結構就與/sys檔案系統對應。不過kobject只能建立單層結構,也就是隻能建立一級目錄,要建立多級目錄,還要使用後面要介紹的Kset。

Kobject結構定義為:

struct kobject {

char * k name; 指向裝置名稱的指標

char name[KOBJ NAME LEN]; 裝置名稱

struct kref kref; 物件引用計數

struct list head entry; 掛接到所在kset中去的單元

struct kobject * parent; 指向父物件的指標

struct kset * kset; 所屬kset的指標

struct kobj type * ktype; 指向其物件型別描述符的指標

struct dentry * dentry; sysfs檔案系統中與該物件對應的檔案節點路徑指標

};

相關操作函式:

void kobjet_init(struct kobject*kobj)

初始化Kobject

int kobject_add(struct kobject*kobj)

將Kobject物件註冊到linux系統,如果失敗則返回一個錯誤碼.

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物件的名字。

kobject的ktype物件是一個指向kobject_type結構的指標,該結構記錄了kobject物件的一些屬性。每個kobject都需要對應一個相應的kobject結構。

struct kobj_type{

       void (*release)(struct kobject *kobj);

       structsysfs_ops *sysfs_ops;

       structattribute **default_attrs;

};

release方法用於釋放kobject佔用的資源,當kobject引用計數為0時被呼叫。

kobje_type的attribute成員:

struct attribute{

              char*name;//屬性檔名

       structmodule *owner;

       mode_tmode;

}

struct attribute(屬性):對應於kobject的目錄下一個檔案,name就是檔名。

kobje_type的struct sysfs_ops成員:

struct sysfs_ops

{

ssize_t (*show)(structkobejct *,  struct attribute *,  char  *name);

ssize_t (*store)(structkobejct *,  struct attribute *,  char  *name);

}

       show:當用戶讀屬性檔案時,該函式被呼叫,該函式將屬性值存入buffer中返回給使用者態;

       store:當用戶寫屬性檔案時,該函式被呼叫,用於儲存使用者存入的屬性值。

Kobject測試模組:

  1. #include <linux/device.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/init.h>
  5. #include <linux/string.h>
  6. #include <linux/sysfs.h>
  7. #include <linux/stat.h>
  8. MODULE_AUTHOR("David Xie");  
  9. MODULE_LICENSE("Dual BSD/GPL");  
  10. void obj_test_release(struct kobject *kobject);  
  11. ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);  
  12. ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);  
  13. struct attribute test_attr = {  
  14.         .name = "kobj_config",  
  15.         .mode = S_IRWXUGO,  
  16. };  
  17. static struct attribute *def_attrs[] = {  
  18.         &test_attr,  
  19.         NULL,  
  20. };  
  21. struct sysfs_ops obj_test_sysops =  
  22. {  
  23.         .show = kobj_test_show,  
  24.         .store = kobj_test_store,  
  25. };  
  26. struct kobj_type ktype =   
  27. {  
  28.         .release = obj_test_release,  
  29.         .sysfs_ops=&obj_test_sysops,  
  30.         .default_attrs=def_attrs,  
  31. };  
  32. void obj_test_release(struct kobject *kobject)  
  33. {  
  34.         printk("eric_test: release .\n");  
  35. }  
  36. ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)  
  37. {  
  38.         printk("have show.\n");  
  39.         printk("attrname:%s.\n", attr->name);  
  40.         sprintf(buf,"%s\n",attr->name);  
  41.         return strlen(attr->name)+2;  
  42. }  
  43. ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)  
  44. {  
  45.         printk("havestore\n");  
  46.         printk("write: %s\n",buf);  
  47.         return count;  
  48. }  
  49. struct kobject kobj;  
  50. static int kobj_test_init()  
  51. {  
  52.         printk("kboject test init.\n");  
  53.         kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");  
  54.         return 0;  
  55. }  
  56. static int kobj_test_exit()  
  57. {  
  58.         printk("kobject test exit.\n");  
  59.         kobject_del(&kobj);  
  60.         return 0;  
  61. }  
  62. module_init(kobj_test_init);  
  63. module_exit(kobj_test_exit);  

測試結果:



在/sys目錄下建立了kobject_test目錄

在kobject_test目錄下有kobj_config檔案

讀kobject_config檔案則呼叫了show函式。並在使用者空間顯示了show返回的kobject物件名字。

寫kobject_config檔案呼叫了store函式。

3)Kset

kset的主要功能是包容;我們可以認為他他是kobject的頂層容器。實際上,在每個kset物件的內部,包含了自己的kobject,並且可以用多種處理kobject的方法處理kset。如果說kobject是基類的話,那麼kset就是派送類。kobject通過kset組織成層次化的結構,kset是相同型別的組合。通俗的講,kobject建立一級的子目錄,kset可以為kobject建立多級的層次性的父目錄。

struct kset {

struct subsystem * subsys; 所在的subsystem的指標

struct kobj type * ktype; 指向該kset物件型別描述符的指標

struct list head list; 用於連線該kset中所有kobject的連結串列頭

struct kobject kobj; 嵌入的kobject

struct  kset_uevent_ops * uevent_ops; 指向熱插拔操作表的指標

};

包含在kset中的所有kobject被組織成一個雙向迴圈連結串列,list域正是該連結串列的頭。Ktype域指向一個kobj type結構,被該kset中的所有kobject共享,表示這些物件的型別。Kset資料結構還內嵌了一個kobject物件(由kobj域表示),所有屬於這個kset 的kobject物件的parent域均指向這個內嵌的物件。此外,kset還依賴於kobj維護引用計數:kset的引用計數實際上就是內嵌的kobject物件的引用計數。

kset與kobject的關係圖

 

Kset操作:

int kset_register(struct kset*kset)

註冊kset

void kset_unregister(struct kset*kset)

登出kset

       熱插拔事件:在linux系統中,當系統配置發生變化時,如新增kset到系統或移動kobject,一個通知會從核心空間傳送到使用者空間,這就是熱插拔事件。熱插拔事件會導致使用者空間中的處理程式(如udev,mdev)被呼叫,這些處理程式會通過載入驅動程式,建立裝置節點等來響應熱插拔事件。

       對熱插拔事件的實際控制是由struct kset_uevent_ops結構中的函式完成的。

       struct kset_uevnt_ops{

       int (*filter)(struct kset *kset,struct  kobject *kobj);

       const char *(*name)(struct kset *kset, struct kobject *kobj );

       int (*uevent)(struct kset *kset,struct  kobject *kobj,struct kobj_uevent *env);

}

filter決定是否產生事件,如果返回0,將不產生事件。

name向用戶空間傳遞一個合適的字串

uevent通過環境變數傳遞任何熱插拔指令碼需要的資訊,他會在(udev或mdev)呼叫之前,提供新增環境變數的機會。

       kset測試模組:

  1. #include <linux/device.h>
  2. #include <linux/module.h>
  3. #include <linux/kernel.h>
  4. #include <linux/init.h>
  5. #include <linux/string.h>
  6. #include <linux/sysfs.h>
  7. #include <linux/stat.h>
  8. #include <linux/kobject.h>
  9. MODULE_AUTHOR("David Xie");  
  10. MODULE_LICENSE("Dual BSD/GPL");  
  11. struct kset kset_p;  
  12. struct kset kset_c;  
  13. int kset_filter(struct kset *kset, struct kobject *kobj)  
  14. {  
  15.         printk("Filter: kobj %s.\n",kobj->name);  
  16.         return 1;  
  17. }  
  18. const char *kset_name(struct kset *kset, struct kobject *kobj)  
  19. {  
  20.         static char buf[20];  
  21.         printk("Name: kobj %s.\n",kobj->name);  
  22.         sprintf(buf,"%s","kset_name");  
  23.         return buf;  
  24. }  
  25. int kset_uevent(struct kset *kset, struct kobject *kobj,struct kobj_uevent_env *env)  
  26. {  
  27.         int i = 0;  
  28.         printk("uevent: kobj %s.\n",kobj->name);  
  29.         while( i <env->envp_idx){  
  30.                 printk("%s.\n",env->envp[i]);  
  31.                 i++;  
  32.         }  
  33.         return 0;  
  34. }  
  35. struct kset_uevent_ops uevent_ops =   
  36. {  
  37.         .filter = kset_filter,  
  38.         .name   = kset_name,  
  39.         .uevent = kset_uevent,  
  40. };  
  41. int kset_test_init()  
  42. {  
  43.         printk("kset test init.\n");  
  44.         kobject_set_name(&kset_p.kobj,"kset_p");  
  45.         kset_p.uevent_ops = &uevent_ops;  
  46.         kset_register(&kset_p);  
  47.         kobject_set_name(&kset_c.kobj,"kset_c");  
  48.         kset_c.kobj.kset = &kset_p;  
  49.         kset_register(&kset_c);  
  50.         return 0;  
  51. }  
  52. int kset_test_exit()  
  53. {  
  54.         printk("kset test exit.\n");  
  55.         kset_unregister(&kset_p);  
  56.         kset_unregister(&kset_c);  
  57.         return 0;  
  58. }  
  59. module_init(kset_test_init);  
  60. module_exit(kset_test_exit);  

測試結果:


可以看出當kset載入時,在/sys下建立了一個kset_p,在kset_p下面建立了kset_c,當kset模組被載入和解除安裝時都產生了熱插拔事件。

參考書籍:

       《linux那些事兒之我是sysfs》

       《linux裝置驅動程式(第三版)》

       《國嵌課件》

PS:

請關注下一篇《Linux裝置驅動工程師之路——裝置模型(下)上層模型》

寫這玩意兒把我累壞了,呵呵。