天天看點

linux驅動裝置模型詳解 1  kobject 2    kobj_type   3    kset 4    符号連結 5    總線 6    裝置 7    驅動 8    類 9    udev

本篇是關于linux的裝置模型,将會覆寫相關的絕大部分知識,以及實踐操作。希望這篇材料讓大家基本掌握linux驅動裝置模型,進而寫出更加優秀的linux驅動。

linux統一的裝置模型,提供了對電源管理和系統關機 、使用者空間的通訊、可熱插拔裝置 、裝置類别 、對象生命期 的廣泛支援。

Linux 裝置模型代碼負責所有這些方面, 驅動代碼作者隻需要充分信任這些代碼即可,但是, 了解裝置模型絕對是一個好事情.

Linux2.6核心引入了sysfs檔案系統,是與proc同類别的檔案系統。sysfs把連接配接在系統上的裝置和總線組織成分級的檔案,使其從使用者空間可以通路到。

有疑問聯系:[email protected]

1  kobject

Kobject 是基礎的結構, 它保持裝置模型在一起,實作了基本的面向對象管理機制,是裝置模型的核心結構。它與sysfs檔案系統緊密相連,在核心中注冊的每個kobject對象對應sysfs檔案系統中的一個目錄。bus,devices,drivers都是典型的容器。這些容器通過kobject連接配接起來,形成了一個樹狀結構。Kobject通常是嵌入到其他結構中的,單獨意義不大。

結構體定義如下,位于檔案include/linux/kobject.h":

struct kobject {

const char              *name;//函數容器名稱的字元串

struct list_head        entry;//用于kobject所插入的連結清單指針

struct kobject          *parent;//指向父kobject

struct kset             *kset;//指向包含的kset

struct kobj_type        *ktype;//指向kobject類型描述符

 struct kernfs_node      *sd; /* sysfs directory entry */

struct kref             kref;//容器引用計數器

#ifdef CONFIG_DEBUG_KOBJECT_RELEASE

struct delayed_work     release;

#endif

unsigned int state_initialized:1;

unsigned int state_in_sysfs:1;

unsigned int state_add_uevent_sent:1;

unsigned int state_remove_uevent_sent:1;

unsigned int uevent_suppress:1;

};

kobject 結構常常用來連接配接對象到一個層級的結構中, parent 成員代表在層次中之上一級. 如果一個 kobject 表示一個

USB 裝置, 它的 parent 指針可能訓示這

個裝置被插入的 hub. parent 指針的主要用途是在 sysfs 層次中定位對象.

這裡不得不先将一個宏,因為kobject會嵌入在其他裝置機構中,那麼如果有一個 struct kobject 指針, 那如何得到包含它的上級結構的指針呢?

這個就是通過container_of 來實作的,通過向後反轉來得到包含kobject的結構的指針。

/**

 *

container_of - cast a member of a structure out to the containing structure

@ptr:        the pointer to the member.

@type:       the type of the container

struct this is embedded in.

@member:     the name of the member

within the struct.

 */

#define

container_of(ptr, type, member)

1.1   

kobject初始化

            kobject初始化需要調用kobject_init 設定

kobject 的引用計數為 1 ,kobject_set_name設定kobject名字。配置設定給 kobject 的名字( 用

kobject_set_name ) 是給 sysfs 目錄使用的名字. 


int kobject_set_name(struct kobject

*kobj, const char *fmt, ...)

kobject的其中一個關鍵函數是引用計數器操作,隻要對這個對象的引用存在, 這個對象(和支援它的代碼) 必須繼續存在.操作一個 kobject 的引用計數的低級函數是

struct kobject

*kobject_get(struct kobject *kobj)

void

kobject_put(struct kobject *kobj)

1.2   

kobject釋放

每個 kobject 必須有一個釋放函數, 并且這個 kobject 必須持續

( 以一緻的狀态 ) 直到這個方法被調用. 當引用為0的時候就會調用release函數。

釋放的具體release函數被關聯到包含 kobject 的結構類型中。 這個類型用kobj_type 結構類型, 我們可以簡單稱為ktype,如下節所示。

2   

kobj_type  

kobj_type描述kobject類型,這樣就不需要每個kobject分别定義自己特性結構體了,結構體如下:

struct kobj_type {

void (*release)(struct kobject *kobj);//析構行為函數

const struct sysfs_ops *sysfs_ops;//sysfs行為

struct attribute **default_attrs;

const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);

const void *(*namespace)(struct kobject *kobj);

每一個 kobject 需要有一個關聯的 kobj_type 結構。如果這個 kobject 是一個 kset 的成員, kobj_type 指針由 kset 提供. 可以通過函數get_ktype來擷取kobject的kobj_type指針。

static inline struct

kobj_type *get_ktype(struct kobject *kobj)

release函數是kobject的釋放函數,當引用為0的時候會調用。

Sysfs_ops負責描述如何使用它們。結構體如下,就兩個函數非常簡單:

struct sysfs_ops {

ssize_t (*show)(struct kobject *, struct attribute *, char *);

ssize_t (*store)(struct kobject *, struct attribute *, const char *, size_t);

使用者空間讀取sysfs時調用show方法,在寫操作時候調用store方法。

預設屬性default_attrs負責将核心資料映射成sysfs中的檔案,其中模式可以是S_IRUGO。

struct attribute {

const char              *name;

umode_t                 mode;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

bool                    ignore_lockdep:1;

struct lock_class_key   *key;

struct lock_class_key   skey;

name 是屬性的名字( 會出現在kobject的sysfs目錄中)。

如果你想添加一 個新屬性到一個 kobject 的 sysfs 目錄, 簡單地填充一個屬性結構并且傳遞它到:

sysfs_create_file(struct kobject *kobj,const struct attribute *attr)

去除屬性使用函數sysfs_remove_file。

sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)

上面這些事文本屬性檔案,還可以建立和删除二進制屬性:

sysfs_create_bin_file(struct kobject

*kobj, const struct bin_attribute *attr)

sysfs_remove_bin_file(struct kobject

這裡的屬性是使用bin_attribute結構體,裡面分裝了attribute。

3   

kset

通過kset資料結構圖可将kobjects組織成一顆層次樹。Kset是同類kobjects的一個集合體,也就是說相關的kobjects包含在同類型的容器中。

其資料結構如下:

struct kset {

struct list_head list;  //包含在kset的kobject連結清單

spinlock_t list_lock;  

struct kobject kobj;   

const struct kset_uevent_ops *uevent_ops;

}

Kset是把kobject集中到一個集合(相同ktype的kobject可以分組到不同的kset),而ktype描述類型所共有的特性。一旦一個
kset 已被建立并且加入到系統, 會有一個 sysfs 目錄給它.

   可以通過kobject_add來将kobject添加到kset中,并在sysfs中出現,會建立一個目錄。當一個 kobject 被傳遞給 kobject_add, 它的引用計數被遞增,移出時清除這個引用。移除的函數是kobject_del。sysfs 層級常常比對使用 kset 建立的内部層次,如果 parent 和 kset 都 是 NULL, sysfs 目錄在頂級被建立。


一個 kset有名字, 存儲于嵌入的 kobject.

如前面所說,kset 中的kojbect指向 kobject_type 結構優先于在 kobject 自身中的 ktype 成員.

最後我們需要知道的是,一個kset屬于子系統subsystem。

我們先來看下上述這些的代碼實戰。

3.1   

kset示例代碼

摘自網上,編譯執行後稱為子產品。因為沒有指定parent對象,是以插入到核心後,會在/sys目錄下生成一個檔案夾叫做kobject_test ,該目錄下有一個檔案是kobject_config,可以往該檔案讀寫會觸發相關函數,具體見代碼:

#include <linux/device.h>

#include <linux/module.h>

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/string.h>

#include <linux/sysfs.h>

#include <linux/stat.h>

MODULE_LICENSE("Dual BSD/GPL");

/*聲明release、show、store函數*/

void obj_test_release(struct kobject *kobject);

ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf);

ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count);

/*對應于kobject的目錄下的一個檔案,Name成員就是檔案名*/

struct attribute test_attr = {

                .name = "kobj_config",

                .mode = S_IRWXUGO,

static struct attribute *def_attrs[] = {

                &test_attr,

                NULL,

//kobject對象的操作

struct sysfs_ops obj_test_sysops =

{

                .show = kobj_test_show,

                .store = kobj_test_store,

/*定義kobject對象的一些屬性及對應的操作*/

struct kobj_type ktype =

                .release = obj_test_release,

.sysfs_ops=&obj_test_sysops,

                .default_attrs=def_attrs,

/*release方法釋放該kobject對象*/

void obj_test_release(struct kobject *kobject)

                printk("object_test: release .\n");

/*當讀檔案時執行的操作*/

ssize_t kobj_test_show(struct kobject *kobject, struct attribute *attr,char *buf)

                printk("have show.\n");

                printk("attrname:%s.\n", attr->name);

                sprintf(buf,"%s\n",attr->name);

                return strlen(attr->name)+2;

/*當寫檔案時執行的操作*/

ssize_t kobj_test_store(struct kobject *kobject,struct attribute *attr,const char *buf, size_t count)

   printk("havestore\n");

                printk("write: %s\n",buf);

                return count;

struct kobject kobj;//聲明kobject對象

static int kobj_test_init(void)

                printk("kboject test init.\n");

                //初始化kobject對象kobj,并将其注冊到linux系統

kobject_init_and_add(&kobj,&ktype,NULL,"kobject_test");

                return 0;

static void kobj_test_exit(void)

                printk("kobject test exit.\n");

                kobject_del(&kobj);

module_init(kobj_test_init);

module_exit(kobj_test_exit);

4   

符号連結

sysfs檔案系統的樹結構,反映它代表的kobjects的層次組織. 一個sysfs子樹(/sys/devices

)代表所有的系統已知的裝置,而其他的子樹( 在 /sys/bus 之下 )表示裝置驅動.但是,不代表驅動和它們所管理的裝置間的關系.

展示這些附加關系需要額外的指針, 指針在 sysfs 中通過符号連接配接實作。

 *     

sysfs_create_link - create symlink between two objects.

@kobj:  object whose directory

we're creating the link in.

@target:        object we're

pointing to.

@name:          name of the

symlink.

 */                      

int sysfs_create_link(struct kobject *kobj, struct kobject *target,

                      const char *name)

移除符号:

int

sysfs_create_link_nowarn(struct kobject *kobj, struct kobject

*target, const char *name)

5   

總線

總線是處理器和一個或多個裝置之間的通道,所有的裝置都通過一個總線連接配接. 總線可以插入另一個,例如一個 USB 控制器常常是一個 PCI 裝置,總線由 bus_type 結構代表.

struct bus_type {

const char              *name;//總線類型名字,用于在sysfs檔案系統辨別總線

const char              *dev_name;

struct device           *dev_root;

const struct attribute_group **bus_groups;

const struct attribute_group **dev_groups;

const struct attribute_group **drv_groups;

int (*match)(struct device *dev, struct device_driver *drv);//檢測裝置驅動是否支援特定方法

int (*uevent)(struct device *dev, struct kobj_uevent_env *env);

int (*probe)(struct device *dev);

int (*remove)(struct device *dev);

void (*shutdown)(struct device *dev);

int (*online)(struct device *dev);

int (*offline)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t

state);//進入低功耗的調用方法

int (*resume)(struct device *dev);//改變供電狀态和恢複硬體上下文的方法

int (*num_vf)(struct device *dev);

const struct dev_pm_ops *pm;

const struct iommu_ops *iommu_ops;

struct subsys_private *p;

struct lock_class_key lock_key;

bool force_dma;

name 成員是總線的名字,一個總線包含 2 個 ksets, 代表已知總線的驅動和所有插入總線的裝置, 每個總線是它自己的子系統,在成員subsys_private中。

注冊總線通過函數bus_register,會在/sys/bus中出現:

bus_register - register a driver-core subsystem

@bus: bus to register

Once we have that, we register the bus with the kobject

infrastructure, then register the children subsystems it has:

the devices and drivers that belong to the subsystem.

int bus_register(struct bus_type *bus)

登出使用函數bus_unregister

bus_unregister(struct bus_type *bus)

在bus_type 結構定義的方法,允許總線代碼作為一個裝置核心和單獨驅動之

間的中介,例如match比對裝置和驅動。

如果周遊總線上的裝置和驅動呢?核心提供了很好的API函數,

bus_for_each_dev - device iterator.

@bus: bus type.

@start: device to start iterating from.

@data: data for the callback.

@fn: function to be called for each device.

Iterate over @bus's list of devices, and call @fn for each,

passing it @data. If @start is not NULL, we use that device to

begin iterating from.

 * We

check the return of @fn each time. If it returns anything

other than 0, we break out and return that value.

NOTE: The device that returns a non-zero value is not retained

 * in

any way, nor is its refcount incremented. If the caller needs

 * to

retain this data, it should do so, and increment the reference

count in the supplied callback.

int bus_for_each_dev(struct bus_type *bus, struct device *start,

                     void *data, int (*fn)(struct device *, void *))

這個函數列舉總線上的每個裝置, 傳遞關聯的裝置結構給 fn, 連同作為 data 來傳遞的 值. 如果 start 是 NULL, 列舉從總線的第一個裝置開始; 否則列舉從 start 之後的第 一個裝置開始. 如果 fn 傳回一個非零值, 列舉停止并且那個值從 bus_for_each_dev 返 回.

            周遊總線上的驅動使用函數bus_for_each_drv。功能基本同bus_for_each_dev,隻不過周遊的是驅動。

5.1   

總線屬性

Linux 驅動模型中的每一層都提供一個添加屬性的接口, 并且總線層不例外.

struct bus_attribute {

struct attribute        attr;

ssize_t (*show)(struct bus_type *bus, char *buf);

ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);

可以在編譯時建立和初始化 bus_attribute 結構,使用BUS_ATTR(_name, _mode, _show, _store)

也可以使用類似kobject建立屬性的方法

int bus_create_file(struct bus_type *bus, struct bus_attribute *attr)

void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr)

6   

裝置

Linux 系統中的每個裝置由一個 struct device 代表。

struct device {

struct device           *parent;

struct device_private   *p;

struct kobject kobj;

const char        

     *init_name; /* initial name of the device

*/

const struct device_type *type;

struct mutex            mutex;  /* mutex to synchronize calls to

                                         * its

driver.

                                         */

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 dev_links_info   links;

struct dev_pm_info      power;

struct dev_pm_domain    *pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN

struct irq_domain       *msi_domain;

#ifdef CONFIG_PINCTRL

struct dev_pin_info     *pins;

#ifdef CONFIG_GENERIC_MSI_IRQ

struct list_head        msi_list;

#ifdef CONFIG_NUMA

int             numa_node;      /* NUMA node this device is close to */

const struct dma_map_ops *dma_ops;

u64             *dma_mask;      /* dma mask (if dma'able device) */

u64             coherent_dma_mask;/* Like dma_mask, but for

alloc_coherent mappings as

not all hardware supports

                                             64

bit addresses for consistent

allocations such descriptors. */

unsigned long  

dma_pfn_offset;

struct device_dma_parameters

*dma_parms;

 struct list_head        dma_pools;      /* dma pools (if dma'ble) */

struct dma_coherent_mem *dma_mem; /* internal for coherent mem

override */

#ifdef CONFIG_DMA_CMA

struct cma *cma_area;           /* contiguous memory area for dma

allocations */

/* arch

specific additions */

struct dev_archdata     archdata;

struct device_node      *of_node; /* associated device tree node */

struct fwnode_handle    *fwnode; /* firmware device node */

dev_t                   devt;   /* dev_t, creates the sysfs "dev" */

u32                     id;     /* device instance */

spinlock_t              devres_lock;

struct list_head        devres_head;

struct klist_node       knode_class;

struct class           

*class;

const struct attribute_group **groups;  /* optional groups */

void    (*release)(struct device *dev);

        struct iommu_group     

*iommu_group;

struct iommu_fwspec     *iommu_fwspec;

bool                    offline_disabled:1;

bool                    offline:1;

bool                    of_node_reused:1;

核心提供标準函數device_register将裝置添加到核心資料結構中。

其中有成員

        struct device           *parent;

  parent父裝置是某種總線或者主要制器. 如果 parent 是 NULL, 裝置是一個頂層裝置.

                        kobj代表這個裝置并且連接配接它到層次中的 kobject.

裝置注冊使用函數device_register,登出使用device_unregister。

device_register - register a device with the system.

@dev: pointer to the device structure

This happens in two clean steps - initialize the device

and add it to the system. The two steps can be called

separately, but this is the easiest and most common.

I.e. you should only call the two helpers separately if

have a clearly defined need to use and refcount the device

before it is added to the hierarchy.

For more information, see the kerneldoc for device_initialize()

and device_add().

NOTE: _Never_ directly free @dev after calling this function, even

 * if

it returned an error! Always use put_device() to give up the

reference initialized in this function instead.

 */    

int device_register(struct device *dev)

注冊完畢後可以在/dev/devices目錄下看到裝置,如果是總線裝置則在/dev/devices/[bus_id]/目錄下,登出函數:

void device_unregister(struct device

*dev)

6.1   

裝置屬性

sysfs 中的裝置入口可以有屬性

struct

device_attribute {

        struct attribute       

attr;

        ssize_t (*show)(struct device *dev, struct

device_attribute *attr,

char *buf);

        ssize_t (*store)(struct device *dev, struct device_attribute *attr,

const char *buf, size_t count);

可以通過device_create_file和device_remove_file函數建立和删除。

device_create_file(struct device *dev,

                       const struct device_attribute *attr)

device_remove_file(struct device *dev,

                        const struct device_attribute *attr)

驅動和裝置定義,甚至包括函數都是非常的類似。

7   

驅動

struct device_driver { 

     *name;//驅動程式名字

struct bus_type         *bus;//指向總線描述符的指針,總線連結所支援的裝置         

struct module           *owner;//表示裝置驅動程式子產品

const char              *mod_name;      /* used for built-in modules */

bool suppress_bind_attrs;       /* disables bind/unbind via sysfs */

enum probe_type probe_type;

const struct of_device_id       *of_match_table;

const struct acpi_device_id     *acpi_match_table;

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);     //恢複方法

 const struct attribute_group **groups;

void (*coredump) (struct device *dev);

struct driver_private *p;

裝置驅動程式資料結構體

            通過driver_register注冊裝置驅動程式。

            通常被靜态嵌入到更大的描述符中,例如pci_driver.

            屬性檔案建立和删除如下:

/**    

driver_create_file - create sysfs file for driver.

@drv: driver.       

@attr: driver attribute descriptor.

 */            

int driver_create_file(struct device_driver *drv, const struct driver_attribute *attr)

void driver_remove_file(struct device_driver *drv, const struct driver_attribute *attr)

8   

類是裝置的進階視圖, 它抽象出低級的實作細節。 驅動可以見到一個 SCSI 磁盤或者一個 ATA 磁盤,

但是在類的級别, 它們都 是磁盤.

所有的類都在 sysfs 中在

/sys/class 下出現. 是以, 例如, 所有的網絡接口可在 /sys/class/net 下發現, 不管接口類型. 輸入裝置可在

/sys/class/input 下, 以及串 行裝置在

/sys/class/tty. 一個例外是塊裝置,在

/sys/block和/sys/class/block中。

裝置驅動模型中每個類是由一個class對象描述的。結構體:

struct class {

struct module           *owner;

const struct attribute_group    **class_groups;

const struct attribute_group    **dev_groups;

struct kobject                  *dev_kobj;

int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

char *(*devnode)(struct device *dev, umode_t *mode);

void (*class_release)(struct class *class);

void (*dev_release)(struct device *dev);

int (*shutdown_pre)(struct device *dev);

const struct kobj_ns_type_operations *ns_type;

const void *(*namespace)(struct device *dev);

每個類需要一個唯一的名字,是這個類在 /sys/class 中出現.

建立class使用class_create(owner, name) ,該函數會調用__class_create(owner, name, &__key);,

最後由__class_register函數來進行注冊。

8.1   

類屬性

類的屬性結構如下:

struct class_attribute {

struct attribute attr;

ssize_t (*show)(struct class *class, struct class_attribute *attr,

                        char *buf);

ssize_t (*store)(struct class *class, struct class_attribute *attr,

                        const char *buf, size_t count);

建立類屬性檔案:

class_create_file(struct class *class, const struct class_attribute *attr)

删除類屬性檔案:

class_remove_file(struct class *class, const struct class_attribute *attr)

類的真正目的是作為一個是該類成員的裝置的容器

8.2   

類接口

類子系統有一個在 Linux 裝置模型其他部分找不到的概念. 這個機制稱為一個接口稱為class_interface.

struct class_interface {

struct list_head        node;

int (*add_dev)          (struct device *, struct class_interface *);

void (*remove_dev)      (struct device *, struct class_interface *);

}; 

接口通過函數注冊和登出。

int class_interface_register(struct class_interface *class_intf)

void class_interface_unregister(struct class_interface *);

一個類裝置被加入到class_interface 結構中指定的類時, 接口的 add 函數被調用. 這個函數可進行任何額外的設

置; 當裝置被從類中去 除, remove 方法被調用來進行任何需要的清理.

9   

udev

核心中建立統一驅動模型的主要原因是允許使用者空間動态管理 /dev 樹.

随着 Linux 核心開始安裝到大型伺服器, 許多使用者遇到如何管理大量裝置的問題.

繼續閱讀