Linux裝置模型

來源:互聯網
上載者:User

 

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。

二是基於這兩個底層資料結構上實現的裝置模型:匯流排,裝置,驅動。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.