內核驅動模型
- 何為驅動模型
- Linux驅動模型
- 驅動模型
- dev型別---struct device
- bus型別---struct bus_type
- drv型別---struct device_driver
- 驅動中涉及的介面
- bus相關
- dev相關
- drv相關
- 總結
- 后續
何為驅動模型
在我看來模型就是對一系列事務,進行抽象、統一、管理,所呈現出的一種層次關系,就像一個公司有部門、小組、個人這樣的一個組織關系,那么這樣做的好處就是便于管理,
Linux驅動模型
在Linux的世界里將C語言運用到極致,在這里,不能單單的想象成結構體,要體會其資料結構背后的含義,而不能單單的理解成結構體型別,在c++中有個更好的說法叫類,所有的型別都想象成類更好理解,其中結構體的成員變數類比成屬性,其中函式指標類比這個類所具有的方法,類比一個人物的固有血條(屬性)以及這個人物的攻擊技能(方法),這樣的結構在Linux里面很常見,
驅動模型
在Linux中最常見的驅動模型,就是設備(dev)-總線(bus)-驅動(drv),這樣的模型結構,在Linux中類一般都是事務的抽象,提取一些通用的共同的內容,設備型別更多的是來描述這個設備具體的一些通用屬性,bus是dev和drv的橋梁,紐帶,dev和drv的關系就是靠bus來銜接,drv描述更多的方法,這一類的設備他具有哪些技能,一般在drv中進行描述,
dev bus drv的型別定義在kernel\include\linux\device.h檔案中
dev型別—struct device
在Linux中設備是一個寬泛的概念,并不是指某一個物體設備,是一個抽象出來的設備,不要當一個物體設備看待,
struct device {
struct device *parent;
struct device_private *p;
struct kobject kobj;
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this
device */
void *platform_data; /* Platform specific data, device
core doesn't touch it */
void *driver_data; /* Driver data, set and get with
dev_set/get_drvdata */
....
}
struct device_private {
struct klist klist_children;
struct klist_node knode_parent;
struct klist_node knode_driver;
struct klist_node knode_bus;
struct list_head deferred_probe;
struct device *device;
};
- 其中最重要的幾個成員變數是*bus *driver指標,指向和這個dev相關聯的總線和驅動,
- 同時還有這個*p的成員變數,這個list node屆時就會插入到bus管理的那個dev鏈表中去,被管理起來,還有drv的節點也會插入到drv管理的dev鏈表中去,被drv管理起來,
- 而根據這個結構體,我就能知道自己這個設備是屬于哪個bus,以及知道我的驅動是哪個,
- dev的name存在于kobj的成員變數name中,他兩用同一個name,
bus型別—struct bus_type
這里的bus,同樣是寬泛的概念,并不是cpu中實實在在的總線,是一種程式上虛擬出的一種代碼結構,
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
struct subsys_private *p;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
}
struct subsys_private {
struct klist klist_devices;
struct klist klist_drivers;
struct bus_type *bus;
}
- match方法為這個bus中dev和drv他們兩個匹配的方法,入參剛好一個是dev一個是drv,只有他們倆某個屬性對上了(通常我們用dev和drv的名字作為匹配的條件),那么我們稱這個dev和drv匹配上了然后就會去執行probe的方法,后面代碼中會介紹,
- bus型別最重要的是*p這個成員變數,他管理了2個鏈表,分別是dev鏈表和drv鏈表,這樣bus就起到一個管理者的角色,他就知道他當前的這個bus擁有哪些設備和驅動,
drv型別—struct device_driver
drv同樣也是廣泛意義上的驅動,并不是某一個具體外設的驅動,這里同樣抽象出來了,但是具體的外設驅動肯定是基于這個資料結構的,有點像c++中外設驅動的父類那種感覺,
struct device_driver {
const char *name;
struct bus_type *bus;
struct driver_private *p;
int (*probe) (struct device *dev);
}
struct driver_private {
struct kobject kobj;
struct klist klist_devices;
struct klist_node knode_bus;
struct module_kobject *mkobj;
struct device_driver *driver;
};
- 根據*bus 知道我這個驅動是屬于哪個bus的,
- 同樣有個p,p里面有一個bus的節點,屆時會插入到bus的那個p的drv鏈表下,被管理起來
- 除此之外還有一個dev的鏈表,而這個鏈表是用來管理這個驅動所匹配上所有的設備的,為什么有這樣的結構,是應為一個設備只能有一個驅動,而一個驅動可以擁有好幾個設備,多個相同或者類似的設備共享一個驅動,這個應該好理解,
驅動中涉及的介面
每個注冊的介面都有一個register,同樣和他相對應的有一個unregister介面,比如int driver_register(struct device_driver *drv) 與他對應有一個void driver_unregister(struct device_driver *drv)介面,主要用于資源的釋放等相關操作,
bus相關
因為drv和dev的初始化都要涉及到bus,所以,在linux中bus都要先做初始化才能被dev和drv注冊時使用,
介面定義在\drivers\base\bus.c
int bus_register(struct bus_type *bus)
int bus_register(struct bus_type *bus)
{
int retval;
struct subsys_private *priv;
priv = kzalloc(sizeof(struct subsys_private), GFP_KERNEL); //申請bus的p空間
priv->bus = bus;//p的bus指向這個將要注冊的bus
bus->p = priv;//將要注冊的bus的p指向這個剛申請出來的p空間
/* 初始化bus中p帶的兩個鏈表,分別是dev鏈表和drv鏈表 */
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
klist_init(&priv->klist_drivers, NULL, NULL);
}
dev相關
介面定義在\drivers\base\core.c,物體大部分作業其實是device_add完成的,
下面介紹的dev注冊程序,是指bus上已經注冊好drv的情況下,dev注冊函式的呼叫程序,
假如bus上沒有drv,那么呼叫程序沒有這么復雜,
假如bus沒有匹配的drv說明當前dev的驅動沒有或者還沒有注冊上,沒關系,等drv注冊的時候還是會來掃一遍設備然后在執行bus的probe方法,
int device_register(struct device *dev)—》int device_add(struct device *dev)
int device_add(struct device *dev)
{
if (dev->init_name) {//用init_name 來初始化這個dev的名字
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
//一系列檢查無誤后最后呼叫bus_add_device->klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
//吧這個dev成員p的bus節點插入到-》bus成員p的dev鏈表上去,被bus管理起來,
error = bus_add_device(dev);
bus_probe_device(dev);
}
//呼叫程序簡化
bus_probe_device(dev)-》device_initial_probe(dev)-》__device_attach(dev, true)-》ret = bus_for_each_drv(dev->bus, NULL, &data,__device_attach_driver)-》int __device_attach_driver;
//重要函式--回圈取bus上的drv鏈表上的驅動來挨個和這個注冊的dev嘗試匹配是否能匹配上,而嘗試匹配函式即為fn---也就是-》__device_attach_driver
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,
void *data, int (*fn)(struct device_driver *, void *))
{
while ((drv = next_driver(&i)) && !error)
error = fn(drv, data);
}
__device_attach_driver(struct device_driver *drv, void *_data)
{
ret = driver_match_device(drv, dev);//直接呼叫bus的match介面,所以是否匹配成功的條件是,bus在注冊的時候match方法決定的,drv->bus->match(dev, drv)
if (ret == 0) {
/* no match */
return 0;
}
return driver_probe_device(drv, dev);
}
driver_probe_device(drv, dev);-》really_probe(dev, drv);
really_probe(struct device *dev, struct device_driver *drv)
{
dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
}
else if (drv->probe) {
ret = drv->probe(dev);
}
//系結驅動,dev的p擁有的drv節點插入到drv的成員變數p的dev鏈表中,被drv管理起來,
//最后實作klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);
driver_bound(dev);
}
drv相關
同樣下面drv的注冊也是基于bus上面dev已有的情況下進行分析,drv注冊函式大部分作業在bus_add_driver完成,
介面定義在\drivers\base\driver.c
int driver_register(struct device_driver *drv)-》bus_add_driver(drv);
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);//申請drv結構的成員變數p
klist_init(&priv->klist_devices, NULL, NULL);//初始化p的dev鏈表,屆時dev注冊的時候,會吧dev掛載這個鏈表下面,被drv所管理起來,
priv->driver = drv;//p的drv指標指向這個drv
drv->p = priv;//drv的p指向這塊申請的記憶體,這兩個操作意思就是隨便拿到p還是drv都能拿到對方的結構,
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);//把drv的p成員的bus節點,插到bus成員p的drv鏈表上,被bus管理起來,操作和dev的注冊類似,
error = driver_attach(drv);
}
driver_attach-》bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
int __driver_attach(struct device *dev, void *data)
{
ret = driver_match_device(drv, dev);//這個函式在dev注冊的時候也出現過,呼叫bus的match介面用來判斷這個dev和drv是否匹配,
if (ret == 0) {
/* no match */
return 0;
}
if (!dev->driver)
driver_probe_device(drv, dev);//這個函式同樣在dev注冊的時候也出現過,其作用,判斷bus的probe假如有則呼叫,否則在指向drv的probe,同時要把這個dev系結到這個驅動上去,被這個驅動管理起來,
}
bus_for_each_dev和drv注冊的時候類似,取bus上的每一個dev和你這個驅動進行匹配,匹配的方法為 __driver_attach
總結
總的來說Linux中的代碼確實很多,全部看懂實在不大可能,包括這篇文章中我也是刪減了很多和主題不大相關的代碼和內容,比如代碼中有很多和除錯相關和檔案系統相關的內容我都沒要貼,因為這些內容主要是在/sys/class/目錄下的一些層次關系的管理,和本文的主題沒什么太大的關聯,所以看代碼也一樣,不是太相關的就直接略過,畢竟經歷有限,而如何判斷哪些是相關的,哪些是次要的,那就要好好的積累經驗了,萬事都有一個程序,你慢慢的熟悉了,接觸的多了,就會有意識,
后續
當初學習的時候也會去網上查資料,先看別人的理解,摸摸大體主干,然后自己再去看代碼,驗證是否理解正確,看了很多資料后,理論架構說的很清楚了,但心里還是有一個疑問,這套東西,用在什么地方,有什么好,說白了就是缺少理論和實際的結合,所以我后面還會在寫一篇,介紹這個驅動模型的實體平臺總線的運用,理論和實體的結合分析會讓你更好的理解這個知識點,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/260383.html
標籤:其他
下一篇:Xtensa 架構處理器總結
