Linux设备模型(总结)
轉:http://www.360doc.com/content/11/1219/16/1299815_173418267.shtml
?
看了一段時間的驅動編程,從LDD3的hello wrod到后來的字符設備以至于更加復雜的驅動,越看越是覺得對linux驅動的結構不清楚,越看越是迷糊。于是就停下腳步搜索一下資料理一下頭緒:以下四個方面來總結一些內容: 1.底層數(shù)據(jù)結構:kobject,kset.
2.linux設備模型層次關系:bus_type,device,device_driver.
3.集成:PCI設備驅動模型實例及設備,設備驅動注冊源碼的簡單分析. 4.面向對象的思想在linux設備模型中的應用分析.
一、底層數(shù)據(jù)結構:kobject,kset
先說說模型的意義:
總體來說是為了系統(tǒng)地管理所有設備。
在具體實現(xiàn)方面分兩個層次:
一是底層數(shù)據(jù)結構來實現(xiàn)基本對象及其層次關系:kobjects和ksets。
二是基于這兩個底層數(shù)據(jù)結構上實現(xiàn)的設備模型:總線,設備,驅動。
kobject?
?
結合面向對象的思維。這個kobject屬于最基礎的結構,也就是最高抽象層(有點像java中的Cobject類)。任何一個設備模型如總線,設備,驅動都屬于一個kobject?。在實現(xiàn)上這種派生關系就是在結構體中包含一個kobject的變量。
這個在層次上處理最頂層的kobject結構提供了所有模型需要的最基本的功能:
1?引用計數(shù)??用于內核維護其存在與消亡
2?sysfs表示??每個sys/下的對象對應著一個kobject。
3?熱拔插事件處理。 處理設備的熱拔插事件。
Kobjects 在內核中對應有一套申請,初始化,添加,注冊,計數(shù)操作,釋放等函數(shù)
struct?kobject?{
?const?char??*?k_name;?名
?char???name[KOBJ_NAME_LEN];
?struct?kref??kref;?計數(shù)
?struct?list_head?entry;?用于連接到同類kobjects的鏈表
?struct?kobject??*?parent;??用于實現(xiàn)層次,指向其父對象。
?struct?kset??*?kset;?用于實現(xiàn)層次,所屬的集合
?struct?kobj_type?*?ktype;??指向對象的類型。
?struct?dentry??*?dentry;??指示在sysfs?中的目錄項
?wait_queue_head_t?poll;
};?(linux 2.6.18)
Kset?和kobj_type?
Kset?在概念上是一個集合或者叫容器。實現(xiàn)了對象的層次。所有屬于一個ksets的對象(kobject)的parent都指向該ksets的kobj.同時這個對象都連接到kset?的list表上。同時位于ksets層次之上的是subsys,在最新的內核中已經取消subsys,因為它本質上也就是一個ksets。Kset有一套類似kobject的操作,實現(xiàn)上只是進一步調用其自身kobj的相應操作,畢竟ksets本質上也是一個kobject。
struct?kset?{
?struct?subsystem?*?subsys;??在最新內核中已經沒有subsys概念了。統(tǒng)一用ksets
?struct?kobj_type?*?ktype;???類型。
?struct?list_head?list;????同一kset的鏈表
?spinlock_t??list_lock;
?struct?kobject??kobj;?自身的kobjects
?struct?kset_uevent_ops?*?uevent_ops;
};(linux 2.6.18)
最后?屬于同一個集合的對象可以擁有共同的屬性:ktype?。
struct?kobj_type?{
?void?(*release)(struct?kobject?*);
?struct?sysfs_ops?*?sysfs_ops;
?struct?attribute?**?default_attrs;
};
所謂的屬性更具體一點說就是一些鍵值對。并且在sysfs_ops中的show函數(shù)被文件系統(tǒng)調用來顯示sys/下面對應入口各屬性的值。
如此?,kobjects與ksets實現(xiàn)層次樹的底層骨架。
進一步地,通過封裝這些底層結構來實現(xiàn)上層的設備驅動模型。
內核設備驅動模型層次劃分三個方面:總線,設備,驅動。
二、linux設備模型層次關系:bus_type,device,device_driver
基本關系簡要的概括如下:
驅動核心可以注冊多種類型的總線。
每種總線下面可以掛載許多設備。(通過kset devices)
每種總線下可以用很多設備驅動。(通過包含一個kset drivers)}
每個驅動可以處理一組設備。
這種基本關系的建立源于實際系統(tǒng)中各種總線,設備,驅動結構的抽象。
下面看看三者數(shù)據(jù)結構的定義。
首先是總線,bus_type.
struct bus_type {
const char??* name;
?struct subsystem?subsys;//代表自身
struct kset??drivers;?? //當前總線的設備驅動集合
struct kset??devices;?//所有設備集合
struct klist??klist_devices;
struct klist??klist_drivers;
?struct bus_attribute?* bus_attrs;//總線屬性
struct device_attribute?* dev_attrs;//設備屬性
struct driver_attribute?* drv_attrs;
?int??(*match)(struct device * dev, struct device_driver * drv);//設備驅動匹配函數(shù)
int??(*uevent)(struct device *dev, char **envp,??
int num_envp, char *buffer, int buffer_size);//熱拔插事件
int??(*probe)(struct device * dev);
int??(*remove)(struct device * dev);
void??(*shutdown)(struct device * dev);
int??(*suspend)(struct device * dev, pm_message_t state);
int??(*resume)(struct device * dev);
};
這是2.6.18的定義。源碼能說明一切。下面是設備device的定義:
struct device {
struct device ?* parent; //父設備,一般一個bus也對應一個設備。
struct kobject kobj;//代表自身
char?bus_id[BUS_ID_SIZE];?
struct bus_type?* bus;??/*?所屬的總線 */
struct device_driver *driver;?/* 匹配的驅動*/
void??*driver_data;?/* data private to the driver?指向驅動?*/
void??*platform_data;?/* Platform specific data,由驅動定義并使用*/
///更多字段忽略了
};
下面是設備驅動定義:
struct device_driver {
const char??* name;
struct bus_type??* bus;//所屬總線
?struct completion?unloaded;
struct kobject??kobj;//代表自身
struct klist??klist_devices;//設備列表
struct klist_node?knode_bus;
?struct module??* owner;
?int?(*probe)?(struct device * dev);
int?(*remove)?(struct device * dev);
void?(*shutdown)?(struct device * dev);
int?(*suspend)?(struct device * dev, pm_message_t state);
int?(*resume)?(struct device * dev);
};
OK。基本的東西弄明白了。通過PCI驅動中設備模型的實例來看看細節(jié)。
?
三、集成:PCI設備驅動模型實例及設備,設備驅動注冊源碼的簡單分析.
先看pci總線類型定義:
struct bus_type pci_bus_type = {
.name??= "pci",
.match??= pci_bus_match,
.uevent??= pci_uevent,
.probe??= pci_device_probe,
.remove??= pci_device_remove,
.suspend?= pci_device_suspend,
.shutdown?= pci_device_shutdown,
.resume??= pci_device_resume,
.dev_attrs?= pci_dev_attrs,
};
然后是pci設備和驅動。pci設備和pci驅動沒有直接使用device和device_driver,而是將二者封裝起來,加上pci特定信息構成pci_dev和pci_driver。當然,意義是一樣的。
struct?pci_dev?{?
/*?PCI設備的ID信息*/?
unsigned?int?devfn;?
unsigned?short?vendor;?
unsigned?short?device;?
unsigned?short?subsystem_vendor;?
unsigned?short?subsystem_device;?
unsigned?int?class;?
/*?...?*/?
struct pci_bus?*bus;?? //所屬pci總線
struct?pci_driver?*driver;??//所屬的pci驅動
?/*?...?*/?
struct?device?dev;? //設備自身
/*?...?*/?
};?
這里省略了許多PCI設備特定的信息,如中斷,資源等。。
當一個PCI?設備被發(fā)現(xiàn),?PCI?核心在內存中創(chuàng)建一個?struct?pci_dev?類型的新變量。這 個?PCI?設備的總線特定的成員被?PCI?核心初始化(?devfn,?vendor,?device,?和其他成員),?并 且?struct?device?變量的?parent?變量被設置為?PCI?總線設備(注意總線也不僅有一個bus_type?結構,還對應一個設備 device)?bus?變量被設置指向?pci_bus_type?結構.?接下來?name?和?bus_id?變量被設置,?根據(jù)讀自?PCI?設 備的?name?和?ID.
在?PCI?設備結構被初始化之后,?pci設備被注冊到驅動核心,?調用?device_register(&dev->dev);?在device_register函數(shù)中,kobject被注冊到驅動核心,pci設備被添加到pci總線的設備列表中,熱拔插事件產生,同時kobject被添加到parent的鏈表中,sysfs入口也被添加。
PCI設備的發(fā)現(xiàn)是通過特定代碼探測PCI空間來實現(xiàn)的。PCI設備由內核自動生成的。這樣在注冊pci驅動的時候PCI設備已經注冊,其屬性如ID的信息都已經是被初始化好了。
最后是pci_driver:
struct pci_driver {
struct list_head node;
char *name; //驅動name
const struct pci_device_id *id_table;?/*?驅動支持的設備ID列表 */
int? (*probe)? (struct pci_dev *dev, const struct pci_device_id *id);?/* New device inserted */
void (*remove) (struct pci_dev *dev);?/* Device removed (NULL if not a hot-plug capable driver) */
int? (*suspend) (struct pci_dev *dev, pm_message_t state);?/* Device suspended */
int? (*resume) (struct pci_dev *dev);???????????????? /* Device woken up */
int? (*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);?? /* Enable wake event */
void (*shutdown) (struct pci_dev *dev);
?struct pci_error_handlers *err_handler;
struct device_driver?driver;?//設備驅動
struct pci_dynids dynids;
};
?這里列出了pci_bus,pci_dev,pci_driver的定義。它們的關系與bus,device,driver一樣。pci_bus直接是一個bus_type結構初始化的實體。
pci_dev由內核探測,并且注冊到驅動核心。pci設備的初始化和注冊分兩個方面,一是pci設備信息如ID,資源等,二是pci_dev.dev的注冊。調用register_device(struct? device * dev)來完成。
pci_driver 一般由模塊定義并且在模塊初始化函數(shù)中向內核注冊。也要兩個方面,一是pci_driver中特定于PCI的方法,支持的ID列表等的初始化;二是內嵌的 device_driver的注冊,使用register_driver(struct device_driver * drv)。
這就有點像面向對象中子類與父類的關系,子類構造函數(shù)的調用隱含父類構造函數(shù)的調用。
沒有register_device(dev)和register_driver(drv)的注冊,驅動核心就不知道設備和驅動的存在,sysfs也沒有相關的入口。
最后一件事,看看register_device(dev)和register_driver(drv)的代碼。
int?device_register(struct?device?*dev)
{
device_initialize(dev);
return?device_add(dev);
}
device_register-->device_initialize(dev);//初始化設備各個字段
void device_initialize(struct device *dev)
{
kobj_set_kset_s(dev, devices_subsys); //所有的dev屬于devices_subsys這個集合
kobject_init(&dev->kobj); //初始kobj
klist_init(&dev->klist_children, klist_children_get,
klist_children_put);
INIT_LIST_HEAD(&dev->dma_pools);
INIT_LIST_HEAD(&dev->node);
init_MUTEX(&dev->sem);
device_init_wakeup(dev, 0);
}
device_register-->device_add(dev);
int?device_add(struct?device?*dev) //主要流程
{
??? dev?=?get_device(dev);
??? parent?=?get_device(dev->parent);
kobject_set_name(&dev->kobj,?"%s",?dev->bus_id);
??? dev->kobj.parent?=?&parent->kobj;
kobject_add(&dev->kobj);//將自身kobject加入到層次結構中,并且建立sysfs entry.
//設置uevent_attr:
dev->uevent_attr.attr.name?=?"uevent";
dev->uevent_attr.attr.mode?=?S_IWUSR;
if?(dev->driver)
?dev->uevent_attr.attr.owner?=?dev->driver->owner;
dev->uevent_attr.store?=?store_uevent;
device_create_file(dev,?&dev->uevent_attr);
//建立顯示設備號的sysfs入口,即當前設備入口下的"dev"文件顯示設備主從設備號。
if?(MAJOR(dev->devt))?{
attr->attr.name?=?"dev";
attr->attr.mode?=?S_IRUGO;
if?(dev->driver)
attr->attr.owner?=?dev->driver->owner;
attr->show?=?show_dev;
error?=?device_create_file(dev,?attr);
}
//建立類的sysfs符號連接?
if?(dev->class)?{
sysfs_create_link(&dev->kobj,?&dev->class->subsys.kset.kobj,"subsystem");
sysfs_create_link(&dev->class->subsys.kset.kobj,?&dev->kobj,dev->bus_id);}
sysfs_create_link(&dev->kobj,?&dev->parent->kobj,?"device");
class_name?=?make_class_name(dev->class->name,?&dev->kobj);
sysfs_create_link(&dev->parent->kobj,?&dev->kobj,?class_name);
}
error?=?bus_add_device(dev);//添加一些bus相關的sysfs符號連接
/*設置環(huán)境變量,然后調用call_usermodehelper?(argv[0],?argv,?envp,?0);?引起熱拔插事件用戶空間腳本執(zhí)行。*/
kobject_uevent(&dev->kobj,?KOBJ_ADD);?
bus_attach_device(dev);?/*如 果dev->driver已經存在,調用device_bind_driver(dev);進行綁定,否則遍歷dev->bus上 drivers列表,調用dev->bus.match(dev,drv)來看是否有一個驅動與該dev匹配。如果匹配則綁定。*/
}?OK,上述是主要流程。。
下面是register_driver(drv)函數(shù):
int driver_register(struct device_driver * drv)
{
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown)) {
printk(KERN_WARNING "Driver ''''%s'''' needs updating - please use bus_type methods\n", drv->name);
}
klist_init(&drv->klist_devices, klist_devices_get, klist_devices_put);
init_completion(&drv->unloaded);
return bus_add_driver(drv);
}
driver_register(drv);-->bus_add_driver(drv);
int bus_add_driver(struct device_driver * drv)
{
struct bus_type * bus = get_bus(drv->bus);
?error = kobject_set_name(&drv->kobj, "%s", drv->name);
drv->kobj.kset = &bus->drivers; //驅動隸屬于總線的驅動集合
error = kobject_register(&drv->kobj);//注冊自身kobject
?driver_attach(drv);//添加驅動到總線
?klist_add_tail(&drv->knode_bus, &bus->klist_drivers);
module_add_driver(drv->owner, drv);
?driver_add_attrs(bus, drv);
add_bind_files(drv);
}
driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv);
void driver_attach(struct device_driver * drv)
{
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
對總線上的每個設備dev,調用__driver_attach(dev,drv);最終調用
driver_probe_device(drv, dev);
driver_register(drv);-->bus_add_driver(drv);-->driver_attach(drv);
-->__driver_attach(dev,drv);-->driver_probe_device(drv, dev);
int driver_probe_device(struct device_driver * drv, struct device * dev)
{
if (drv->bus->match && !drv->bus->match(dev, drv))
goto Done;//優(yōu)先調用總線提供匹配方法
?dev->driver = drv;
if (dev->bus->probe) {
ret = dev->bus->probe(dev);//總線的探測方法
}
else if (drv->probe)
{
ret = drv->probe(dev); //用dev->driver的探測方法
}
device_bind_driver(dev); /*探測成功則綁定設備到驅動,添加dev到drv的設備列表并且建立驅動與設備在sys/入口中相互關聯(lián)的符號連接*/
goto Done;
?Done:
return ret;
}
亂七八糟的。主線還是模型的層次關系。對kobject,kset細節(jié)中關于屬性,熱拔插,sys入口的部分沒有深入。或許,理解總體和設計思想是更重要的。人的精力真的有限。
四、面向對象的思想在linux設備模型中的應用分析.
通 過設備模型,看到了面向對象編程思想用C語言的實現(xiàn)。內核中常見到封裝了數(shù)據(jù)和方法的結構體,這是面向對象封裝特性的實現(xiàn)。而這里展現(xiàn)的更多的是繼承方面 的實現(xiàn)。比如說pci_driver,它的父類是device_driver,而更上一層是一個kobject。在C++中,繼承一個父類則子類中相應的 包含父類的一個實例。內核中也是通過包含一個父類的實體來實現(xiàn)這種派生關系。因此,一個pci_driver內部必然包含一個 device_driver,同樣,device_driver內部必然包含一個kobject。
上面提到過,注冊一個模型的過程類似于面向對象中構造函數(shù)的調用。子類需要調用父類構造函數(shù)來完成自身的構造。再來看看注冊一個pci_driver的過程:
pci_register_driver(struct pci_driver *driver)
-->driver_register(&drv->driver);
-->kobject_register(&drv->kobj);
這不是OO中的繼承么?
設 備模型源碼中還能找到多態(tài)(虛函數(shù))的思想。看到pci_driver和device_driver中提供了差不多同名的方法不覺得奇怪嗎??它們不同的 地方在于參數(shù)。pci_driver中方法的參數(shù)是pci_device * dev ,而device_driver方法的參數(shù)則是 device * dev 。這么安排是有意的!
最典型的例子莫過于platform_driver和device_driver。
struct?platform_driver?{
?int?(*probe)(struct?platform_device?*);
int?(*remove)(struct?platform_device?*);
void?(*shutdown)(struct?platform_device?*);
int?(*suspend)(struct?platform_device?*,?pm_message_t?state);
int?(*resume)(struct?platform_device?*);
struct?device_driver?driver;
};
這顯然比pci_driver來得簡潔。platform_driver除了包含一個device_driver,其它就是5個與device_driver同名的方法。
注冊一個platform_driver的過程:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}
這里設置了platform_driver包含的device_driver的函數(shù)指針。看看這些函數(shù)中的platform_drv_probe。
static int platform_drv_probe(struct device *_dev)
{
struct platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
?return drv->probe(dev);
}
這里出現(xiàn)了兩個指針類型轉換(通過container_of()宏實現(xiàn)的),然后調用platform_driver提供的probe函數(shù)。
考 慮一下platform_driver的注冊過程。每個驅動注冊過程相同。如前面分析過的,進入到driver_register后,設備驅動 device_driver層的probe將會被調用來探測設備,這個函數(shù)像上面源碼所指示的那樣完成類型轉化調用其子類platform_driver 層的probe函數(shù)來完成具體的功能。 那么,從device_driver層看來,相同的函數(shù)調用由子類來完成了不同的具體功能。這不是多態(tài)的思想么??
這里非常粗淺的分析了linux設備模型中使用C實現(xiàn)面向對象的三大要素(封裝,繼承,多態(tài))的基本思想。用C來實現(xiàn)確實做的工作要多一些,不過靈活性更高了。怪不得linus炮轟C++.
"使用優(yōu)秀的、高效的、系統(tǒng)級的和可移植的C++的唯一方式,最終還是限于使用C本身具有的所有特性。"
轉載于:https://www.cnblogs.com/pengdonglin137/p/3328990.html
總結
以上是生活随笔為你收集整理的Linux设备模型(总结)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 那些零碎的感悟,那些成长的事【壹】
- 下一篇: linux命令详解:file命令