Linux設(shè)備模型是對系統(tǒng)設(shè)備組織架構(gòu)進行抽象的一個數(shù)據(jù)結(jié)構(gòu),旨在為設(shè)備驅(qū)動進行分層、分類、組織。降低設(shè)備多樣性帶來的Linux驅(qū)動開發(fā)的復(fù)雜度,以及設(shè)備熱拔插處理、電源管理等。
Overview
設(shè)計目的
電源管理和系統(tǒng)關(guān)機(Power management and system shutdown)
設(shè)備之間大多情況下有依賴、耦合,因此要實現(xiàn)電源管理就必須對系統(tǒng)的設(shè)備結(jié)構(gòu)有清楚的理解,應(yīng)知道先關(guān)哪個然后才能再關(guān)哪個。設(shè)計設(shè)備模型就是為了使系統(tǒng)可以按照正確順序進行硬件的遍歷。
與用戶空間的交互(Communications with user space)
實現(xiàn)了sysfs虛擬文件系統(tǒng)。它可以將設(shè)備模型中定義的設(shè)備屬性信息等導(dǎo)出到用戶空間,使得在用戶空間可以實現(xiàn)對設(shè)備屬性的訪問及參數(shù)的更改。詳見Documentation/filesystems/sysfs.txt。
可熱插拔設(shè)備(Hotpluggable devices)
設(shè)備模型管理內(nèi)核所使用的處理用戶空間熱插拔的機制,支持設(shè)備的動態(tài)添加與移除。
設(shè)備類別(Device classes)
系統(tǒng)的許多部分對設(shè)備如何連接沒有興趣, 但是它們需要知道什么類型的設(shè)備可用。設(shè)備模型也實現(xiàn)了一個給設(shè)備分類的機制, 它在一個更高的功能性級別描述了這些設(shè)備。
對象生命期(Object lifecycles)
設(shè)備模型的實現(xiàn)一套機制來處理對象生命期。
設(shè)備模型框圖
Linux 設(shè)備模型是一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。如圖所示為和USB鼠標(biāo)相關(guān)聯(lián)的設(shè)備模型的一小部分:
這個框圖展示了設(shè)備模型最重要的四個部分的組織關(guān)系(在頂層容器中詳解):
Devices
描述了設(shè)備如何連接到系統(tǒng)。
Drivers
系統(tǒng)中可用的驅(qū)動。
Buses
跟蹤什么連接到每個總線,負(fù)責(zé)匹配設(shè)備與驅(qū)動。
classes
設(shè)備底層細(xì)節(jié)的抽象,描述了設(shè)備所提供的功能。
底層實現(xiàn)
kobject
作用與目的
Kobject是將整個設(shè)備模型連接在一起的基礎(chǔ)。主要用來實現(xiàn)以下功能:
對象的引用計數(shù)(Reference counting of objects)
通常, 當(dāng)一個內(nèi)核對象被創(chuàng)建, 沒有方法知道它會存在多長時間。 一種跟蹤這種對象生命周期的方法是通過引用計數(shù)。 當(dāng)沒有內(nèi)核代碼持有對給定對象的引用, 那個對象已經(jīng)完成了它的有用壽命并且可以被刪除。
sysfs 表示(Sysfs representation)
在sysfs中顯示的每一個項目都是通過一個與內(nèi)核交互的kobject實現(xiàn)的。
數(shù)據(jù)結(jié)構(gòu)粘和(Data structure glue)
設(shè)備模型整體來看是一個極端復(fù)雜的由多級組成的數(shù)據(jù)結(jié)構(gòu), kobject實現(xiàn)各級之間的連接粘和。
熱插拔事件處理(Hotplug event handling)
kobject處理熱插拔事件并通知用戶空間。
數(shù)據(jù)結(jié)構(gòu)
/* include in */struct kobject { const char *name; /* 該kobject的名稱,同時也是sysfs中的目錄名稱 */ struct list_head entry; /* kobjetct雙向鏈表 */ struct kobject *parent; /* 指向kset中的kobject,相當(dāng)于指向父目錄 */ struct kset *kset; /*指向所屬的kset*/ struct kobj_type *ktype; /*負(fù)責(zé)對kobject結(jié)構(gòu)跟蹤*/ ...};/* 定義kobject的類型及釋放回調(diào) */struct kobj_type { void (*release)(struct kobject *); /* kobject釋放函數(shù)指針 */ struct sysfs_ops *sysfs_ops; /* 默認(rèn)屬性操作方法 */ struct attribute **default_attrs; /* 默認(rèn)屬性 */};/* kobject上層容器 */struct kset { struct list_head list; /* 用于連接kset中所有kobject的鏈表頭 */ spinlock_t list_lock; /* 掃描kobject組成的鏈表時使用的鎖 */ struct kobject kobj; /* 嵌入的kobject */ const struct kset_uevent_ops *uevent_ops; /* kset的uevent操作 */};/* 包含kset的更高級抽象 */struct subsystem { struct kset kset; /* 定義一個kset */ struct rw_semaphore rwsem; /* 用于串行訪問kset內(nèi)部鏈表的讀寫信號量 */};
kobject和kset關(guān)系:
如圖所示,kset將它的children(kobjects)組成一個標(biāo)準(zhǔn)的內(nèi)核鏈表。所以說kset是一個包含嵌入在同種類型結(jié)構(gòu)中的kobject的集合。它自身也內(nèi)嵌一個kobject,所以也是一個特殊的kobject。設(shè)計kset的主要目的是容納,可以說是kobject的頂層容器。kset總是會在sysfs中以目錄的形式呈現(xiàn)。需要注意的是圖中所示的kobject其實是嵌入在其他類型中(很少單獨使用),也可能是其他kset中。
kset和subsystem關(guān)系:
一個子系統(tǒng)subsystem, 其實只是一個附加了個讀寫信號量的kset的包裝,反過來就是說每個 kset 必須屬于一個子系統(tǒng)。根據(jù)subsystem之間的成員關(guān)系建立kset在整個層級中的位置。
子系統(tǒng)常常使用宏直接靜態(tài)定義:
/* 定義一個struct subsystem name_subsys 并初始化kset的type及hotplug_ops */ decl_subsys(name, struct kobj_type *type,struct kset_hotplug_ops *hotplug_ops);
操作函數(shù)
初始化
/* 初始化kobject內(nèi)部結(jié)構(gòu) */void kobject_init(struct kobject *kobj);/* 設(shè)置name */int kobject_set_name(struct kobject *kobj, const char *format, ...);/* 先將kobj->kset指向要添加的kset中,然后調(diào)用會將kobject加入到指定的kset中 */int kobject_add(struct kobject *kobj);/* kobject_register = kobject_init + kobject_add */extern int kobject_register(struct kobject *kobj);/* 對應(yīng)的Kobject刪除函數(shù) */void kobject_del(struct kobject *kobj);void kobject_unregister(struct kobject *kobj);/* 與kobject類似的kset操作函數(shù) */void kset_init(struct kset *kset);kobject_set_name(&my_set->kobj, "The name");int kset_add(struct kset *kset);int kset_register(struct kset *kset);void kset_unregister(struct kset *kset);
Tip: 初始化前應(yīng)先使用memset將kobj清零;初始化完成后引用計數(shù)為1
引用計數(shù)管理
/* 引用計數(shù)加1并返回指向kobject的指針 */struct kobject *kobject_get(struct kobject *kobj);/* 當(dāng)一個引用被釋放, 調(diào)用kobject_put遞減引用計數(shù),當(dāng)引用為0時free這個object */void kobject_put(struct kobject *kobj);/* 與kobject類似的kset操作函數(shù) */struct kset *kset_get(struct kset *kset);void kset_put(struct kset *kset);
釋放
當(dāng)引用計數(shù)為0時,會調(diào)用ktype中的release,因此可以這樣定義release回調(diào)函數(shù):void my_object_release(struct kobject *kobj){ struct my_object *mine = container_of(kobj, struct my_object, kobj); /* Perform any additional cleanup on this object, then... */ kfree(mine);}/* 查找ktype */struct kobj_type *get_ktype(struct kobject *kobj);
subsystem相關(guān)
decl_subsys(name, type, hotplug_ops);void subsystem_init(struct subsystem *subsys);int subsystem_register(struct subsystem *subsys);void subsystem_unregister(struct subsystem *subsys);struct subsystem *subsys_get(struct subsystem *subsys);void subsys_put(struct subsystem *subsys);
Low-Level Sysfs Operations
kobject和sysfs關(guān)系
kobject是實現(xiàn)sysfs虛擬文件系統(tǒng)背后的機制。sysfs中的每一個目錄都對應(yīng)內(nèi)核中的一個kobject。將kobject的屬性(atrributes)導(dǎo)出就會在sysfs對應(yīng)的目錄下產(chǎn)生由內(nèi)核自動生成的包含這些屬性信息的文件。只需簡單的調(diào)用前面所提到的kobject_add就會在sysfs中生成一個對應(yīng)kobject的入口,但值得注意的是:
這個入口總會以目錄呈現(xiàn), 也就是說生成一個入口就是創(chuàng)建一個目錄。通常這個目錄會包含一個或多個屬性文件(見下文)。
分配給kobject的名字(用kobject_set_name)就是給 sysfs 目錄使用的名字,因此在sysfs層級中相同部分的kobject命名必須唯一,不能包含下劃線,避免使用空格。
這個入口所處的目錄表示kobject的parent指針,如果parent為NULL,則指向的是它的kset,因此可以說sysfs的層級其實對應(yīng)的就是kset的層級。但當(dāng)kset也為NULL時,這個入口就會創(chuàng)建在sysfs的top level,不過實際中很少出現(xiàn)這種情況。
屬性(atrributes)
屬性即為上面所提到的一旦導(dǎo)出就會由內(nèi)核自動生成的包含kobject內(nèi)核信息的文件。結(jié)構(gòu)如下:
struct attribute { char *name; /* 屬性名,也是sysfs對應(yīng)entry下的文件名 */ struct module *owner; /* 指向負(fù)責(zé)實現(xiàn)這個屬性的模塊 */ mode_t mode; /* 權(quán)限位,在中定義 */};
屬性的導(dǎo)出顯示及導(dǎo)入存儲函數(shù):
/* kobj: 需要處理的kobject attr: 需要處理的屬性 buffer: 存儲編碼后的屬性信息,大小為PAGE_SIZE return: 實際編碼的屬性信息長度 */struct sysfs_ops { ssize_t (*show)(struct kobject *kobj, struct attribute *attr,char *buffer); /* 導(dǎo)出到用戶空間 */ ssize_t (*store)(struct kobject *kobj, struct attribute *attr,const char *buffer, size_t size); /* 存儲進內(nèi)核空間 */};
需要注意的是:
每個屬性都是用name=value表示,name即使屬性的文件名,value即文件內(nèi)容,如果value超過PAGE_SIZE,則應(yīng)分為多個屬性來處理;
上述函數(shù)可以處理不同的屬性??梢栽趦?nèi)部實現(xiàn)時同過屬性名進行區(qū)分來實現(xiàn);
由于store是從用戶空間到內(nèi)核,所以實現(xiàn)時首先要檢查參數(shù)的合法行,以免內(nèi)核崩潰及其他問題。
缺省屬性(Default Attributes)
在kobject創(chuàng)建時都會賦予一些缺省的默認(rèn)屬性,即上面所提到的kobj_type中的default_attrs數(shù)組,這個數(shù)組的最后一個成員須設(shè)置成NULL,以表示數(shù)組大小。所有使用這個kobj_type的kobject都是通過kobj_type中的sfsfs_ops回調(diào)函數(shù)入口實現(xiàn)對缺省屬性的定義。
非缺省屬性(Nondefault Attributes)
一般來說,定義時就可以通過default_attrs完成所有的屬性,但這里也提供了后續(xù)動態(tài)添加和刪除屬性的方法:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr); int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);
二進制屬性(Binary Attributes)
上述屬性包含的可讀的文本值,二進制屬性很少使用,大多用在從用戶空間傳遞一些不改動的文件如firmware給設(shè)備的情況下。
struct bin_attribute { struct attribute attr; /* 定義name,owner,mode */ size_t size; /* 屬性最大長度,如沒有最大長度則設(shè)為0 */ ssize_t (*read)(struct kobject *kobj, char *buffer,loff_t pos, size_t size); ssize_t (*write)(struct kobject *kobj, char *buffer,loff_t pos, size_t size); };
read/write一次加載多次調(diào)用,每次最多PAGE_SIZE大小。注意write無法指示最后一個寫操作,得通過其他方式判斷操作的結(jié)束。
二進制屬性不能定義為缺省值,因此需明確的創(chuàng)建與刪除:
int sysfs_create_bin_file(struct kobject *kobj,struct bin_attribute *attr); int sysfs_remove_bin_file(struct kobject *kobj,struct bin_attribute *attr);
符號連接(Symbolic Links)
方法:
int sysfs_create_link(struct kobject *kobj, struct kobject *target,char *name); void sysfs_remove_link(struct kobject *kobj, char *name);
熱插拔事件生成(Hotplug Event Generation)
熱插拔事件即當(dāng)系統(tǒng)配置發(fā)生改變是內(nèi)核向用戶空間的通知。然后用戶空間會調(diào)用/sbin/hotplug通過創(chuàng)建節(jié)點、加載驅(qū)動等動作進行響應(yīng)。這個熱插拔事件的產(chǎn)生是在kobject_add和kobject_del時。我們可以通過上面kset中定義的uevent_ops對熱插拔事件產(chǎn)生進行配置:
struct kset_uevent_ops { /* 實現(xiàn)事件的過濾,其返回值為0時不產(chǎn)生事件 */ int (* const filter)(struct kset *kset, struct kobject *kobj); /* 生成傳遞給/sbin/hotplug的name參數(shù) */ const char *(* const name)(struct kset *kset, struct kobject *kobj); /* 其他傳遞給/sbin/hotplug的參數(shù)通過這種設(shè)置環(huán)境變量的方式傳遞 */ int (* const uevent)(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);};
頂層容器
Buses, Devices, Drivers and Classes
Buses
總線Buses是處理器和設(shè)備的通道。在設(shè)備模型中,所有設(shè)備都是通過總線連接在一起的,哪怕是一個內(nèi)部虛擬的platform總線。
/* defined in */struct bus_type { const char *name; /* 總線類型名 */ struct bus_attribute *bus_attrs; /* 總線的屬性 */ struct device_attribute *dev_attrs; /* 設(shè)備屬性,為每個加入總線的設(shè)備建立屬性鏈表 */ struct driver_attribute *drv_attrs; /* 驅(qū)動屬性,為每個加入總線的驅(qū)動建立屬性鏈表 */ /* 驅(qū)動與設(shè)備匹配函數(shù):當(dāng)一個新設(shè)備或者驅(qū)動被添加到這個總線時, 這個方法會被調(diào)用一次或多次,若指定的驅(qū)動程序能夠處理指定的設(shè)備,則返回非零值。 必須在總線層使用這個函數(shù), 因為那里存在正確的邏輯,核心內(nèi)核不知道如何為每個總線類型匹配設(shè)備和驅(qū)動程序 */ int (*match)(struct device *dev, struct device_driver *drv); /*在為用戶空間產(chǎn)生熱插拔事件之前,這個方法允許總線添加環(huán)境變量(參數(shù)和 kset 的uevent方法相同)*/ int (*uevent)(struct device *dev, struct kobj_uevent_env *env); ... struct subsys_private *p; /* 一個很重要的域,包含了device鏈表和drivers鏈表 */}/* 定義bus_attrs的快捷方式 */BUS_ATTR(name, mode, show, store);/* bus屬性文件的創(chuàng)建移除 */int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);/* 總線注冊 */int bus_register(struct bus_type *bus);void bus_unregister(struct bus_type *bus);/* 遍歷總線上的設(shè)備與驅(qū)動 */int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int(*fn)(struct device *, void *));int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int(*fn)(struct device_driver *, void *));
Devices
Linux中,每一個底層設(shè)備都是structure device的一個實例:
struct device { struct device *parent; /* 父設(shè)備,總線設(shè)備指定為NULL */ struct device_private *p; /* 包含設(shè)備鏈表,driver_data(驅(qū)動程序要使用數(shù)據(jù))等信息 */ struct kobject kobj; const char *init_name; /* 初始默認(rèn)的設(shè)備名 */ struct bus_type *bus; /* type of bus device is on */ struct device_driver *driver; /* which driver has allocated this device */ ... void (*release)(struct device *dev); };int device_register(struct device *dev);void device_unregister(struct device *dev);DEVICE_ATTR(name, mode, show, store);int device_create_file(struct device *device,struct device_attribute *entry);void device_remove_file(struct device *dev,struct device_attribute *attr);
Drivers
設(shè)備模型跟蹤所有系統(tǒng)已知的驅(qū)動。
struct device_driver { const char *name; /* 驅(qū)動名稱,在sysfs中以文件夾名出現(xiàn) */ struct bus_type *bus; /* 驅(qū)動關(guān)聯(lián)的總線類型 */ int (*probe) (struct device *dev); /* 查詢設(shè)備的存在 */ int (*remove) (struct device *dev); /* 設(shè)備移除回調(diào) */ void (*shutdown) (struct device *dev); ...}int driver_register(struct device_driver *drv);void driver_unregister(struct device_driver *drv);DRIVER_ATTR(name, mode, show, store);int driver_create_file(struct device_driver *drv,struct driver_attribute *attr);void driver_remove_file(struct device_driver *drv,struct driver_attribute *attr);
Classes
類是設(shè)備的一個高級視圖,實現(xiàn)了底層細(xì)節(jié)。通過對設(shè)備進行分類,同類代碼可共享,減少了內(nèi)核代碼的冗余。
struct class { const char *name; /* class的名稱,會在“/sys/class/”目錄下體現(xiàn) */ struct class_attribute *class_attrs; struct device_attribute *dev_attrs; /* 該class下每個設(shè)備的attribute */ struct kobject *dev_kobj; /* 當(dāng)該class下有設(shè)備發(fā)生變化時,會調(diào)用class的uevent回調(diào)函數(shù) */ int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env); char *(*devnode)(struct device *dev, mode_t *mode); void (*class_release)(struct class *class); void (*dev_release)(struct device *dev); int (*suspend)(struct device *dev, pm_message_t state); int (*resume)(struct device *dev); struct class_private *p;};int class_register(struct class *cls);void class_unregister(struct class *cls);CLASS_ATTR(name, mode, show, store);int class_create_file(struct class *cls,const struct class_attribute *attr);void class_remove_file(struct class *cls,const struct class_attribute *attr);
Putting It All Together
?
評論