Linux裝置模型
看LDD3中裝置模型一章,覺得思維有些混亂。這裡從整體的角度來理理思路。
本文從四個方面來總結一些內容:
1.底層資料結構:kobject,kset.
2.linux裝置模型層次關係:bus_type,device,device_driver.
3.整合:PCI裝置驅動模型執行個體及裝置,裝置驅動註冊源碼的簡單分析.
4.物件導向的思想在linux裝置模型中的應用分析.
一、底層資料結構:kobject,kset
先說說模型的意義:
總體來說是為了系統地管理所有裝置。
kobject
結合物件導向的思維。這個kobject屬於最基礎的結構,也就是最高抽象層(有點像java中的Cobject類)。任何一個裝置模型如匯流排,裝置,驅動都屬於一個kobject 。在實現上這種派生關係就是在結構體中包含一個kobject的變數。
這個在層次上處理最頂層的kobject結構提供了所有模型需要的最基本的功能:
1 引用計數 用於核心維護其存在與消亡
2 sysfs表示 每個sys/下的對象對應著一個kobject。
3 熱拔插事件處理。 處理裝置的熱拔插事件。
Kobjects 在核心中對應有一套申請,初始化,添加,註冊,計數操作,釋放等函數
struct kobject {
const char * k_name; 名
char name[KOBJ_NAME_LEN];
struct kref kref; 計數
struct list_head entry; 用於串連到同類kobjects的鏈表
struct kobject * parent; 用於實現層次,指向其父物件。
struct kset * kset; 用於實現層次,所屬的集合
struct kobj_type * ktype; 指向對象的類型。
struct dentry * dentry; 指示在sysfs 中的目錄項
wait_queue_head_t poll;
}; (linux 2.6.18)
Kset 和kobj_type struct kset {
struct subsystem * subsys; 在最新核心中已經沒有subsys概念了。統一用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)
Kset 在概念上是一個集合或者叫容器。實現了對象的層次。所有屬於一個ksets的對象(kobject)的parent都指向該ksets的kobj.同時這個對象都串連到kset 的list表上。同時位於ksets層次之上的是subsys,在最新的核心中已經取消subsys,因為它本質上也就是一個ksets。Kset有一套類似kobject的操作,實現上只是進一步調用其自身kobj的相應操作,畢竟ksets本質上也是一個kobject。
最後 屬於同一個集合的對象可以擁有共同的屬性:ktype 。
struct kobj_type {
void (*release)(struct kobject *);
struct sysfs_ops * sysfs_ops;
struct attribute ** default_attrs;
};
所謂的屬性更具體一點說就是一些索引值對。並且在sysfs_ops中的show函數被檔案系統調用來顯示sys/下面對應入口各屬性的值。
如此 ,kobjects與ksets實現層次樹的底層骨架。
進一步地,通過封裝這些底層結構來實現上層的裝置驅動模型。
核心裝置驅動模型層次劃分三個方面:匯流排,裝置,驅動。
二、linux裝置模型層次關係:bus_type,device,device_driver
基本關係簡要的概括如下:
驅動核心可以註冊多種類型的匯流排。
每種匯流排下面可以掛載許多裝置。(通過kset devices)
每種匯流排下可以用很多裝置驅動。(通過包含一個kset drivers)}
每個驅動可以處理一組裝置。
這種基本關係的建立源於實際系統中各種匯流排,裝置,驅動結構的抽象。
下面看看三者資料結構的定義。
首先是匯流排,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);//裝置驅動匹配函數
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驅動中裝置模型的執行個體來看看細節。
三、整合: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 裝置被發現, PCI 核心在記憶體中建立一個struct pci_dev 類型的新變數。這個PCI 裝置的匯流排特定的成員被PCI 核心初始化( devfn, vendor, device, 和其他成員), 並且struct device 變數的parent 變數被設定為PCI 匯流排裝置(注意匯流排也不僅有一個bus_type 結構,還對應一個裝置device) bus 變數被設定指向pci_bus_type 結構. 接下來name 和bus_id 變數被設定, 根據讀自PCI 裝置的name 和ID.
在PCI 裝置結構被初始化之後, pci裝置被註冊到驅動核心, 調用device_register(&dev->dev); 在device_register函數中,kobject被註冊到驅動核心,pci裝置被添加到pci匯流排的裝置列表中,熱拔插事件產生,同時kobject被添加到parent的鏈表中,sysfs入口也被添加。
PCI裝置的發現是通過特定代碼探測PCI空間來實現的。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;
};
沒有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符號串連
/*設定環境變數,然後調用call_usermodehelper (argv[0], argv, envp, 0); 引起熱拔插事件使用者空間指令碼執行。*/
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)函數:
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;//優先調用匯流排提供匹配方法
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/入口中相互關聯的符號串連*/
goto Done;
Done:
return ret;
}
亂七八糟的。主線還是模型的層次關係。對kobject,kset細節中關於屬性,熱拔插,sys入口的部分沒有深入。或許,理解總體和設計思想是更重要的。人的精力真的有限。
四、物件導向的思想在linux裝置模型中的應用分析.
通過裝置模型,看到了物件導向編程思想用C語言的實現。核心中常見到封裝了資料和方法的結構體,這是物件導向封裝特性的實現。而這裡展現的更多的是繼承方面的實現。比如說pci_driver,它的父類是device_driver,而更上一層是一個kobject。在C++中,繼承一個父類則子類中相應的包含父類的一個執行個體。核心中也是通過包含一個父類的實體來實現這種派生關係。因此,一個pci_driver內部必然包含一個device_driver,同樣,device_driver內部必然包含一個kobject。
上面提到過,註冊一個模型的過程類似於物件導向中建構函式的調用。子類需要調用父類建構函式來完成自身的構造。再來看看註冊一個pci_driver的過程:
pci_register_driver(struct pci_driver *driver)
-->driver_register(&drv->driver);
-->kobject_register(&drv->kobj);
這不是OO中的繼承嗎?
裝置模型源碼中還能找到多態(虛函數)的思想。看到pci_driver和device_driver中提供了差不多同名的方法不覺得奇怪嗎??它們不同的地方在於參數。pci_driver中方法的參數是pci_device * dev ,而device_driver方法的參數則是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的函數指標。看看這些函數中的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);
}
這裡出現了兩個指標類型轉換(通過container_of()宏實現的),然後調用platform_driver提供的probe函數。
考慮一下platform_driver的註冊過程。每個驅動註冊過程相同。如前面分析過的,進入到driver_register後,裝置驅動device_driver層的probe將會被調用來探測裝置,這個函數像上面源碼所指示的那樣完成類型轉化調用其子類platform_driver層的probe函數來完成具體的功能。 那麼,從device_driver層看來,相同的函數調用由子類來完成了不同的具體功能。這不是多態的思想嗎??
這裡非常粗淺的分析了linux裝置模型中使用C實現物件導向的三大要素(封裝,繼承,多態)的基本思想。用C來實現確實做的工作要多一些,不過靈活性更高了。怪不得linus炮轟C++.
"使用優秀的、高效的、系統級的和可移植的C++的唯一方式,最終還是限於使用C本身具有的所有特性。"
這裡列出了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一般由模組定義並且在模組初始化函數中向核心註冊。也要兩個方面,一是pci_driver中特定於PCI的方法,支援的ID列表等的初始化;二是內嵌的device_driver的註冊,使用register_driver(struct device_driver * drv)。
這就有點像物件導向中子類與父類的關係,子類建構函式的調用隱含父類建構函式的調用。在具體實現方面分兩個層次:
一是底層資料結構來實現基本對象及其層次關係:kobjects和ksets。
二是基於這兩個底層資料結構上實現的裝置模型:匯流排,裝置,驅動。