Linux的设备模型



前言

Linux引入了设备驱动模型分层的概念, 将我们编写的驱动代码分成了两块:设备与驱动。设备负责提供硬件资源而驱动代码负责去使用这些设备提供的硬件资源。 并由总线将它们联系起来。这样子就构成以下图形中的关系。
在这里插入图片描述

设备模型通过几个数据结构来反映当前系统中总线、设备以及驱动的工作状况,提出了以下几个重要概念:

设备(device) :挂载在某个总线的物理设备;

驱动(driver) :与特定设备相关的软件,负责初始化该设备以及提供一些操作该设备的操作方式;

总线(bus) :负责管理挂载对应总线的设备以及驱动;

类(class) :对于具有相同功能的设备,归结到一种类别,进行分类管理;

在Linux中一切皆“文件”,在根文件系统中有个/sys文件目录,里面记录各个设备之间的关系。 下面介绍/sys下几个较为重要目录的作用。

/sys/bus目录下的每个子目录都是注册好了的总线类型。这里是设备按照总线类型分层放置的目录结构, 每个子目录(总线类型)下包含两个子目录——devices和drivers文件夹;其中devices下是该总线类型下的所有设备, 而这些设备都是符号链接,它们分别指向真正的设备(/sys/devices/下);如下图bus下的usb总线中的device则是Devices目 录下/pci()/dev 0:10/usb2的符号链接。而drivers下是所有注册在这个总线上的驱动,每个driver子目录下 是一些可以观察和修改的driver参数。

/sys/devices目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。一般来说, 所有的物理设备都按其在总线上的拓扑结构来显示。/sys/devices是内核对系统中所有设备的分层次表达模型, 也是/sys文件系统管理设备的最重要的目录结构。

/sys/class目录下则是包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型, 我们知道每种设备都具有自己特定的功能,比如:鼠标的功能是作为人机交互的输入,按照设备功能分类无论它 挂载在哪条总线上都是归类到/sys/class/input下。
在这里插入图片描述

将它们统一起来就形成了上面的拓扑图,记录着设备与设备之间的关系。而我们这章的重心则放在bus文件夹目录下,创建自己的总线类型以及devices和drivers。

了解上面设备与设备的拓扑图之后,让我们再回来“总线-设备-驱动”模型中来。“总线-设备-驱动”它们之间是如何相互配合工作的呢?
在这里插入图片描述

在总线上管理着两个链表,分别管理着设备和驱动,当我们向系统注册一个驱动时,便会向驱动的管理链表插入我们的新驱动, 同样当我们向系统注册一个设备时,便会向设备的管理链表插入我们的新设备。在插入的同时总线会执行一个bus_type结构体中match的方法对新插入的设备/驱动进行匹配。 (它们之间最简单的匹配方式则是对比名字,存在名字相同的设备/驱动便成功匹配)。 在匹配成功的时候会调用驱动device_driver结构体中probe方法(通常在probe中获取设备资源,具体的功能可由驱动编写人员自定义), 并且在移除设备或驱动时,会调用device_driver结构体中remove方法。

以上只是设备驱动模型的 机制 ,上面的match、probe、remove等方法需要我们来实现需要的功能。看到这里相信我们都已经对设备驱动模型有了粗略的整体认识。 无论以后学习平台设备驱动、块设备驱动或者是其他总线设备,都跟Linux设备模型息息相关。sysfs文件系统用于把内核的设备驱动导出到用户空间, 用户便可通过访问sys目录及其下的文件,来查看甚至控制内核的一些驱动设备。 接下来对总线、驱动、设备进行进一步的了解了,具体了解如何使用代码来实现创建自己的总线并在自己的总线上创建设备及驱动。 同时也可以将我们驱动的某个控制变量,导出到用户空间。

1. 总线

总线是连接处理器和设备之间的桥梁,总线代表着同类设备需要共同遵守的工作时序,是连接处理器和设备之间的桥梁。我们接触到的设备大部分是依靠总线来进行通信的, 它们之间的物理连接如图所示,对于野火开发板而言,触摸芯片是依赖于I2C,鼠标、键盘等HID设备,则是依赖于USB。从功能上讲,这些设备都是将文字、字符、控制命令或采集的数据等信息输入到计算机。
在这里插入图片描述
总线驱动则负责实现总线的各种行为,其管理着两个链表,分别是添加到该总线的设备链表以及注册到该总线的驱动链表。当你向总线添加(移除)一个设备(驱动)时,便会在对应的列表上添加新的节点, 同时对挂载在该总线的驱动以及设备进行匹配,在匹配过程中会忽略掉那些已经有驱动匹配的设备。
在这里插入图片描述

在内核中使用结构体bus_type来表示总线,如下所示:

bus_type结构体(内核源码/include/linux/device.h)

struct bus_type {
	const char              *name;
	const struct attribute_group **bus_groups;
	const struct attribute_group **dev_groups;
	const struct attribute_group **drv_groups;
	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);
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
	const struct dev_pm_ops *pm;
	struct subsys_private *p;
};

name :指定总线的名称,当新注册一种总线类型时,会在/sys/bus目录创建一个新的目录,目录名就是该参数的值;

drv_groups、dev_groups、bus_groups :分别表示驱动、设备以及总线的属性。这些属性可以是内部变量、字符串等等。通常会在对应的/sys目录下在以文件的形式存在,对于驱动而言,在目录/sys/bus//driver/存放了设备的默认属性;设备则在目录/sys/bus//devices/中。这些文件一般是可读写的,用户可以通过读写操作来获取和设置这些attribute的值。

match :当向总线注册一个新的设备或者是新的驱动时,会调用该回调函数。该回调函数主要负责判断是否有注册了的驱动适合新的设备,或者新的驱动能否驱动总线上已注册但没有驱动匹配的设备;

uevent :总线上的设备发生添加、移除或者其它动作时,就会调用该函数,来通知驱动做出相应的对策。

probe :当总线将设备以及驱动相匹配之后,执行该回调函数,最终会调用驱动提供的probe函数。

remove :当设备从总线移除时,调用该回调函数;

suspend、resume :电源管理的相关函数,当总线进入睡眠模式时,会调用suspend回调函数;而resume回调函数则是在唤醒总线的状态下执行;

pm :电源管理的结构体,存放了一系列跟总线电源管理有关的函数,与device_driver结构体中的pm_ops有关;

p :该结构体用于存放特定的私有数据,其成员klist_devices和klist_drivers记录了挂载在该总线的设备和驱动;

在实际编写linux驱动模块时,Linux内核已经为我们写好了大部分总线驱动,正常情况下我们一般不会去注册一个新的总线, 内核中提供了bus_register函数来注册总线,以及bus_unregister函数来注销总线,其函数原型如下:

注册/注销总线API(内核源码/drivers/base/bus.c)

int bus_register(struct bus_type *bus);

参数: bus: bus_type类型的结构体指针

返回值:

成功: 0

失败: 负数

注册/注销总线API(内核源码/drivers/base/bus.c)

void bus_unregis
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值