加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Linux内核之设备驱动-driver model结构

(2016-08-21 12:42:32)
标签:

linux

设备驱动

驱动模型结构

分类: linux

Linux内核之设备驱动-driver model结构

    如表1-1Linux设备模型包含以下四个基本结构:

类型

所包含的内容

内核数据结构

对应/sys

设备(Devices)

设备是此模型中最基本的类型,以设备本身的连接按层次组织

struct device

/sys/devices.../

驱动(Drivers)

在一个系统中安装多个相同设备,只需要一份驱动程序的支持

struct device_driver

/sys/bus/pci/drivers

类别(Classes)

这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/

struct class

/sys/class

       

 

设备

具有特定功能的硬件,比如键盘、鼠标等。

device 数据结构

struct device {

       struct klist             klist_children;

       struct klist_node     knode_parent; 

       struct klist_node     knode_driver;

       struct klist_node     knode_bus;

       struct device          *parent;

 

       struct kobject               kobj;

       char                      bus_id[BUS_ID_SIZE];  

       unsigned                uevent_suppress:1;

       const char             *init_name;    

 

       struct bus_type      *bus;             

       struct device_driver *driver;         

       void                      *driver_data;   

 

       dev_t                    devt;                   

 

       struct klist_node     knode_class;

       struct class            *class;

       struct attribute_group **groups;   

 

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

 

 

};

klist_children

子设备双向链表头,用于指向该设备所有子设备链表的表头。通过此表头可以查找所有子设备。操作函数如下:

int device_for_each_child(struct device *parent, void *data,

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

struct device *device_find_child(struct device *parent, void *data,

int (*match)(struct device *dev, void *data));

这两个函数的区别是:前者遍历子设备,并作fn()处理,如果fn()返回非0值,则停止遍历,返回错误码;后者是遍历子设备,并作match()处理,如果match()返回非0,则调用get_device()获取对应的子设备(将对应的子设备参考值曾1),然后停止遍历,返回子设备。注意后者获取的子设备用完后,需要用put_device()去除子设备。

knode_parent

父设备的子设备集节点,用于链入父设备的klist_children链表中。

knode_driver

设备所属驱动的设备集节点,用于链入具有相同驱动的设备链表中。该链的表头在device_driver->p ->klist_devices

knode_bus

设备所属总线的设备集节点,用于链入具有相同总线的设备链表中。该链的表头为bus_type->p -> klist_devices

*parent

设备的父设备,即该设备所属的设备。在大多数情况下,一个父设备通常是某种总线或者是宿主控制器。

kobj

表示该设备并把该它连接到sysfs体系中的kobject

bus_id[BUS_ID_SIZE]

在总线上唯一标识该设备的字符串。

uevent_suppress

过滤该设备的uevent事件标志位。为1,则过滤。

*init_name

设备初始化名称,device_add()里将该值拷贝给bus_id,因此也表示总线上该设备标识。注意,文件系统中显示的设备名称并非此处指定,而是由该设备对应的kobj中指定。

*bus

标识了该设备连接在何种类型的总线上。

*driver

管理该设备的驱动程序。在下一节中将介绍device_driver结构。

*driver_data

由设备驱动程序使用的私有数据成员。

devt

32位主从设备号,高12位为主设备号,低20位为从设备号。

knode_class

设备所属类的设备集节点,用于链入具有相同类的设备链表中。该链的表头为class->p -> class_devices

*class

标识设备所属类。

**groups

设备属性组,会体现到sysfs系统中,用户可操作。

*release

当指向设备的最后一个引用被删除时,内核调用该方法;它将从内嵌的kobjectrelease方法中调用。所有指向核心注册的device结构都必须有一release方法,否则内核将打印出错误信息。

 

1-2给出了设备与总线、驱动等连接关系。文件系统子框表明设备是通过kobj将自己与sysfs中的文件夹连接到一起的。子设备、总线、驱动、类等子系统给设备进行相应分类,并使用klistklist_node将它们链到一起。每个子系统同时提供***_for_each_*** ***_find_***两个遍历函数来查找并操作指定设备。

http://hi.csdn.net/attachment/201007/28/0_1280284181GRZ6.gifmodel结构" />

 

2-2:设备结构图

方法

设备架构初始化:

int __init devices_init(void);

1 sysfs目录下创建设备子集(kset)和目录/sys/devices;并将设备通用事件处理函数device_uevent_ops注册给该设备子集。之后所有设备都会添加到devices目录下。

2另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store

3 kset注册成功后,向用户空间发送添加对象的uevent事件。

4 创建目录/sys/dev,并在该目录下创建两个目录/sys/dev/block/sys/dev/char这两个目录下维护一个按字符设备/块设备的主次号码(major:minor)链接到真实的设备(/sys/devices)的符号链接文件。

 

设备注册/去注册函数:

int device_register(struct device *dev);

void device_unregister(struct device *dev);

注册函数先后执行的操作有:获取设备所在子集,初始化设备数据,将设备加入到sysfs系统中,增加设备默认属性及操作方法,加设备到类设备中,加设备到总线,向用户空间发送添加设备uevent事件,尝试为设备获取驱动。

采用此方法注册的设备都会获取释放对象(device_release)及处理所属属性(dev_attr_show/dev_attr_store)的方法(也就是kobj_type结构)。

如果用户空间程序用sysfs来读取设备属性的值,sysfsread函数先调用的是对象属性处理函数dev_attr_show,因为sysfs能处理或者说能看得见的只有对象级的东西,我们从图3的图中也可表明这一点。

去注册是注册的逆向操作。去注册后,用户不可再操作设备。

 

遍历子设备:

int device_for_each_child(struct device *parent, void *data,

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

struct device *device_find_child(struct device *parent, void *data,

int (*match)(struct device *dev, void *data));

函数说明请参考前一节数据结构分析中klist_children项。

 

设备通用事件(uevent)处理函数:

static int dev_uevent_filter(struct kset *kset, struct kobject *kobj);

static const char *dev_uevent_name(struct kset *kset, struct kobject *kobj);

static int dev_uevent(struct kset *kset, struct kobject *kobj, struct kobj_uevent_env *env);

dev_uevent_filter设备级事件过滤函数,发送uevent事件前会先调用此函数检查是否发送该事件,返回0则不发送,为1则调用总线或类级过滤函数再次检查。

dev_uevent_name获取设备所属总线或类的名称。

dev_uevent设备级事件处理函数,向事件中添加指定设备的环境变量,如:设备主次编号,设备类型,设备使用的驱动。如果设备有所属总线或类,本函数还会调用总线或类级事件处理函数。

详细事件处理流程将在uevent章节中描述。

 

设备属性文件添加删除:

int device_create_file(struct device *dev, struct device_attribute *attr);

void device_remove_file(struct device *dev, struct device_attribute *attr);

创建/删除设备属性文件。

 

设备的对象(kobj)属性文件操作函数:

static ssize_t dev_attr_show(struct kobject *kobj, struct attribute *attr, char *buf);

static ssize_t dev_attr_store(struct kobject *kobj, struct attribute *attr,

const char *buf, size_t count);

sysfs层面看,只能识别对象级的属性文件处理函数(如上面这两个函数的格式)。设备使用这两个函数实现对象到设备属性文件处理的转换。它们使用container_of方法实现内核对象(kobj)到对应设备的转换。通过设备找到属性处理函数,并调用找到的函数(格式如下)来读取/存储指定设备的属性值。

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

};

驱动

让设备运行起来的一组函数。

数据结构

struct device_driver {

const char *name;

struct bus_type *bus;

 

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

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

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

struct attribute_group **groups;

 

struct driver_private *p;

 

 

};

 

*name

驱动程序的名称。与kobj->name相同。

*bus

该驱动使用的总线。

*probe

用来查询特定设备是否存在,以及该驱动程序能否操作它。

*remove

删除设备时使用remove函数告知驱动不可再操作此设备。

*shutdown

关机时调用shutdown函数关闭设备。

**groups

驱动属性组,会体现到sysfs系统中,用户可操作。

*p

该成员用于处理与设备、总线以及sysfs之间的连接关系,如下:

struct driver_private {

struct kobject kobj;

struct klist klist_devices;

struct klist_node knode_bus;

struct module_kobject *mkobj;

struct device_driver *driver;

};

方法

操作device_driver结构的函数有注册/去注册函数,增删属性及属性组文件函数,遍历设备函数。

 

注册函数:

int driver_register(struct device_driver *drv);

该函数实现向相应总线的驱动子集(kset)中注册驱动对象,并将总线上可以使用该驱动的设备加入到该驱动的设备链表中。

注意,每个驱动对象均赋予driver_ktype类型,用来释放对象和处理属性文件。

可以用下面函数去注册一个驱动:

void driver_unregister(struct device_driver *drv);

 

驱动对象释放函数:

static void driver_release(struct kobject *kobj);

该函数同过driver_ktype注册给驱动对象,用来释放对象。

 

驱动对象属性文件处理函数:

static ssize_t drv_attr_show(struct kobject *kobj, struct attribute *attr, char *buf);

static ssize_t drv_attr_store(struct kobject *kobj, struct attribute *attr,

const char *buf, size_t count);

这两个函数也是通过driver_ktype注册给驱动对象的,用于操作对象的属性。如同驱动一节对应函数描述的那样。这两个函数也只是个适配函数,每个驱动有自己的属相处理函数。sysfs只是通过这两个适配函数去访问驱动属性处理函数。实现方法仍然是通过container_of行为由对象获取外层包含该对象的驱动结构,从而获取驱动属性处理函数。注意,这两个函数定义在bus.c中。

 

增删属性文件函数:

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

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

用户可以通过操作增加到sysfs中驱动属性文件来修改驱动属性值。

 

增删属性组文件函数:

static int driver_add_groups(struct device_driver *drv, struct attribute_group **groups);

static void driver_remove_groups(struct device_driver *drv, struct attribute_group **groups);

这两个函数可以一次向系统增删一组驱动属性。

 

遍历设备函数:

int driver_for_each_device(struct device_driver *drv, struct device *start,

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

struct device *driver_find_device(struct device_driver *drv, struct device *start, void *data,

int (*match)(struct device *dev, void *data));

这两函数均用于遍历驱动上的设备,并对设备执行fnmatch操作。具体差异请读代码。

总线

总线是处理器与一个或者多个设备之间的通道。在设备模型中,所有的设备都通过总线相连。

数据结构

设备模型用bus_type结构表示总线,如下:

struct bus_type {

const char *name;

struct bus_attribute *bus_attrs;

struct device_attribute *dev_attrs;

struct driver_attribute *drv_attrs;

 

int (*match)(struct device *dev, struct device_driver *drv);

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

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

 

struct bus_type_private *p;

 

 

};

*name

总线名称。

*bus_attrs

总线属性集。

*dev_attrs

总线设备属性集。

*drv_attrs

总线驱动属性集。

*match

当一个总线上的新设备或者新驱动程序被添加时,会调用此函数。如果指定的驱动程序可以处理指定的设备,该函数返回1

*uevent

总线级别的uevent处理。向用户空间发送uevent时间前,使用该方法给总线增加环境变量。

*probe

探测指定设备是否在总线上。

*p

struct bus_type_private {

struct kset subsys;

struct kset *drivers_kset;

struct kset *devices_kset;

struct klist klist_devices;

struct klist klist_drivers;

};

subsys 表示总线集;*drivers_kset*devices_kset是从sysfs角度描述总线支持的驱动集和设备集,而klist_devicesklist_drivers是总线支持的设备及驱动链表,用于遍历总线上的驱动和设备。

方法

总线初始化函数:

int __init buses_init(void);

系统初始化时调用。在sysfs根目录下增加bus目录,并创建bus子集(kset),注册子集uevent事件处理函数。完成后向用户空间发送增加对象uevent事件。

另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store。注意,所有系统子集都会这么做,且使用一组同样的函数,包括设备子集和类子集。

 

总线注册函数:

int bus_register(struct bus_type *bus);

void bus_unregister(struct bus_type *bus);

向系统注册/去注册总线时,使用此两函数。这里要注意的是注册的每一个总线在sysfs中不仅仅是一个目录(kobject),它还是一个子集。即总线是以子集(kset)方式注册的,而非对象(kobject)。更重要的是每个总线子集下同时注册了设备(devices)和驱动(drivers)两个子集(kset)。前面讲到设备初始化时也会注册devices子集。事实上,这儿的设备子集只是保存设备的链接。而驱动没有初始化函数,因此驱动对象都保存在这儿各总线的驱动子集里。

 

uevent属性文件操作函数:

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

用户可以通过修改总线文件夹下的uevent文件内容,触发内核向用户空间发送uevent事件,事件类型由用户写入的内容决定。

 

总线属性文件操作函数:

static ssize_t bus_attr_show(struct kobject *kobj, struct attribute *attr, char *buf);

static ssize_t bus_attr_store(struct kobject *kobj, struct attribute *attr,

const char *buf, size_t count);

与设备和驱动对应的对象属性操作函数功能近似,这里不再赘述。记住,这只是个适配函数。为sysfs服务。

 

增删属性函数:

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

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

增加删除总线属性文件,一次一个。

static int bus_add_attrs(struct bus_type *bus);

static void bus_remove_attrs(struct bus_type *bus);

增加删除总线属性文件,一次多个,其实就是将总线提供的属性列表一次性增加到系统中。我们从数据结构中可知bus_type不仅为自己提供了属性列表。也为设备和驱动各提供了一份。因此,总线也提供了相应的添加/删除方法,类似于上面两个函数,不再列出。

 

总线遍历设备函数:

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

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

struct device *bus_find_device(struct bus_type *bus, struct device *start, void *data,

int (*match)(struct device *dev, void *data));

前者对指定总线上所有设备进行fn()操作。若fn()返回非零值,则视为出错,退出遍历。而后者只是用match()对总线上每个设备进行匹配,找到则返回对应的设备。

 

总线遍历驱动函数:

int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,

void *data, int (*fn)(struct device_driver *, void *));

对指定总线上的所有驱动进行fn()操作。若fn()返回非零值,则视为出错,退出遍历。

 

向总线增加驱动:

int bus_add_driver(struct device_driver *drv);

该函数为驱动注册函数的一部分,将驱动对象注册到对应总线的驱动(drivers)子集下。

 

向总线增加设备:

int bus_add_device(struct device *dev);

该函数为设备注册函数的一部分,但并不像驱动那样,它只将设备子集的指定设备链接到总线的设备(devices)子集下。

 

设备绑定驱动函数:

void bus_attach_device(struct device *dev);

为设备获取合适的驱动。

int device_reprobe(struct device *dev);

将设备与其驱动解绑,然后为设备获取新的驱动。这通常为热插拔服务。

int bus_rescan_devices(struct bus_type *bus);

为指定的bus上的每个设备重新绑定驱动,如果设备还没有驱动,则为其重新探测驱动并绑定。

类是一个设备的高层视图,它抽象出了底层的实现细节。比如驱动程序看到的是SCSI磁盘和ATA磁盘,但是在类的层次上,它们都是磁盘而已。类允许用户空间使用设备所能提供的功能,而不关心设备是如何连接的,以及它们是如何工作的。

数据结构

struct class {

const char *name;

struct module *owner;

 

struct class_attribute *class_attrs;

struct device_attribute *dev_attrs;

struct kobject *dev_kobj;

 

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

 

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

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

 

struct class_private *p;

};

 

*name

类名称。
*owner

类所属模块。

*class_attrs

类属性集。

*dev_attrs

类给其所有设备定义的公共属性集。

*dev_kobj

devices_init函数中创建了/sys/dev/char/sys/dev/block两个对象,dev_kobj指向二者之一,添加对象时(device_add),会将对象链接到这个目录下。按照我的理解,内核设计者是想在/sys/dev/char/sys/dev/block目录下将各设备按类分目录存放,此处的dev_kobj即是为了创建这个分目录。遗憾的是该功能并未使用,我们看到charblock下没有分目录,全是以majorminor格式命名的链接文件。这样我们就可以通过主次设备号快速找到对应的设备了。

*dev_uevent

类级uevent事件构造函数。

*class_release

用于释放指定的类。

*dev_release

用于释放指定的设备。

*p

struct class_private {

struct kset class_subsys;

struct klist class_devices;

struct list_head class_interfaces;

struct class *class;

};

class_subsys是该类在sysfs中的代表,它表明每个类都是一个子集。class_devices将所有归属于该类的设备串成一个链表,便于遍历查询或对设备进行操作。class_interfaces用于链接所有类接口。class指向所属的class对象。

 

方法

类初始化函数:

int __init classes_init(void);

系统初始化时调用。在sysfs根目录下增加class/sys/class)目录,并创建class子集(kset),注册子集uevent事件处理函数。完成后向用户空间发送增加对象uevent事件。

另外,kset在初始化时,会将公共的kobj_type一组函数注册到kset中内嵌的kobj中,其中包括释放kset函数kset_release和一组属性show/store函数kobj_attr_show/kobj_attr_store。注意,所有系统子集都会这么做,且使用一组同样的函数,包括设备子集和总线子集。

 

类注册/去注册函数:

int __class_register(struct class *cls, struct lock_class_key *key);

void class_unregister(struct class *cls);

向系统增加/去除类子集和相应路径。

 

类创建/删除函数:

struct class *__class_create(struct module *owner, const char *name, struct lock_class_key *key);

void class_destroy(struct class *cls);

与类注册/去注册函数功能一致,只是对以上两个函数的简单封装。

 

遍历类设备函数:

int class_for_each_device(struct class *class, struct device *start,

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

struct device *class_find_device(struct class *class, struct device *start, void *data,

int (*match)(struct device *, void *));

与总线或设备中的遍历功能类似,不再赘述。

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有