总线、设备、驱动之间的关系
如下是USB总线的示意图,USB总线上可以连接各种各样的USB设备,为了让这些设备能正常工作,系统又再USB总线上注册个各种各样的设备驱动,当一个设备接入USB总线,USB总线会根据设备信息去遍历注册到USB总线上的驱动,然后找到一个与设备匹配的驱动,并将其于设备绑定,用于驱动设备。
再一个实际的计算机系统中往往还存在其他各种各样的总线,如SPI总线、IIC总线等,Linux内核中使用struct bus_type、struct device、struct device_driver来对总线、设备、设备驱动进行抽象,这3个对象都内嵌了struct kobject或struct kset对象,因此可以在/sys/目录生成相应的文件用于描述3者之间的关系
通过前面的内容不难发现引入总线、设备、驱动框架后设备和设备驱动分别挂载在对应的总线上,实现了驱动和设备的分离,降低了设备和驱动之间的耦合(这也是总线、设备、驱动框架的一大优点)。
struct bus_type对象
struct bus_type 对象表示一条总线,其中主要包括以下成员:
//总线名称
const char *name;
//完成设备和驱动进行匹配,返回非0值表示匹配成功
int (*match)(struct device *dev, struct device_driver *drv);
注册、注销总线
通过下列函数可以实现总线的注册和注销
//注册总线
int bus_register(struct bus_type *bus)
//注销总线
void bus_unregister(struct bus_type *bus)
struct device对象
struct device 对象表示一个设备,其中主要包括以下成员:
//设备名称
const char *init_name;
//所所属的总线
struct bus_type *bus;
//平台数据,一般记录设备的私有信息
void *platform_data;
//驱动数据,一般用于记录驱动的私有信息
void *driver_data;
//设备释放函数,在注销设备时被调用,若不提供此函数在卸载时可能会抱错
void (*release)(struct device *dev);
//指向此设备的设备树节点
struct device_node *of_node;
注册、注销设备
通过以下函数可以完成设备的注册和注销:
//注册设备
int device_register(struct device *dev)
//注销设备
void device_unregister(struct device *dev)
struct device_driver对象
struct device_driver对象表示一个驱动,其中主要包括以下成员:
//驱动名称
const char *name;
//所依赖的总线
struct bus_type *bus;
//设备树匹配表,在使用设备树时用此参数与设备树描述的设备的compatible属性进行匹配
const struct of_device_id *of_match_table;
//设备和驱动匹配成功执行
int (*probe)(struct device *dev);
//设备或驱动卸载时执行
int (*remove)(struct device *dev);
注册、注销驱动
通过下面的函数可以实现驱动的注册和注销
//注册驱动
int driver_register(struct device_driver *drv)
//注销驱动
void driver_unregister(struct device_driver *drv)
驱动代码实现
此节的驱动代码分为3个部分,分别时描述总线的代码、描述设备的代码、设备驱动代码
总线代码
描述总线的代码主要向系统注册一条总线(这条总线是高度抽象的,没有实现具体的功能),在代码中主要关注总线的注册、注销以及总线的设备驱动匹配函数,具体代码如下:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
//匹配设备和驱动
static int mybus_match(struct device *dev, struct device_driver *drv)
{
//返回非0值表示匹配成功
return 1;
}
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
};
EXPORT_SYMBOL(mybus);
static int __init bus_init(void)
{
//注册总线
return bus_register(&mybus);
}
static void __exit bus_exit(void)
{
//注销总线
bus_unregister(&mybus);
}
module_init(bus_init);
module_exit(bus_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("This module is mybus");
MODULE_ALIAS("mybus");
设备代码
描述设备的代码主要向系统注册一个设备(注册的是一个高度抽象的设备,不包括具体的特性),在代码中主要关注设备的注册、注销、以及设备释放函数的实现,具体代码如下:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
//设备释放函数,在设备卸载时执行
static void mydev_release(struct device *dev)
{
printk("mydev release\r\n");
}
static struct device mydev =
{
.init_name = "mydev",
.bus = &mybus,
.release = mydev_release,
};
static int __init dev_init(void)
{
//注册设备
return device_register(&mydev);
}
static void __exit dev_exit(void)
{
//注销设备
device_unregister(&mydev);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("This module is mydev");
MODULE_ALIAS("mydev");
驱动代码
驱动代码主要完成驱动的注册、注销(注册的时一个高度抽象的驱动,没有实现具体的驱动功能),驱动代码主要关注驱动的注册、注销,以及probe函数和remove函数的实现,probe函数在设备和驱动匹配成功后被调用,这时驱动应根据具体的设备执行相应的初始化操作,remove函数在驱动或设备被卸载时执行,这时驱动程序应释放初始化过程中分配的资源,如下是一个最近的的设备驱动:
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
extern struct bus_type mybus;
//设备和驱动匹配成功后被调用,可以执行一些初始化操作
static int probe(struct device *dev)
{
printk("mydrv %s\n", __FUNCTION__);
return 0;
}
//设备或驱动被卸载时执行,释放初始化过程分配的资源
static int remove(struct device *dev)
{
printk("mydrv %s\n", __FUNCTION__);
return 0;
}
static struct device_driver mydrv =
{
.name = "mydrv",
.bus = &mybus,
.probe = probe,
.remove = remove,
};
static int __init drv_init(void)
{
//注册驱动
return driver_register(&mydrv);
}
static void __exit drv_exit(void)
{
//注销驱动
driver_unregister(&mydrv);
}
module_init(drv_init);
module_exit(drv_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("This module is mydrv");
MODULE_ALIAS("mydrv");
上机实验
- 从这里下载代码,并进行编译(这里涉及到同时编译多个内核模块的内容,请参考1.4模块间相互引用)中的模块编译方法),然后将代码拷贝到目标板跟文件系统。
- 执行命令insmod bus.ko加载总线模块,加载完成后可在/sys/bus/看到刚才注册的总线(mybus总线)
- 执行命令insmod dev.ko加载设备模块,加载完成后可在/sys/bus/mybus/devices/目录下看到刚才注册的设备
- 执行命令insmod drv.ko加载驱动模块,加载成功后会执行驱动的probe函数(因为设备已经实现加载,所以设备和驱动成功匹配),同时在/sys/bus/mybus/drivers目录下可以看到刚才注册的驱动。
5.执行命令rmmod dev.ko卸载设备,此时设备的release函数和驱动的remove函数被调用,此时再执行rmmod drv.ko不会有输出(因为设备驱动再卸载设备过程中已经解绑,驱动的remove函数已被调用)
提示
在设备驱动框架中,设备和驱动都依赖于总线,所以模块加载时需要先加载总线,卸载时最后卸载总线,驱动和设备之间没有直接的依赖关系,来者的加载卸载顺序可以随意。