Linux内核之设备驱动-driver
model结构
如表1-1,Linux设备模型包含以下四个基本结构:
类型
所包含的内容
内核数据结构
对应/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
klistklist_children;
struct
klist_nodeknode_parent;
struct
klist_nodeknode_driver;
struct
klist_nodeknode_bus;
struct
device*parent;
struct
kobjectkobj;
charbus_id[BUS_ID_SIZE];
unsigneduevent_suppress:1;
const
char*init_name;
struct
bus_type*bus;
struct
device_driver *driver;
void*driver_data;
dev_tdevt;
struct
klist_nodeknode_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
当指向设备的最后一个引用被删除时,内核调用该方法;它将从内嵌的kobject的release方法中调用。所有指向核心注册的device结构都必须有一release方法,否则内核将打印出错误信息。
图1-2给出了设备与总线、驱动等连接关系。文件系统子框表明设备是通过kobj将自己与sysfs中的文件夹连接到一起的。子设备、总线、驱动、类等子系统给设备进行相应分类,并使用klist和klist_node将它们链到一起。每个子系统同时提供***_for_each_***
和 ***_find_***两个遍历函数来查找并操作指定设备。
图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来读取设备属性的值,sysfs的read函数先调用的是对象属性处理函数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));
这两函数均用于遍历驱动上的设备,并对设备执行fn或match操作。具体差异请读代码。
总线是处理器与一个或者多个设备之间的通道。在设备模型中,所有的设备都通过总线相连。
设备模型用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_devices和klist_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即是为了创建这个分目录。遗憾的是该功能并未使用,我们看到char和block下没有分目录,全是以major:minor格式命名的链接文件。这样我们就可以通过主次设备号快速找到对应的设备了。
*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
*));
与总线或设备中的遍历功能类似,不再赘述。