linux驱动设备模型详解

简介:

本篇是关于linux的设备模型,将会覆盖相关的绝大部分知识,以及实践操作。希望这篇材料让大家基本掌握linux驱动设备模型,从而写出更加优秀的linux驱动。

linux统一的设备模型,提供了对电源管理和系统关机 用户空间的通讯、可热插拔设备 设备类别 对象生命期 的广泛支持。

Linux 设备模型代码负责所有这些方面, 驱动代码作者只需要充分信任这些代码即可,但是, 理解设备模型绝对是一个好事情.

Linux2.6内核引入了sysfs文件系统,是与proc同类别的文件系统。sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到。

有疑问联系:perftrace@gmail.com

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;

#endif

};

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 *kobj, const struct bin_attribute *attr)

这里的属性是使用bin_attribute结构体,里面分装了attribute

3    kset

通过kset数据结构图可将kobjects组织成一颗层次树。Kset是同类kobjects的一个集合体,也就是说相关的kobjects包含在同类型的容器中。

其数据结构如下:

struct kset {

        struct list_head list;  //包含在ksetkobject链表

        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");

/*声明releaseshowstore函数*/

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

void 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;

#endif

#ifdef CONFIG_PINCTRL

        struct dev_pin_info     *pins;

#endif

#ifdef CONFIG_GENERIC_MSI_IRQ

        struct list_head        msi_list;

#endif

 

#ifdef CONFIG_NUMA

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

#endif

        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 */

#endif

        /* 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, 设备是一个顶层设备.

struct kobject kobj;

                        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函数创建和删除。

int device_create_file(struct device *dev,

                       const struct device_attribute *attr)

void device_remove_file(struct device *dev,

                        const struct device_attribute *attr)

驱动和设备定义,甚至包括函数都是非常的类似。

7    驱动

struct device_driver { 

        const char              *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;

 

        const struct dev_pm_ops *pm;

        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 {

        const char              *name;

        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);

 

        const struct dev_pm_ops *pm;

 

        struct subsys_private *p;

};

每个类需要一个唯一的名字,是这个类在 /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;

        struct class            *class;

       

        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 内核开始安装到大型服务器, 许多用户遇到如何管理大量设备的问题.

 

 

目录
相关文章
|
3月前
|
缓存 安全 Linux
Linux 五种IO模型
Linux 五种IO模型
|
3月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
43 6
|
3月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
49 5
|
3月前
|
存储 缓存 Unix
Linux 设备驱动程序(三)(上)
Linux 设备驱动程序(三)
38 3
|
3月前
|
Linux
Linux 设备驱动程序(四)
Linux 设备驱动程序(四)
21 1
|
3月前
|
存储 数据采集 缓存
Linux 设备驱动程序(三)(中)
Linux 设备驱动程序(三)
35 1
|
3月前
|
存储 前端开发 大数据
Linux 设备驱动程序(二)(中)
Linux 设备驱动程序(二)
29 1
|
3月前
|
缓存 安全 Linux
Linux 设备驱动程序(二)(上)
Linux 设备驱动程序(二)
40 1
|
2月前
|
Linux API
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
Linux里的高精度时间计时器(HPET)驱动 【ChatGPT】
|
3月前
|
存储 缓存 安全
Linux 设备驱动程序(三)(下)
Linux 设备驱动程序(三)
32 0