标签:linux设备驱动模型
1.1Linux设备驱动模型简介
1、什么是设备驱动模型
(1)类class。总线bus(负责将设备和驱动挂接起来)。设备devices、驱动driver(可以看到在驱动源码中,不管是什么样的驱动,都是以struct_xxx_driver来表示的)。Linux设备驱动中的四个框架、分别对应Linux驱动源代码中的四个结构体。四个结构体分别描述Linux设备驱动中的类、总线、设备、驱动,这四个概念。对应的就是设备驱动模型这个概念了,四个模子。
(2)kobject和对象生命周期。kobject是Linux源代码中的一个结构体,高度抽象的结构体,就是Linux内核中所有对象高度抽象出来的类,也就是Linux面向对象中,一个总的父类。
Linux中是如何管理对象的生命周期呢。就是利用了kobject总类中的机制,每个对象都有这种机制,因为kobject是linux中最高的父类,也就是基类,这种机制就会让每个对象能够具有自我管理生命周期的特性。 对象不用时,自己会将自己free掉,就跟调用了析构函数一样。这就是Linux内核虽然是用C写的,但是是面向对象的含义所在。
(3)sysfs,一种虚拟文件系统,作用是将应用层的空间和内核空间中的内容建立起了一个映射关系,就是内核中的一些结构体什么的信息值啊,在sysfs这个虚拟文件系统中以文件的形式展现出来,这样应用层就可以跟内核进行互动。比如在sysfs中的控制led灯的文件中,我们echo一个值进行就可以让led灯亮,这就是sysfs虚拟文件系统为我们提供的机制,让内核和应用层建立起了映射关系。
(4)udev,也是为了实现内核空间和用户空间(应用层)之间的通信,让用户空间可以及时的知道内核空间中发生的事情。比如某个驱动被加载了,或者被卸载了,在用户空间都可以体现,像可以在用户空间用lsmod查看。
什么是设备驱动模型呢,上面的四个东西()就是设备驱动模型。
1、2为什么需要设备驱动模型
1、早期内核(2.4版本之前)是没有统一的设备驱动模型的,但照样可以用。用法就是我们自己去insmod一个驱动,mknod一个设备文件、在驱动对象卸载时,rmmod去卸载,对应的的释放内存的那种方法。
2、2.6版本及以后就引入了设备驱动模型了,我们就可以去class_create,自动创建一个设备驱动等。因为设备越来越多了,需要管理了,所以需要有一个好的体系。所以为了好管理,我们在去写驱动的时候,就会去用设备驱动模型去写驱动了,就是调用框架中提供的成员函数去创建驱动,同时每一个驱动因为都继承自最终的父类kobject,所以都可以在自己驱动消亡的时候,自己知道去释放内存。说白了设备驱动模型就是一种规则,用这种规则去写驱动,可以很好的管理设备驱动,因为设备驱动太多了。
3、设备驱动模型统一实现和维护一些特性:如,电源管理、热插拔、对象自我管理自己的生命周期(内存的释放),用户空间和内核空间之间的信息交互等。
1、3整个Linux驱动开发中的两个点
1、驱动源码本身的编写、调试。重点在于对硬件要很了解,才能写这个硬件的驱动。这样的驱动一般都是厂家写的。
2、驱动什么时候被安装(当insmod时、这种用户层去手动安装的方式是非常老的了,当我们设备接入时驱动自动安装,开机时自动加载,这个是新的方式,也就是利用设备驱动模型去写的驱动。)、驱动中的函数什么时候被调用(在应用层怎么操作一下,就会对应调用驱动中的函数)。这部分和硬件无关,完全和设备驱动模型有关。设备驱动模型是Linux内核提供的,所以我们就是用设备驱动模型。
2、1、设备驱动模型的底层架构(三件套,三个结构体、kobject、kobj_type、kset)
1、kobject(总的结构体,最开始的,相当于父类,其他的结构体都是基于这个结构体构建起来的,所以可以把这个结构体看成一个基类,用C面向过程的角度来看,这个结构体是会被其他结构体包含的,是其他结构体的一个成员)
(1)kobject定义在include/linux/kobject.h中。
(2)是各种对象(总线、设备、驱动、类class等的对象)最基本的单元,提供一些公用型服务,如:对象引用计数、维护对象链表、对象上锁、对用户空间的表示。
(3)设备驱动模型中各种对象其内部都会包含一个kobject结构体,都继承自这个父类,kobject就是基类。
(4)总线、class类、设备、驱动、这几个抽象的类中都会继承kobject,所以kobject有的属性和行为,总线、class类、设备、驱动都会有。
(5)struct kobject {
const char*name;//来描述当前这个模型对象的名字
struct list_headentry;//用来将来维护对象链表,维护统一模型对象的链表,比如可以遍历驱动的所有对象、设备的所有对象、总线的、class的。
struct kobject*parent;//上下层之间的挂接、不是平行的挂接,比如驱动的对象和总线的对象进行挂接,就需要这个成员变量来进行挂接。
struct kset*kset;//对象上锁的功能。当用当前这个对象要进行敏感操作时,如果用总线的对象进行敏感操作,为了防止其他人用这个对象
//就需要对这个对象进行上锁,就可以用这个成员变量来进行,防止其他人用这个总线的对象,当自己操作完事之后在
//进行解锁。
struct kobj_type*ktype;//用来对对象在用户空间的表示的,将来在sysfs中能看到设备的驱动等。
struct sysfs_dirent*sd;
struct krefkref;//用来对对象进行引用计数的,当前的对象被别人用了几次,来统计该模型(总线、class、设备、驱动)的对象被他人用的次数。
//就是让自己记住,同时被几个人调用了。帮助维护对象的生命周期的,当当前这个对象的这个引用计数为0时,就表示没有东西
//在用当前这个对象了,没有用东西要靠当前这个对象来完成任务了,这时引用计数为0,就可以释放这个对象的内存了。
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;
};
struct kref {//atomic_t是原子操作,就是操作是不可分割的。
atomic_t refcount;//refcount,用来对对象的引用计数的,用了几个对象。
};
typedef struct {
int counter;
} atomic_t;
(6)kobject相当于面向对象中的总的基类。总线、设备、class、驱动这些抽象的结构体中都会有这个kobject结构体。
2、kobj_type
(1)很多书中简称ktype,每一个kobject都需要绑定一个ktype来提供相应的功能,kobject这个总的基类中有一个成员变量为ktype,这个成员变量将来来绑定一个struct kobj_type。
(2)绑定和包含的语义区别:包含指的是结构体肚子中有一个实实在在存在的成员,不需要我们来指定,内部本来就有了。绑定的意思是指,结构体内部中本来没有这个东西,但是提供了一个指向这个东西的指针,将来我们在用的时候的时候,需要手动的去绑定这个东西,就是将一个东西的指针赋值给这个指针,这样叫做绑定。
(3)struct kobj_type的定义,凡是手动绑定了这个kobj_type结构体的kobject,都会有kobj_type中的功能,其实就相当于为了kobject提供了成员函数,只不过
这些成员函数最开始并不是在kobject中存在的,这些成员函数是存在一个kobj_type结构体中的,当我们需要了某些行为的时候,比如需要kobj_type中这些成员
函数功能时,就会手动的去让kobject中的struct kobj_type类型的指针变量去指向这个strct kobj_type有相应成员函数的对象。达到是的kobject中有了相应的
成员函数的目的。将来在设备驱动模型(总线、设备、class、驱动)继承这个kobject时,也会有这些行为的存在。
struct kobj_type {//这个kobj_type的功能就是提供给我们在sysfs目录下对文件的那些操作
void (*release)(struct kobject *kobj);//释放,如果kobject手动绑定了这个kobj_type,在这个函数中会去检查kobject中记录引用计数的那个成员
//,如果发现引用计数不为0,说明还有人在用当前对象,那么就会将引用计数减1,如果进到这个release函数
//中,发现引用计数为0了,说明现在是最后一个用这个对象的人也进行调用这个release函数进行释放对象了,
//则会将当前对象进行释放,内存释放,这也就是为什么这个函数的名字不用close,而用release的含义所在。
//close表示调用一次就会关闭所有的,但是release就没有close语义上那么强硬了,并不是调用一次就关闭所有
//的,而实际上是要看当前对象被引用的次数有没有变成0.
//看引用计数的方法很简单,因为函数参数是struct kobject *,所以通过这个参数就可以找到 struct kref,从
//而可以知道当前被引用的次数
const struct sysfs_ops *sysfs_ops;//这个指针,指向的就是我们在sysfs目录下所操作那个文件对应的执行函数
struct attribute **default_attrs;//这个attribute就是属性,就是我们在sysfs目录里面看到的那些文件名,每一个文件对应一个属性,比如之前led驱动中的bright亮度这个属性
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
@1://Kobj_type中的这个sysfs_ops结构体指针,这个结构体struct sysfs_ops的封装是
struct sysfs_ops {
ssize_t(*show)(struct kobject *, struct attribute *,char *);//这个就是操作sysfs时的show方法,对应的就是我们去读取这个文件,当我们cat这个文件的时候,对应的
//就是这个show方法
ssize_t(*store)(struct kobject *,struct attribute *,const char *, size_t);//这个就是操作sysfs的store方法,对应的就是我们去写入这个文件,当我们echo向这个文件中
//写入东西的时候,对应的就是这个store方法
};
@2:struct kobj_type的两个关键点就是:
关键点1:sysfs_ops,提供该对象在sysfs中的操作方法(show和store,对应的就是我们cat和echo时内部所执行的函数)
关键点2:attribute,属性,提供在sysfs中以文件形式存在的属性(比如,led驱动中的bright亮度这个属性文件,其次就是应用接口)
(4)kobject中的kset
struct kset {
struct list_head list;//包含了一个链表,用来将kset链接起来
spinlock_t list_lock;//包含了一个自旋锁
struct kobject kobj;//表示kset里面可以内置一个kobject,就是kset中包含了一个kobject,包含就是里面有一个kobject。也就是说,kset里面包含了一个kobject,但是kobject
//里面有一个kset指针指向了kset(kobject里面绑定了一个kset)
const struct kset_uevent_ops *uevent_ops;
};
@1:kset的主要作用是做顶层kobject的容器类
@2:kset的主要目的是将各个kobject(代表着各个对象)组织出目录层次架构
@3:可以认为kset就是为了sysfs中弄出目录(kset直观看就是个目录,实现目录间的上下层结构的),从而让设备驱动模型中的多个对象能够有层次有逻辑性的组织在一起
总结:总的来说,上面的kobject,kobj_type,kset,这些设备驱动模型的底层架构,就是为了实现sysfs虚拟文件系统中的那些玩意的,kobject是基础,kobj_type就是sysfs中对应的文件,
和操作这些文件的基础(attribute属性,一个属性就对应一个文件,sysfs_ops对应的就是对属性文件的show方法和store方法,kset是构建sysfs虚拟文件系统中目录层次架构的),kset容器。
3、总线式设备驱动的组织方式(这部分是在Kobject底层架构之上的一个层次,这部分的代码也是写内核的人写的,但是我们写驱动会用到这里的一部分代码,越往上越靠近我们写驱动,越往
下,越靠近写内核的这些人)
(1)总线:物理上和CPU中都是用来连接东西的
(2)linux内核在设计的时候,采用的就是抽象的总线式设计的。
(3)驱动框架中的总线式设计
@1:比如CPU外面有50个设备,这50个设备就要有50个驱动进行对应,不然这些设备怎么工作呢,这种驱动和设备的对象程度要达到,一旦这个设备接入到了CPU中,这个驱动马上就跟这个设备对
应上了,一旦这个设备不存在了,这个驱动马上就会卸载掉。
@2:在Linux内核中,是以总线式的方法来管理设备和驱动之间的关系的。管理的方式:首先操作系统创建一些总线,比如USB总线、PCI总线等,总线由操作系统来管理,设备和驱动又由总线来
管理,比如USB总线又分为两个分支,一个分支是设备的分支,一个分支是驱动的分支,把所有已经注册了的USB类的设备放到USB总线下面的设备分支去管理,只要是USB的驱动,就把它放到
USB总线的驱动分支里面去管理,USB总线下面的那些很多个设备的联系肯定是由一个链表将他们连接起来的,USB驱动那边也有很多个驱动,也肯定是由一个链表连接起来的。这样当我们有一个
USB设备直接插入到了系统中,系统检测到了这个设备,就会将这个USB设备添加到USB总线下的USB设备分支下面的链表中去进行管理,这样我们的系统中就注册了一个新的USB设备了,那么这个
设备有没有驱动呢,因此系统就会在USB总线下的USB驱动分支中的链表中去找有没有这个USB设备对应的USB驱动,如果找到了,两者配上对了,就可以直接使用了,如果在USB总线的USB驱动分支
中的链表里没有找到这个USB设备的USB驱动,那么就会像上层进行汇报表明这个新插入的USB设备是没有驱动的。这套找的和管理的方法,是由USB总线来负责的,并不是由Linux内核来负责的。
所以在Linux内核中,总线是用来管理设备和驱动的,查找设备对应的驱动,找的了进行配对,没找到进行汇报,删除驱动等,这些设备和驱动的管理都是有总线来管理的,比如上面说的USB总线
,而Linux内核却不需要参与其中,因此Linux内核就像是总经理,他只需要管理总线就行了,而总线就像是部门主管,他需要管理他下面的兵,也就是设备和驱动,这种管理设备和驱动的方法。
就叫做总线式管理设备和驱动的方法。总线就像是中层管理人员,负责承上启下,对上由Linux内核负责,对下管理设备和驱动。
(3)bus_type结构体(总线对应的结构体,就是一个总线模板,有I2C总线的bus_type,usb总线的bus_type),关键是match函数和uevent函数
struct bus_type {//这个就是总线的模子,类,是一个抽象,抽象出了总线所应该有的功能,将来所要管理的事
const char*name;//总线的名字,当这个总线注册上的时候,在操作系统的sys目录下的bus中是可以看到的,里面全是总线,比如在AC97这个总线中,里面就有两个目录,一个
//是设备device目录,一个driver驱动目录,可以看出来总线下面确实是有两个分支的,一个设备的分支(所有注册在AC97总线中的设备),一个驱动的分支(
//所有注册在AC97这个总线下面的驱动)
struct bus_attribute*bus_attrs;//总线本身自己所拥有的属性(在总线目录下),就是在sys/bus/ac97这个总线目录下看到的文件,属性就是文件
struct device_attribute*dev_attrs;//就是进入到该名字的总线中的device目录下时,device拥有的属性(device目录下的文件)
struct driver_attribute*drv_attrs;//就是进入到该名字的总线中的driver目录下时,driver拥有的属性(driver目录下的文件)
int (*match)(struct device *dev, struct device_driver *drv); //总线模板中的这个函数,就是用来做该总线下的设备(device这个分支)和驱动(driver这个分支)之间的匹配的
//改总线下的每一个设备和驱动之间的关系就是由这个match方法来管理的
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;//这个dev_pm_ops里面全是函数指针,是一个函数指针集,是跟电源管理相关的,总线中的电源管理函数全在这里,比如让这个总线上的所有设备全都休眠
//,就可以调用这个函数指针集中的相应的函数来让这个总线上的所有设备全部进入到休眠状态
struct bus_type_private *p;
};
4、设备(总线下管理的那些设备)
(1)struct device是硬件设备在内核驱动框架中的一种抽象,每用一个这个结构体类型产生一个变量(对象),就代表我们系统里面的一个硬件设备,这个结构体中的信息是所有的设备所共有
的一些信息。填充时就是填充的这些设备共有的信息。
(2)device_register用于向内核驱动框架中注册一个设备,我们自己写驱动的时候,就需要用这个device_register去注册这个设备,这个函数是由内核驱动框架提供的,给驱动开发者使用的,
比如要写一个U盘的驱动,就需要注册一个U盘的设备,比如要写一个声卡的驱动,就需要注册一个声卡的设备,设备里面有很多的信息,如使用的中断号等
(3)通常device不会单独使用(是作为一个父类被继承的),而是被包含在一个具体设备结构体中,如struct usb_device(这个结构体则是指某一个具体的硬件设备,所以里面要继承自device
这个结构体,也就是要包含所有设备所共有的那部分信息)。struct device就是父类,usb_device就是子类,子类比父类更加具体。
5、驱动(总线下管理的那些驱动)
(1)struct device_driver是驱动程序在内核驱动框架中的抽象
(2)关键元素1:name,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
(3)关键元素2:probe,驱动程序的探测函数(probe函数是用总线式驱动框架来设计驱动时才需要用到的,百分之80多的驱动是要用总线式的方式来完成的,因为将来设备多了便于管理)
,用来检测一个设备是否可以被该驱动所管理。probe函数是总线式管理的设备和驱动中,非常重要的函数,是驱动的入口。地位相当于我们以前写的驱动中的insmod时调用的函数
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;
};
总结:总线管理设备和驱动。我们经常写驱动时,需要用总线式来进行设计,用总线模板填充一个我们的总线,用设备模板填充一个我们的设备,用驱动模板填充一个我们的驱动,用总线来管理。
bus_type总线模板,比如I2C总线会有I2C_bus_type表示是i2c中。struct device 挂在总线上的设备公用功能部分,struct usb_device,device前面加上设备的名字,此时这个结构中包含dev
ice公有的部分,同时包含usb设备自有的东西,该设备中有指向表示是哪个bus_type总线的指针,也有表示自己设备的名字,struct device_driver 设备的驱动,这个里面就包含了设备的驱动代码部分,同时也有对应的名字,里面也有一个指向bus_type总线的指针,用来表示这个驱动是属于哪个总线下的,包含了改驱动的哪些操作方法等,也有probe探测函数,设备和驱动所指向的同一个bus_type总线中有一个mach函数,用来将设备和驱动进行匹配在一起,匹配规则是用名字来匹配的。 总线、设备和驱动三者是一组的。
6、类
(1)相关结构体:struct class 和 struct class_device
(2)udev的使用离不开class,udev的功能,就是热插拔的功能,就是在系统运行的时候,有一个设备突然插入到了这个系统中,想用udev就需要用到class,udev/mdev的热插拔功能就是用这个class类来实现的
(3)class的真正意义在于作为同属于一个class的多个设备的容器,也就是说,class是一种人造的概念,目的就是为了对各种设备进行分类管理。当然,class在分类的同时还对每个类贴上了一
些“标签”,这也是设备驱动模型为我们写驱动提供的基础设施。一方面某个设备是从属于某个总线的,另一方面某个设备也是丛属于某个class的。比如插USB接口的可以是U盘,也可以是USB
摄像头,他俩属于同一个总线,但是不属于同一个类,所以一个设备是接受双重管理的。也就是说,一个设备是由某个总线管理的,同时这个设备又属于某一个类,所以在总线目录下找到了这个
设备,很有可能在class类中的不同类中也找到了这个设备。
(4)sys目录下的device才是真正的设备,bus下面的device是说明这个device是由哪个总线管理的,最终进入到这个设备中都会符号链接跳转到sys目录下的device中
本文出自 “whylinux” 博客,谢绝转载!
(kobject、ktype、kset,bus_type、device、device_driver)
标签:linux设备驱动模型
原文地址:http://whylinux.blog.51cto.com/10900429/1905466