为什么需要有设备驱动模型?
早期内核(2.4之前)没有的设备驱动模型的概念,但照样可以用;而到了2.6版本正式引入了设备驱动模型,因为实际的硬件设备越来越多,很多设备都有功耗要求等的
一些新特点,这就导致内核必须有一套更加优秀、更加易用的驱动体系来对设备进行分工管理,这就引入了我们这里所说的设备驱动模型。
设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互等基础设施(sysfs),设备驱动模型目的是简化驱动
程序编写、管理设备和驱动,但是客观上设备驱动模型本身设计和实现很复杂。其实这也就印证了一句话:越好用的东西,其实实现起来就越复杂。
设备驱动模型的底层架构
(1)kobject结构体 (include\linux\kobject.h)
kobject结构体是设备驱动模型底层的一个结构体,这个结构体是设备驱动模型下的所有对象的一个基本单元,他是对设备驱动模型下所有对象抽象出来的共有的部分;
kobject结构体提供了一些公共型的服务:对象引用计数、维护对象链表、对象上锁、对用户空间的表示。
设备驱动模型中的各种对象其内部都会包含一个kobject,地位相当于面向对象思想中的总基类。
(2)kobject结构体元素分析
1 struct kobject { 2 const char *name; // 对象的名字 3 struct list_head entry; // 用来指向平行关系中的下一个kobject结构体对象(可以理解为同一个目录下的多个文件或者文件夹给他们链接起来) 4 struct kobject *parent; // 用来指向父类对象(也就是他的上一层文件夹所对应的对象) 5 struct kset *kset; // 用来指向父类对象的kset 6 struct kobj_type *ktype; // 指向一个kobj_type对象 7 struct sysfs_dirent *sd; 8 struct kref kref; // kobject的引用计数 9 unsigned int state_initialized:1; // 该对象是否被初始化了 的状态标志位 10 unsigned int state_in_sysfs:1; 11 unsigned int state_add_uevent_sent:1; 12 unsigned int state_remove_uevent_sent:1; 13 unsigned int uevent_suppress:1; 14 };
(3)kset结构体元素分析 (include\linux\kobject.h)
1 struct kset { 2 struct list_head list; // 用来链接该目录下的所有kobject对象 3 spinlock_t list_lock; // 自旋锁 4 struct kobject kobj; // 这个kobject就是本目录对应的对象结构体 5 const struct kset_uevent_ops *uevent_ops; 6 };
从kset结构体可以看出来,kobject其实是被kset包含了,kset的主要作用是做顶层kobject的容器类,kset的主要目的是将各个kobject
(代表着各个对象)组织出目录层次架构,可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起。
可以这样理解只要是sysfs中的一个文件夹形式的对象必须是被kset包含进来的。
(4)kset和kobject之间的关系
kset与kobject的关系:kset是kobject的一个容器,可以认为kset是一个目录,而kobject是这个目录下的各个文件,如果这个kset目录下还有子目录,那么这个
目录下就会存在子kset。
kset与kobject、kobject与kobject、kset与kset之间的连接关系:
(1)平行关系下的连接关系:用kobject对象A、kset对象B进行说明
(1)kobject与kobject的连接关系:A通过list_head结构体中的next指针指向下一个kobject对象的list_head结构体(如果A是一个链表尾,则next
指向他上层的kset对象中的list_head结构体),通过list_head结构体中的prev指针指向上一个kobject对象的list_head结构体(如果A是一个链
表头,则prev指向他上层的kset对象中的list_head结构体)。
(2)kset与kobject的连接关系:B通过嵌入内部的kobject结构体中的list_head结构体中的next指针指向下一个kobject对象的list_head结构体(如果
B是一个链表尾,则next指向他的上层的kset中的list_head结构体),通过list_head结构体中的prev指针指向上一个kobject对象的list_head
结构体(如果B是一个链表头,则prev指向他上层的kset中的list_head结构体)。
(3)kset与kset之间的连接关系:B通过嵌入内部的kobject结构体中的list_head结构体中的next指针指向下一个kset中的kobject结构体中的list_head
结构体(如果B是一个链表尾,则next指向他的上层的kset中的list_head结构体),通过list_head结构体中的prev指针指向上一个kset中的
kobject结构体中的list_head结构体(如果B是一个链表头,则prev指向他上层的kset中的list_head结构体)。
所以从上面可以看出来,平行关系的连接是通过kobject下的list_head结构体进行连接的,父类kset是通过kset下的list_head连接他下面的所有对象组成的链表头和链表尾。
(2)上下级关系下的连接关系:属于kset容器下的所有kobject对象通过parent指针指向父类kset中的kobject结构体,通过kset指针指向父类kset。
属于kset容器下的所有kset子容器通过他下面的kobject结构体中parent指针指向父类kset中的kobject结构体,通过kobject结构体中的kset指针指向父类kset容器。
(5)kobj_type结构体
很多书中简称为ktype,每一个kobject都需要绑定一个ktype来提供相应功能
关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store) 关键点2:attribute,提供在sysfs中以文件形式存在的属性,其实就是应用接口
(6)kset、kobject、kobj_type3者之间的关系
设备驱动模型下的总线式设备驱动的组织方式
(1)总线
总线其实是物理上本来就有的,他的英文名bus(公共汽车),总线的作用是将总线两端的设备连接起来,使他们能够通过总线进行通信(数据交互);那么驱动模型中
引入总线其实是借用了总线式的设计优点,与物理总线形成一个对应关系,能够更加方便的管理硬件设备。
(1.1)bus_type结构体 (include\linux\device.h)
bus_type结构体是总线中用来创建一类总线时需要到的结构体,实际的总线根据自身的特点和要求对结构体进行填充形成具体的总线。
1 struct bus_type { 2 const char *name; // 总线名字 3 struct bus_attribute *bus_attrs; // 该总线的属性 4 struct device_attribute *dev_attrs; // 该总线下设备的属性 5 struct driver_attribute *drv_attrs; // 该总线下设备驱动的属性 6 7 int (*match)(struct device *dev, struct device_driver *drv); // 该总线下设备与设备驱动的匹配函数 8 int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 事件函数 热拨插 9 int (*probe)(struct device *dev); // 总线下的 探针函数 10 int (*remove)(struct device *dev); 11 void (*shutdown)(struct device *dev); 12 13 int (*suspend)(struct device *dev, pm_message_t state); 14 int (*resume)(struct device *dev); 15 16 const struct dev_pm_ops *pm; // 电源管理相关的 17 18 struct bus_type_private *p; // 总线的私有数据 p->subsys.kobj 表示该总线在驱动模型中对应的对象 19 };
例如内核中ac97总线的定义: usb总线的定义
(2)设备
(2.1)设备驱动模型中的总线式设计中会包含设备和驱动两个,例如对于usb总线:usb_device和usb_driver ;对于platform总线:platform_device和platform_driver
他们中会包含相应的基类设备和基类驱动:struct device和struct device_driver,就好比和上面的驱动模型中所有对象都包含了基类kobject,所以对于所有具体总线
中的设备结构体中包含了基类struct device,驱动结构体中包含了基类struct device_driver。
而本段谈论的是struct device结构体(include\linux\device.h),也就是设备。
(2.2)struct device是硬件设备在内核驱动框架中的抽象
(2.3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如struct usb_device。就是上面说的被包含到一个具体的总线下的设备结构体中
1 struct device { 2 struct device *parent; 3 struct device_private *p; 4 struct kobject kobj; /* 包含一个kobject结构体,因为属于设备驱动模型中的一个对象 */ 5 const char *init_name; 6 struct device_type *type; 7 struct mutex mutex; 8 struct bus_type *bus; 9 struct device_driver *driver; 10 void *platform_data; /* 这个是一个void类型的指针,用来指向这个设备特有的数据结构,数据结构类型由自己的定义,后面的led驱动就是使用了这个自定义结构体来存放设备的信息:GPIO等,而没有使用总线下的资源结构体 */ 11 struct dev_pm_info power; 12 13 #ifdef CONFIG_NUMA 14 int numa_node; 15 #endif 16 17 u64 *dma_mask; 18 u64 coherent_dma_mask; 19 struct device_dma_parameters *dma_parms; 20 struct list_head dma_pools; 21 struct dma_coherent_mem *dma_mem; 22 struct dev_archdata archdata; 23 24 #ifdef CONFIG_OF 25 struct device_node *of_node; 26 #endif 27 28 dev_t devt; 29 spinlock_t devres_lock; 30 struct list_head devres_head; 31 struct klist_node knode_class; 32 struct class *class; 33 void (*release)(struct device *dev); /* 当我们卸载设备时调用时调用的函数 */ 34 };
(3)驱动(struct device_driver)(include\linux\device.h)
跟上面一样,struct device_driver是驱动程序在内核驱动模型中的抽象,结构体如下:重点是关注name和probe
(4)类(class)
class的真正意义在于作为同属于一个class的多个设备的容器。也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的
同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。所以一个设备可以从类这个角度讲他是属于哪个类,也可以从总线的角度讲
他是挂在哪个总线下的。
mdev(嵌入式中用于自动创建设备节点和设备类的工具)的使用离不开class。
(4.1)相关结构体:struct class (include\linux\device.h)和 struct class_device。
设备驱动模型和设备驱动框架的关系
刚开始学设备驱动模型的时候,很容易搞混,我看老师有时候会把这两个概念搞在一起,我觉得有必要对这两个概念做一下区别。(以下纯属于我自己的理解)
(1)适用性范围:设备驱动框架其实是针对某一类具体的硬件设备而言的,不同的硬件设备都有针对他们本身特点的驱动框架,例如led就会存在led的驱动框架,flash就会有flash的
驱动框架,你就不能把led的驱动框架用在flash硬件设备上。而设备驱动模型之前说了它是用来管理硬件设备、使得驱动编写更加简单的一种软件体系,本身这套体系是很复杂的
,这套体系只有一套,能够对所有的硬件设备都是可行的。
(2)作用:设备驱动框架是内核中维护驱动相关的工程师们针对不同的硬件设备写的标准接口,他们已经将硬件设备相同的属性都封装起来了,留出来一些接口给我们具体的驱动开发工程师
来使用,其实这些标准接口也是使用最原始的内核提供的编写驱动相关的接口函数封装而来的,仅仅是为了使得驱动开发变得简单。而设备驱动模型是内核中构建实现的一套复杂的软件体系,
他的作用并不仅仅限于用在驱动的开发上,更多的是用来设备和设备驱动的管理上,也是为了应对现在越来越多的新特性。
(3)需要注意的是:我们是可以不基于驱动模型和不使用驱动框架来编写一个驱动程序,其实刚开始学习驱动的时候写的驱动就直接使用内核提供的最原始的接口函数来编写驱动的,
这一点是没有问题的,驱动也是能够正常运行的,当然对于复杂的设备可能比较困难。
以上就是为各位朋友分享的相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多等着你!