分析過mdev(udev的BusyBox簡化版)源碼的都知道mdev的基本原理: a、執行mdev -s命令時,mdev掃描/sys/block(塊裝置儲存在/sys/block目錄下,核心2.6.25版本以後,塊裝置也儲存在/sys/class/block目錄下。mdev掃描/sys/block是為了實現向後相容)和/sys/class兩個目錄下的dev屬性檔案,從該dev屬性檔案中擷取到裝置編號(dev屬性檔案以"major:minor\n"形式儲存裝置編號),並以包含該dev屬性檔案的目錄名稱作為裝置名稱device_name(即包含dev屬性檔案的目錄稱為device_name,而/sys/class和device_name之間的那部分目錄稱為subsystem。也就是每個dev屬性檔案所在的路徑都可表示為/sys/class/subsystem/device_name/dev),在/dev目錄下建立相應的裝置檔案。例如,cat /sys/class/tty/tty0/dev會得到4:0,subsystem為tty,device_name為tty0。 b、當mdev因uevnet事件(以前叫hotplug事件)被調用時,mdev通過由uevent事件傳遞給它的環境變數擷取到:引起該uevent事件的裝置action及該裝置所在的路徑device path。然後判斷引起該uevent事件的action是什麼。若該action是add,即有新裝置加入到系統中,不管該裝置是虛擬設備還是實際物理裝置,mdev都會通過device path路徑下的dev屬性檔案擷取到裝置編號,然後以device path路徑最後一個目錄(即包含該dev屬性檔案的目錄)作為裝置名稱,在/dev目錄下建立相應的裝置檔案。若該action是remote,即裝置已從系統中移除,則刪除/dev目錄下以device path路徑最後一個目錄名稱作為檔案名稱的裝置檔案。如果該action既不是add也不是remove,mdev則什麼都不做。 由上面可知,如果我們想在裝置加入到系統中或從系統中移除時,由mdev自動地建立和刪除裝置檔案,那麼就必須做到以下三點:1、在/sys/class的某一subsystem目錄下,2、建立一個以裝置名稱device_name作為名稱的目錄,3、並且在該device_name目錄下還必須包含一個dev屬性檔案,該dev屬性檔案以"major:minor\n"形式輸出裝置編號。 思路有了,剩下的就是如何?了。下面通過介紹linux裝置驅動模型裡兩個重要的資料結構:class和class_device,順帶講述如何架構mdev驅動程式,並給出2個例子:HelloWorld和udev_demo。 注意:下面講到的結構體、函數以及例子都是針對linux-2.6.24.7版本核心,並不適合於所有版本的linux核心。各版本核心的結構體和函數原型可能有變。比如我在Ubuntu9.04下make一下第二個驅動例子,出現了好幾個錯誤,後來發現Ubuntu9.04的核心linux-2.6.28裡面關於linux裝置驅動模型的資料結構有了很大改變,沒有了class_device。所以當你跑下面兩個例子時,如不是linux-2.6.24.7版本核心,那就要注意一下例子裡使用資料結構與你核心裡的資料結構是否一致。 1、class 一個類是一個裝置的高層視圖,它抽象掉了底層的實現細節。例如,在驅動層面時,你可能會見到SCSI磁碟或者ATA磁碟;但在類層面時,它們都是磁碟。類允許使用者空間基於它們做什麼來使用裝置,而不是它們如何被串連或者它們如何工作。 class表示一類裝置,所有class都屬於class_subsys(class子系統),即出現在/sys/class目錄下,除了塊裝置(可能出現在/sys/block/或/sys/class/block,上面講過了)。 其實,class在/sys/class下產生的目錄也就是上面提到subsystem。這樣第1點就有了。 /* class結構體 */ structclass{ constchar * name; /* class的名稱 */ struct module * owner; /* 擁有該class的模組 */ struct kset subsys; /* 該class對應的子系統 */ struct list_head children; /* 該class的class_device列表 */ struct list_head devices; struct list_head interfaces; struct kset class_dirs; struct semaphore sem; /* locks both the children and interfaces lists */ struct class_attribute * class_attrs;/* 該class的預設屬性,以NULL結尾 */ struct class_device_attribute * class_dev_attrs;/* 添加到class的class_device所擁有的預設屬性 */ struct device_attribute * dev_attrs; /* 該函數提供在產生熱插拔class_device事件時,添加環境變數的能力 */ int (*uevent)(struct class_device *dev,struct kobj_uevent_env *env); /* 該函數提供在產生熱插拔device(物理裝置)事件時,添加環境變數的能力 */ int (*dev_uevent)(struct device *dev,struct kobj_uevent_env *env); /* 添加到class的class_device移除時,調用該函數進行必要的清理工作 */ void(*release)(struct class_device *dev); /* class被移除時,調用該函數進行必要的清理工作 */ void(*class_release)(structclass*class); void(*dev_release)(struct device *dev); int (*suspend)(struct device *, pm_message_t state); int (*resume)(struct device *); }; /* class註冊函數 */ int __must_check class_register(structclass*); void class_unregister(structclass*); 建立一個class有兩種方法 a、根據需要,填充一個struct class,然後再調用class_register註冊該class就ok了(此法比較靈活,可以自己定製很多東西) b、就是通過下面的class_create來建立一個class,該函數會返回一個指向剛建立的class的指標(建立class的最簡單方法) /* class_create用於建立一個名為name的class,其owner參數一般為THIS_MODULE。class_create內部調用了class_register */ structclass*class_create(struct module *owner,constchar*name); /* class_destroy用於刪除一個class,實際上其內部只是簡單調用了class_unregister(cls)來登出cls */ void class_destroy(structclass*cls); 一個class屬性對應於/sys/class/class.name(class.name就是該class的名稱)目錄裡的一個檔案。通過這些檔案,可以向使用者空間輸出一些關於該class的資訊,也可從使用者空間擷取到一些資訊。 /* class屬性結構體 */ struct class_attribute { struct attribute attr; /* 當使用者空間讀取該屬性時,調用show函數輸出一個"屬性值"給使用者空間 */ ssize_t (*show)(structclass*,char* buf); /* 當使用者空間寫該屬性時,調用store函數儲存使用者寫入的"屬性值" */ ssize_t (*store)(structclass*,constchar* buf,size_tcount); }; struct attribute { constchar * name; struct module * owner; mode_t mode; }; /* CLASS_ATTR可以在編譯時間建立一個class屬性,該屬性的名稱為class_attr_name */ #define CLASS_ATTR(_name,_mode,_show,_store) \ struct class_attribute class_attr_##_name = __ATTR(_name,_mode,_show,_store) /* class_create_file與class_remove_file用於建立與刪除class預設屬性外的屬性 */ int __must_check class_create_file(structclass*, conststruct class_attribute *); void class_remove_file(structclass*,conststruct class_attribute *); 2、class_device 一個class可以看成是一個容器(一個子系統subsystem),包含了很多的class_device,這些class_device是由class這個大的容器來管理的,而每個class_device都對應著一個具體的裝置。 每個class對象包括一個class_device鏈表,每個class_device對象表示一個邏輯裝置並通過struct class_device中的dev成員(一個指向struct device的指標)關聯一個物理裝置。一個邏輯裝置總是對應一個物理裝置,而一個物理裝置卻可以對應多個邏輯裝置。 實際上,class_device在/sys/class/subsystem產生的目錄就是上面提到的class_device。這樣第2點也有了。 /* class_device結構體 */ struct class_device { struct list_head node; /* 僅供驅動核心內部使用 */ struct kobject kobj; /* 該class_device相應的kobject,僅供驅動核心內部使用 */ structclass *class; /* 該class_device所屬的class,必須有 */ dev_t devt; /* 該class_device的裝置編號,用於建立其dev屬性檔案,僅供驅動核心內部使用 */ struct device * dev; /* 指向該class_device相關的device結構體(物理裝置),可選.若不為NULL,用於建立一個從class入口到/sys/devices下相應入口的符號串連,以便使用者空間尋找裝置入口 */ void * class_data; /* 該class_device的私人資料指標 */ struct class_device *parent; /* parent of this child device, if there is one */ struct attribute_group ** groups; /* optional groups */ void (*release)(struct class_device *dev); int (*uevent)(struct class_device *dev,struct kobj_uevent_env *env); char class_id[BUS_ID_SIZE]; /* 該class_device的名稱,在其所屬class中應是唯一的,不可重名 */ }; /* class_devic註冊函數 */ int __must_check class_device_register(struct class_device *); void class_device_unregister(struct class_device *); 與class一樣,建立一個class_device也有兩種方法 a、根據需要,填充一個struct class_device,然後再調用class_device_register註冊該class_device就ok了(此法比較靈活,可以自己定製很多東西) b、就是通過下面的class_device_create來建立一個class_device,該函數會返回一個指向剛建立的class_device的指標(建立class_device的最簡單方法) /* class_device_create用於建立一個class_device,其名稱最後兩個參數決定(類似於printf的格式化字串) * cls指明了其所屬的class,可以是自己填充的class或由class_create返回的class * parent指明了該class_device的父class_device,若沒有,則為NULL * 該class_device的裝置編號,用於建立其dev屬性檔案,必須指明 * device指明了該class_device(邏輯裝置)對應的device(物理裝置),可有可無,無則為NULL * 實際上,class_device_create也就是填充一個class_device,然後調用了class_device_register註冊該class_device */ struct class_device *class_device_create(structclass*cls, struct class_device *parent, dev_t devt, struct device *device, constchar*fmt,...) __attribute__((format(printf,5,6))); /* class_destroy用於刪除一個class,內部調用了class_unregister(cls)來登出cls */ void class_device_destroy(structclass*cls, dev_t devt); class_device屬性對應於/sys/class/class.name/class_device.class_id目錄下一個檔案。通過這些檔案,可以向使用者空間輸出一些關於該class_device的資訊,也可從使用者空間擷取到一些資訊。 /* class_device屬性,其show和store函數類似於class屬性的show和store函數 */ struct class_device_attribute { struct attribute attr; ssize_t (*show)(struct class_device *,char* buf); ssize_t (*store)(struct class_device *,constchar* buf,size_tcount); }; /* CLASS_DEVICE_ATTR可以在編譯時間建立一個class_device屬性,該屬性的名稱為class_device_attr_name */ #define CLASS_DEVICE_ATTR(_name,_mode,_show,_store) \ struct class_device_attribute class_device_attr_##_name = \ __ATTR(_name,_mode,_show,_store) /* class_device_create_file與class_device_remove_file用於建立與刪除class_device預設屬性外的屬性 */ int __must_check class_device_create_file(struct class_device *, conststruct class_device_attribute *); void class_device_remove_file(struct class_device * class_dev, conststruct class_device_attribute * attr); 其實,在調用class_device_register註冊一個class_device時,該函數內部調用了 int class_device_add(struct class_device *class_dev) 在class_device_add內,通過class_device_create_file建立dev、uevent和該class_device所擁有的預設屬性(由class_device.class->class_dev_attrs指定)等屬性檔案。這樣第3點也有了。 dev屬性檔案用於向使用者空間輸出該class_device的裝置編號。 uevent屬性檔案使使用者可以手動觸發uevent事件(通過向該檔案寫,如add、remove等字串)。 接下來是兩個基於mdev的驅動例子。 struct class_interface { struct list_head node; struct class *class; /* 該class_interface所屬的class */ int (*add) (struct class_device *, struct class_interface *); /* class屬性 */ void (*remove) (struct class_device *, struct class_interface *); /* class屬性 */ int (*add_dev) (struct device *, struct class_interface *); void (*remove_dev) (struct device *, struct class_interface *); }; /* class_interface註冊函數 */ int __must_check class_interface_register(struct class_interface *); void class_interface_unregister(struct class_interface *); |