来自朱有鹏老师的课堂笔记,如有侵权,马上删
参考博文:设备驱动模型底层架构
LINUX设备驱动模型底层架构及组织方式
设备驱动模型
一、linux设备驱动模型简介
1、什么是设备驱动模型
设备驱动模型其实是Linux内核为了管理硬件上的设备和对应的驱动制定的一套软件体系。那么其实设备驱动模型是一个比较抽象、比较广的一个概念,一两句话是很难说清楚的,类(class)、总线(bus)、设备(device)、驱动(driver)、mdev(自动创建设备节点和设备类)sysfs等都属于设备驱动模型的范畴。
(1)类class
、总线bus
、设备device
、驱动driver
1、都是 Linux 里面的一个结构体。
2、每一个结构体变量都能代表一个实例。
(2)kobject和对象生命周期
kobject:kernel object 处在最高层的模型,包含所有的模型。
对象生命周期:有一种机制,让对象可以自动 free,自动释放内存空间。
(3)sysfs
可以 cat 、echo
(4)udev
自动创建设备节点和设备类。(class_creat 、 device_creat)
2、为什么需要设备驱动模型
(1)早期内核(2.4之前)没有统一的设备驱动模型,但照样可以用
(2)2.6版本中正式引入设备驱动模型,目的是在设备越来越多,功耗要求等新特性要求的情况下让驱动体系更易用、更优秀。
(3)设备驱动模型负责统一实现和维护一些特性,诸如:电源管理、热插拔、对象生命周期、用户空间和驱动空间的交互、等基础设施
(4)设备驱动模型目的是简化驱动程序编写,但是客观上设备驱动模型本身设计和实现很复杂。
3、优点如下:
(1)代码重用。将对象抽象为总线、驱动、设备三种,各司其职。同一总线的多个驱动使用相同的总线对象。同一驱动可以关联驱动多个设备。
(2)通过sysfs文件系统,清晰了展示内核驱动模型中的层次关系。同时sysfs文件系统还提供了方便的同用户控件交互的接口。
4、驱动开发的2个点
(1)驱动源码本身编写、调试。重点在于对硬件的了解。
硬件的驱动:生产硬件的厂家会搞定。
(2)驱动什么时候被安装、驱动中的函数什么时候被调用。跟硬件无关,完全和设备驱动模型有关。
真正智能是:自动安装,自动卸载,实现热插拔。
二、设备驱动模型的底层架构
1、kobject结构体
1、这个结构体相当于一个总基类,被其他结构体所继承。
2、所以其他结构体当中包含这个基类。
(1)定义在 linux/kobject.h
中
struct kobject {
const char *name; //kobject的名字,且作为一个目录的名字
struct list_head entry; //连接下一个kobject结构
struct kobject *parent; //指向父亲kobject结构体,如果父设备存在
struct kset *kset; //指向kset集合
struct kobj_type *ktype; //指向kobject的属性描述符
struct sysfs_dirent *sd; //对应sysfs的文件目录
struct kref kref; //kobject的引用计数
unsigned int state_initialized:1; //表示该kobject是否初始化
unsigned int state_in_sysfs:1; //表示是否加入sysfs中
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
(2)kobject是驱动、总线、设备、各种对象最基本单元。它提供一些公用型服务如:
1、对象引用计数:观察当前这个对象是否仍然有在被使用。
2、维护对象链表:
3、对象上锁:两个进程要访问全局变量的时候,这个时候就需要上锁。
4、对用户空间的表示:
(3)每一个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录,而不是文件。
2、kobj_type结构体
struct kobj_type {
void (*release)(struct kobject *kobj);//释放kobject和其占用的函数
const struct sysfs_ops *sysfs_ops; //操作一个属性数组的方法
struct attribute **default_attrs; //属性数组的方法
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
(1)很多书中简称为ktype
,每一个kobject都需要绑定一个ktype来提供相应功能
区别绑定和包含:
绑定:通过某种方式可以找到这个东西:结构体指针。
包含:实实在在包含 结构体变量。
struct kobject {
struct kobj_type *ktype; (绑定)
struct kref kref; (包含)
};
(2)关键点1:sysfs_ops
,提供该对象在sysfs中的操作方法(show和store)
struct sysfs_ops
{
ssize t (*show) (struct kobject *, struct attribute *, char *);/*读属性操作函数*/
ssize t (*store) (struct kobject *,struct attribute *,const char *buf, size_t count);/*写属性操作函数*/
}
show()
函数用于读取一个属性到用户空间。函数的第1个参数是要读取的kobect的指针,它对应要读的目录;第2个参数是要读的属性;第3个参数是存放读到的属性的缓存区。当函数调用成功后,会返回实际读取的数据长度,这个长度不能超过PAGESIZE个字节大小。
store()
函数将属性写入内核中。函数的第1个参数是与写相关的kobject的指针,它对应要写的目录:第2个参数是要写的属性;第3个参数是要写入的数据;第4个参数是要写入的参数长度。这个长度不能超过PAGE-SIZE个字节大小。只有当拥有属性有写权限时,才能调用store0函数。
(3)关键点2:attribute
,提供在sysfs中以文件形式存在的属性,其实就是应用接口
struct attribute {
const char *name; //属性的名字
struct module *owner;//指向用于该属性的模块,已经不常使用
mode_t mode; //属性读写权限
};
3、kset
struct kset {
struct list_head list; //连接链表
spinlock_t list_lock; //链表的自旋锁
struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
const struct kset_uevent_ops *uevent_ops; //热插拔事件
};
(1)同样也是被每一个kobject都需要绑定的结构体,但是 kset 当中包含kobject
(2)kset的主要作用是做顶层kobject的容器类
容器类:用来装载 kobject 。
(3)kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
(4)可以认为kset就是为了在sysfs中弄出目录,从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
三、总线式设备驱动组织方式
1、总线
(1)物理上的真实总线及其作用(英文bus)
(2)硬件上:芯片当中的总线:
(3)软件上:驱动框架中的总线式设计:
可以发现总线下面包含:
devices
和drivers
两个东西。
match 匹配函数: 就是匹配该总线下面的devices
和drivers
(4)bus_type
结构体,关键是 match
函数和 uevent
函数
struct bus_type {
const char *name; //总线类型名
struct bus_attribute *bus_attrs; //总线属性和导出到sysfs中的方法
struct device_attribute *dev_attrs; //设备属性和导出到sysfs中的方法
struct driver_attribute *drv_attrs; //驱动程序属性和导出到sysfs中的方法
//匹配函数,检验参数2中的驱动是否支持参数1中的设备
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 (*suspend)(struct device *dev, pm_message_t state);//改变设备供电状态,使其节能
int (*resume)(struct device *dev); //恢复供电状态,使其正常工作
const struct dev_pm_ops *pm; //关于电源管理的操作符
struct bus_type_private *p; //总线的私有数据
};
(4)Linux系统内核使用 bus_type 结构体表示总线。应用例子:内核中的platform
总线以及pci
总线
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR,
};
2、设备
(1)struct device 是硬件设备在内核驱动框架中的抽象
(2)device_register 用于向内核驱动框架注册一个设备
(3)通常device不会单独使用,而是被包含在一个具体设备结构体中,如 struct usb_device
device :是一个基类,会被其他类所继承。
设备驱动模型中的总线式设计中会包含设备和驱动两个,例如对于:
1、platform总线:
platform_device
和platform_driver
;
2、pci总线:pci_device
和pci_driver
。
它们中会包含相应的基类设备和基类驱动:struct device
和 struct device_driver
,就好比和上面的驱动模型中所有对象都包含了基类kobject,所以对于所有具体总线中的设备结构体中包含了基类struct device,驱动结构体中包含了基类struct device_driver。
struct device {
struct klist_klist children;/*连接子设备的链表*/
struct device *parent;/*指向父设备的指针*/
struct kobject kobj;/*内嵌的kobject结构体*/
char bus_id[BUS ID SIZE];/*连接到总线上的位置*/
unsigned uevent suppress:1;/*是否支持热插拔事件*/
const char init_name;/*设备的初始化名字*/
struct device_type *type;/*设备相关的特殊处理函数*/
struct bus_type *bus;/*指向连接的总线指针*/
struct device_driver *driver;/*指向该设备的驱动程序*/
void *driver data;/*指向驱动程序私有数据的指针*/
struct dev_pm info power;/*电源管理信息*/
dev t deyt;/*设备号*/
struct class *class;/*指向设备所属类*/
struct attribute_group **groups;/*设备的组属性*/
void (*release) (struct device *dev);/*释放设备描述符的回调函数*/
}
- 设备属性
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);
};
3、驱动
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 */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
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;//电源管理
struct driver_private *p;//设备驱动私有数据
};
(1)struct device_driver 是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name
,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2:probe
,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理
4、类
(1)相关结构体:struct class
和 struct class_device
(2)udev
的使用离不开class
struct class {
const char *name; //类名称
struct module *owner;
struct class_attribute *class_attrs; //class给自己添加的属性
const struct attribute_group **dev_groups; //class给所包含的设备添加的属性
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 (*suspend)(struct device *dev, pm_message_t state); //设备休眠时调用
int (*resume)(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;
};
(3)class的真正意义在于作为同属于一个class的多个设备的容器。
也就是说,class是一种人造概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。
总结
(1)模型思想很重要,其实就是面向对象的思想
(2)全是结构体套结构体,对基本功(语言功底和大脑复杂度)要求很高
5、设备驱动模型 和 设备驱动框架的关系
刚开始学设备驱动模型的时候,很容易搞混,我看老师有时候会把这两个概念搞在一起,我觉得有必要对这两个概念做一下区别。(以下纯属于我自己的理解)
(1)适用性范围:
设备驱动框架其实是针对某一类具体的硬件设备而言的,不同的硬件设备都有针对他们本身特点的驱动框架,例如led就会存在led的驱动框架,flash就会有flash的驱动框架,你就不能把led的驱动框架用在flash硬件设备上。
而设备驱动模型之前说了它是用来管理硬件设备、使得驱动编写更加简单的一种软件体系,本身这套体系是很复杂的,这套体系只有一套,能够对所有的硬件设备都是可行的。
(2)作用:
设备驱动框架是内核中维护驱动相关的工程师们针对不同的硬件设备写的标准接口,他们已经将硬件设备相同的属性都封装起来了,留出来一些接口给我们具体的驱动开发工程师来使用,其实这些标准接口也是使用最原始的内核提供的编写驱动相关的接口函数封装而来的,仅仅是为了使得驱动开发变得简单。
而设备驱动模型是内核中构建实现的一套复杂的软件体系,他的作用并不仅仅限于用在驱动的开发上,更多的是用来设备和设备驱动的管理上,也是为了应对现在越来越多的新特性。
(3)需要注意的是:我们是可以不基于驱动模型和不使用驱动框架来编写一个驱动程序,其实刚开始学习驱动的时候写的驱动就直接使用内核提供的最原始的接口函数来编写驱动的,这一点是没有问题的,驱动也是能够正常运行的,当然对于复杂的设备可能比较困难。