2021-05-12 14:32:11
Linux裝置驅動之裝置模型
Linux裝置模型是對系統裝置組織架構進行抽象的一個資料結構,旨在為裝置驅動進行分層、分類、組織。降低裝置多樣性帶來的Linux驅動開發的複雜度,以及裝置熱拔插處理、電源管理等。
Overview
設計目的
-
電源管理和系統關機(Power management and system shutdown)
裝置之間大多情況下有依賴、耦合,因此要實現電源管理就必須對系統的裝置結構有清楚的理解,應知道先關哪個然後才能再關哪個。設計裝置模型就是為了使系統可以按照正確順序進行硬體的遍歷。
-
與使用者空間的互動(Communications with user space)
實現了sysfs虛擬檔案系統。它可以將裝置模型中定義的裝置屬性資訊等匯出到使用者空間,使得在使用者空間可以實現對裝置屬性的存取及引數的更改。詳見Documentation/filesystems/sysfs.txt。
-
可熱插拔裝置(Hotpluggable devices)
裝置模型管理核心所使用的處理使用者空間熱插拔的機制,支援裝置的動態新增與移除。
-
裝置類別(Device classes)
系統的許多部分對裝置如何連線沒有興趣, 但是它們需要知道什麼型別的裝置可用。裝置模型也實現了一個給裝置分類的機制, 它在一個更高的功能性級別描述了這些裝置。
-
物件生命期(Object lifecycles)
裝置模型的實現一套機制來處理物件生命期。
裝置模型框圖
Linux 裝置模型是一個複雜的資料結構。如圖所示為和USB滑鼠相關聯的裝置模型的一小部分:
這個框圖展示了裝置模型最重要的四個部分的組織關係(在頂層容器中詳解):
-
Devices
描述了裝置如何連線到系統。
-
Drivers
系統中可用的驅動。
-
Buses
跟蹤什麼連線到每個匯流排,負責匹配裝置與驅動。
-
classes
裝置底層細節的抽象,描述了裝置所提供的功能。
底層實現
kobject
作用與目的
Kobject是將整個裝置模型連線在一起的基礎。主要用來實現以下功能:
-
物件的參照計數(Reference counting of objects)
通常, 當一個核心物件被建立, 沒有方法知道它會存在多長時間。 一種跟蹤這種物件生命週期的方法是通過參照計數。 當沒有核心程式碼持有對給定物件的參照, 那個物件已經完成了它的有用壽命並且可以被刪除。
-
sysfs 表示(Sysfs representation)
在sysfs中顯示的每一個專案都是通過一個與核心互動的kobject實現的。
-
資料結構黏和(Data structure glue)
裝置模型整體來看是一個極端複雜的由多級組成的資料結構, kobject實現各級之間的連線黏和。
-
熱插拔事件處理(Hotplug event handling)
kobject處理熱插拔事件並通知使用者空間。
資料結構
/* include in <linux/kobject.h> */
struct kobject {
const char *name; /* 該kobject的名稱,同時也是sysfs中的目錄名稱 */
struct list_head entry; /* kobjetct雙向連結串列 */
struct kobject *parent; /* 指向kset中的kobject,相當於指向父目錄 */
struct kset *kset; /*指向所屬的kset*/
struct kobj_type *ktype; /*負責對kobject結構跟蹤*/
...
};
/* 定義kobject的型別及釋放回撥 */
struct kobj_type {
void (*release)(struct kobject *); /* kobject釋放函數指標 */
struct sysfs_ops *sysfs_ops; /* 預設屬性操作方法 */
struct attribute **default_attrs; /* 預設屬性 */
};
/* 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內部連結串列的讀寫號誌 */
};
kobject和kset關係:
如圖所示,kset將它的children(kobjects)組成一個標準的核心連結串列。所以說kset是一個包含嵌入在同種型別結構中的kobject的集合。它自身也內嵌一個kobject,所以也是一個特殊的kobject。設計kset的主要目的是容納,可以說是kobject的頂層容器。kset總是會在sysfs中以目錄的形式呈現。需要注意的是圖中所示的kobject其實是嵌入在其他型別中(很少單獨使用),也可能是其他kset中。
kset和subsystem關係:
一個子系統subsystem, 其實只是一個附加了個讀寫號誌的kset的包裝,反過來就是說每個 kset 必須屬於一個子系統。根據subsystem之間的成員關係建立kset在整個層級中的位置。
子系統常常使用宏直接靜態定義:
/* 定義一個struct subsystem name_subsys 並初始化kset的type及hotplug_ops */
decl_subsys(name, struct kobj_type *type,struct kset_hotplug_ops *hotplug_ops);
操作函數
- 初始化
/* 初始化kobject內部結構 */
void kobject_init(struct kobject *kobj);
/* 設定name */
int kobject_set_name(struct kobject *kobj, const char *format, ...);
/* 先將kobj->kset指向要新增的kset中,然後呼叫會將kobject加入到指定的kset中 */
int kobject_add(struct kobject *kobj);
/* kobject_register = kobject_init + kobject_add */
extern int kobject_register(struct kobject *kobj);
/* 對應的Kobject刪除函數 */
void kobject_del(struct kobject *kobj);
void kobject_unregister(struct kobject *kobj);
/* 與kobject類似的kset操作函數 */
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: 初始化前應先使用memset將kobj清零;初始化完成後參照計數為1
- 參照計數管理
/* 參照計數加1並返回指向kobject的指標 */
struct kobject *kobject_get(struct kobject *kobj);
/* 當一個參照被釋放, 呼叫kobject_put遞減參照計數,當參照為0時free這個object */
void kobject_put(struct kobject *kobj);
/* 與kobject類似的kset操作函數 */
struct kset *kset_get(struct kset *kset);
void kset_put(struct kset *kset);
- 釋放
當參照計數為0時,會呼叫ktype中的release,因此可以這樣定義release回撥函數:
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相關
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關係
kobject是實現sysfs虛擬檔案系統背後的機制。sysfs中的每一個目錄都對應核心中的一個kobject。將kobject的屬性(atrributes)匯出就會在sysfs對應的目錄下產生由核心自動生成的包含這些屬性資訊的檔案。只需簡單的呼叫前面所提到的kobject_add就會在sysfs中生成一個對應kobject的入口,但值得注意的是:
- 這個入口總會以目錄呈現, 也就是說生成一個入口就是建立一個目錄。通常這個目錄會包含一個或多個屬性檔案(見下文)。
- 分配給kobject的名字(用kobject_set_name)就是給 sysfs 目錄使用的名字,因此在sysfs層級中相同部分的kobject命名必須唯一,不能包含下劃線,避免使用空格。
- 這個入口所處的目錄表示kobject的parent指標,如果parent為NULL,則指向的是它的kset,因此可以說sysfs的層級其實對應的就是kset的層級。但當kset也為NULL時,這個入口就會建立在sysfs的top level,不過實際中很少出現這種情況。
屬性(atrributes)
屬性即為上面所提到的一旦匯出就會由核心自動生成的包含kobject核心資訊的檔案。結構如下:
struct attribute {
char *name; /* 屬性名,也是sysfs對應entry下的檔名 */
struct module *owner; /* 指向負責實現這個屬性的模組 */
mode_t mode; /* 許可權位,在<linux/stat.h>中定義 */
};
屬性的匯出顯示及匯入儲存函數:
/* kobj: 需要處理的kobject
attr: 需要處理的屬性
buffer: 儲存編碼後的屬性資訊,大小為PAGE_SIZE
return: 實際編碼的屬性資訊長度
*/
struct sysfs_ops {
ssize_t (*show)(struct kobject *kobj, struct attribute *attr,char *buffer); /* 匯出到使用者空間 */
ssize_t (*store)(struct kobject *kobj, struct attribute *attr,const char *buffer, size_t size); /* 儲存進核心空間 */
};
需要注意的是:
- 每個屬性都是用name=value表示,name即使屬性的檔名,value即檔案內容,如果value超過PAGE_SIZE,則應分為多個屬性來處理;
- 上述函數可以處理不同的屬性。可以在內部實現時同過屬性名進行區分來實現;
- 由於store是從使用者空間到核心,所以實現時首先要檢查引數的合法行,以免核心崩潰及其他問題。
預設屬性(Default Attributes)
在kobject建立時都會賦予一些預設的預設屬性,即上面所提到的kobj_type中的default_attrs陣列,這個陣列的最後一個成員須設定成NULL,以表示陣列大小。所有使用這個kobj_type的kobject都是通過kobj_type中的sfsfs_ops回撥函數入口實現對預設屬性的定義。
非預設屬性(Nondefault Attributes)
一般來說,定義時就可以通過default_attrs完成所有的屬性,但這裡也提供了後續動態新增和刪除屬性的方法:
int sysfs_create_file(struct kobject *kobj, struct attribute *attr);
int sysfs_remove_file(struct kobject *kobj, struct attribute *attr);
二進位制屬性(Binary Attributes)
上述屬性包含的可讀的文字值,二進位制屬性很少使用,大多用在從使用者空間傳遞一些不改動的檔案如firmware給裝置的情況下。
struct bin_attribute {
struct attribute attr; /* 定義name,owner,mode */
size_t size; /* 屬性最大長度,如沒有最大長度則設為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一次載入多次呼叫,每次最多PAGE_SIZE大小。注意write無法指示最後一個寫操作,得通過其他方式判斷操作的結束。
二進位制屬性不能定義為預設值,因此需明確的建立與刪除:
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)
熱插拔事件即當系統設定發生改變是核心向使用者空間的通知。然後使用者空間會呼叫/sbin/hotplug通過建立節點、載入驅動等動作進行響應。這個熱插拔事件的產生是在kobject_add和kobject_del時。我們可以通過上面kset中定義的uevent_ops對熱插拔事件產生進行設定:
struct kset_uevent_ops {
/* 實現事件的過濾,其返回值為0時不產生事件 */
int (* const filter)(struct kset *kset, struct kobject *kobj);
/* 生成傳遞給/sbin/hotplug的name引數 */
const char *(* const name)(struct kset *kset, struct kobject *kobj);
/* 其他傳遞給/sbin/hotplug的引數通過這種設定環境變數的方式傳遞 */
int (* const uevent)(struct kset *kset, struct kobject *kobj,
struct kobj_uevent_env *env);
};
頂層容器
Buses, Devices, Drivers and Classes
Buses
匯流排Buses是處理器和裝置的通道。在裝置模型中,所有裝置都是通過匯流排連線在一起的,哪怕是一個內部虛擬的platform匯流排。
/* defined in <linux/device.h> */
struct bus_type {
const char *name; /* 匯流排型別名 */
struct bus_attribute *bus_attrs; /* 匯流排的屬性 */
struct device_attribute *dev_attrs; /* 裝置屬性,為每個加入匯流排的裝置建立屬性連結串列 */
struct driver_attribute *drv_attrs; /* 驅動屬性,為每個加入匯流排的驅動建立屬性連結串列 */
/* 驅動與裝置匹配函數:當一個新裝置或者驅動被新增到這個匯流排時,
這個方法會被呼叫一次或多次,若指定的驅動程式能夠處理指定的裝置,則返回非零值。
必須在匯流排層使用這個函數, 因為那裡存在正確的邏輯,核心核心不知道如何為每個匯流排型別匹配裝置和驅動程式 */
int (*match)(struct device *dev, struct device_driver *drv);
/*在為使用者空間產生熱插拔事件之前,這個方法允許匯流排新增環境變數(引數和 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屬性檔案的建立移除 */
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);
/* 遍歷匯流排上的裝置與驅動 */
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中,每一個底層裝置都是structure device的一個範例:
struct device {
struct device *parent; /* 父裝置,匯流排裝置指定為NULL */
struct device_private *p; /* 包含裝置連結串列,driver_data(驅動程式要使用資料)等資訊 */
struct kobject kobj;
const char *init_name; /* 初始預設的裝置名 */
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
裝置模型跟蹤所有系統已知的驅動。
struct device_driver {
const char *name; /* 驅動名稱,在sysfs中以資料夾名出現 */
struct bus_type *bus; /* 驅動關聯的匯流排型別 */
int (*probe) (struct device *dev); /* 查詢裝置的存在 */
int (*remove) (struct device *dev); /* 裝置移除回撥 */
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
類是裝置的一個高階檢視,實現了底層細節。通過對裝置進行分類,同類程式碼可共用,減少了核心程式碼的冗餘。
struct class {
const char *name; /* class的名稱,會在“/sys/class/”目錄下體現 */
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs; /* 該class下每個裝置的attribute */
struct kobject *dev_kobj;
/* 當該class下有裝置發生變化時,會呼叫class的uevent回撥函數 */
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
本文永久更新連結地址:http://www.linuxidc.com/Linux/2016-12/138151.htm
相關文章