【Linux】总线-设备-驱动模型

背景

前面,我们介绍了写驱动代码的一些常规步骤,并且也写了最基本的驱动代码,但是那些代码存在着问题,我们将硬件的信息都写进了驱动里了,如果我们在杂项设备驱动中控制led,那么会在硬件操作接口中包含硬件信息,如果引脚有变化,这个驱动代码就得重新修改,虽然修改也很简单,但是从框架的角度来看,这是不合理的,相当于驱动代码写死了。

于是,Linux引入了设备驱动模型分层的概念,将我们编写的驱动代码分为两块:设备和驱动。设备负责提供硬件资源,驱动代码负责去使用这些设备提供的硬件资源。并由总线将他们联系起来。

bus
device
driver

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

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

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

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

重要目录

/sys/bus目录下的每个子目录都是注册好了的总线类型,每个总线类型下包含两个子目录(/sys/bus/devicessys/bus/drivers)。其中/sys/bus/devices下是该总线下的所有设备,而这些设备都是符号链接,他们分别指向真正的设备(/sys/devices目录下)。sys/bus/drivers目录下是所有注册在这个总线上的驱动,每个子目录下是一些可以观察和修改的driver参数。

/sys/devices目录下是全局设备结构体系,包含所有被发现的注册在各种总线上的各种物理设备。

/sys/dev目录下是所有的设备节点,但实际上都是些链接文件,同样指向了/sys/devices目录下的文件。

/sys/class目录下则是包含所有注册在kernel里面的设备类型,这是按照设备功能分类的设备模型。
在这里插入图片描述

框架原理

  • 总线管理着两个链表:设备链表和驱动链表。
  • 当我们向内核注册一个驱动时,便插入到总线的驱动链表。
  • 当我们向内核注册一个设备时,便插入到总线的设备链表。
  • 在插入的同时,总线会执行一个bus_type结构体中的match方法对新插入的设备或驱动进行匹配。
  • 匹配成功后,会调用驱动device_driver结构体中的probe方法。通常在probe方法中获取设备资源,具体的功能可自定义。
  • 移除设备或驱动时,会调用device_driver结构体中remove方法。

设备驱动模型

总线

总线是连接处理器和设备之间的桥梁,总线代表着同类设备需要共同遵守的工作时序。

在这里插入图片描述

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);
void bus_unregister(struct bus_type *bus);

设备

device结构体(内核源码/include/linux/device.h)struct device {
const char *init_name;struct device           *parent;struct bus_type *bus;struct device_driver *driver;void            *platform_data;void            *driver_data;struct device_node      *of_node;dev_t                   devt;struct class            *class;
void (*release)(struct device *dev);const struct attribute_group **groups;  /* optional groups */
struct device_private   *p;
........
};
  • init_name :指定该设备的名称,总线匹配时,一般会根据比较名字,来进行配对;
  • parent :表示该设备的父对象,前面提到过,旧版本的设备之间没有任何关联,引入Linux设备模型之后,设备之间呈树状结构,便于管理各种设备;
  • bus :表示该设备依赖于哪个总线,当我们注册设备时,内核便会将该设备注册到对应的总线。
  • of_node :存放设备树中匹配的设备节点。当内核使能设备树,总线负责将驱动的of_match_table以及设备树的compatible属性进行比较之后,将匹配的节点保存到该变量。
  • platform_data :一个指针,用于保存具体的平台相关的数据。具体的driver模块,可以将一些私有的数据,暂存在这里,需要使用的时候,再拿出来,因此设备模型并不关心该指针得实际含义。
  • driver_data :同上,驱动层可通过dev_set/get_drvdata函数来获取该成员;
  • class :指向了该设备对应类,开篇我们提到的触摸,鼠标以及键盘等设备,对于计算机而言,他们都具有相同的功能,都归属于输入设备。我们可以在/sys/class目录下对应的类找到该设备,如input、leds、pwm等目录;
  • dev :dev_t类型变量,字符设备章节提及过,它是用于标识设备的设备号,该变量主要用于向/sys目录中导出对应的设备。
  • release :回调函数,当设备被注销时,会调用该函数。如果我们没定义该函数时,移除设备时,会提示“Device ‘xxxx’ does not have a release() function, it is broken and must be fixed”的错误。
  • group :指向struct attribute_group类型的指针,指定该设备的属性;
  • *p:是私有数据结构指针,该指针中会保存子设备链表、用于添加到bus/driver/prent等设备中的链表头等等。
内核也提供相关的API来注册和注销设备int device_register(struct device *dev);
void device_unregister(struct device *dev);

在讲解总线的时候,我们说过,当成功注册总线时,会在/sys/bus目录下创建对应总线的目录,该目录下有两个子目录,分别是drivers和devices, 我们使用device_register注册的设备从属于某个总线时,该总线的devices目录下便会存在该设备文件。

驱动

device_driver结构体(内核源码/include/linux/device.h)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 */const struct of_device_id       *of_match_table;const struct acpi_device_id     *acpi_match_table;int (*probe) (struct device *dev);int (*remove) (struct device *dev);const struct attribute_group **groups;struct driver_private *p;};
  • name :指定驱动名称,总线进行匹配时,利用该成员与设备名进行比较;
  • bus :表示该驱动依赖于哪个总线,内核需要保证在驱动执行之前,对应的总线能够正常工作;
  • suppress_bind_attrs :布尔量,用于指定是否通过sysfs导出bind与unbind文件,bind与unbind文件是驱动用于绑定/解绑关联的设备。
  • owner :表示该驱动的拥有者,一般设置为THIS_MODULE;
  • of_match_table :指定该驱动支持的设备类型。当内核使能设备树时,会利用该成员与设备树中的compatible属性进行比较。
  • remove :当设备从操作系统中拔出或者是系统重启时,会调用该回调函数;
  • probe :当驱动以及设备匹配后,会执行该回调函数,对设备进行初始化。通常的代码,都是以main函数开始执行的,但是在内核的驱动代码,都是从probe函数开始的。
  • group :指向struct attribute_group类型的指针,指定该驱动的属性;

内核提供了driver_register函数以及driver_unregister函数来注册/注销驱动,成功注册的驱动会记录在/sys/bus//drivers目录

device_driver结构体(内核源码/include/linux/device.h)int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

示例

暂无。一般情况我们都会在系统已注册的总线上写驱动和设备。这篇文章主要了解这种架构的原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/56744.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【SQL实验】数据库、表、模式的SQL语句操作

完整代码在文章末尾 1、数据库的建立、删除和修改操作 (1)使用SQL语句创建数据库EDUC,并进行如下设置: 数据库文件和日志文件的逻辑名称分别为:Student_data和Student_log;数据文件的物理文件名为‘C:\DA…

基于Java语言的培训平台+学习平台+在线学习培训系统+教育平台+教育学习系统+课程学习平台

简述 企业培训平台企业考试系统培训平台考试系统企业大学企业视频网站视频学习平台 介绍 企业培训平台支持企业培训考试全流程,在线学习、在线考试,成熟的企业培训考试解决方案,充分满足企业培训需求。 独立部署,仅内部员工登录…

20240817 全志 笔试

文章目录 1、选择题1.11.21.31.41.51.61.71.81.91.101.111.121.131.141.151.161.171.181.191.202、填空题2.12.23、问答题3.14、编程题4.14.2岗位:NO2510 嵌入式软件设计工程师(珠海/广州/西安/深圳) 题型:20 道选择题,2 道填空题,1 道问答题,2 道编程题 1、选择题 1.…

【热门】智慧果园管理系统解决方案

随着科技的进步,原有农业种植方式已经不能满足社会发展的需要,必须对传统的农业进行技术更新和改造。经过多年的实践,人们总结出一种新的种植方法——温室农业,即“用人工设施控制环境因素,使作物获得最适宜的生长条件,从而延长生产季节,获得最佳的产出”。这种农业生产方式…

AI的作用和应用领域

AI AI的作用 本质:处理文字/图像/音频数据,本质上是使用统计模仿人类思考的能力。所有人类可胜任的地方都可做。 输入输出:文字/图像/音频。 效率提升:AI技术在各个领域的应用目标都是提高效率和精度。自动化:AI技术…

笔试强训day37

旋转字符串 描述 字符串旋转: 给定两字符串A和B,如果能将A从中间某个位置分割为左右两部分字符串(可以为空串),并将左边的字符串移动到右边字符串后面组成新的字符串可以变为字符串B时返回true。 例如:如果A‘youz…

发动机冷却系统排空气

发动机冷却系统排空气的几种常见方法 发动机冷却系统是汽车发动机的重要组成部分,它的主要作用是通过循环冷却液来吸收和散发发动机产生的热量,确保发动机在正常工作温度下运行。然而,在冷却系统的运行过程中,由于各种原因&#…

数据压缩(5)——上下文转换编码

统计压缩编码基于单个字符,字典编码基于单词;上下文变换基于具有联系的上下文,例如: RLE编码针对重复字符:AAAABCCC可以记为[A,4]B[C,3] 增量编码针对数值型数据:通过一些运算以减少数值的变化范围&#…

03.顺序表实现

顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储,在数组上完成数据的增删改查。一般见到的顺序表都是在结构体中定义的数组,只是比普通数组多了增删改查等一些其他功能函数。 上节已经介绍了顺序表有…

【android studio】Gradle和Gradle插件版本关系/配置/常见ERR示例

参考链接: Android之Gradle和Gradle插件区别及联系 grdle 的安装与配置 、gradle和jdk版本对应关系 Android Gradle Plugin与Gradle版本、JDK版本对应关系 配置示例 常见err 主要原因 1.编译版本未设定 2.有多个module而且gradle 版本设置不一致 修改如下&#xf…

虚幻闪烁灯光材质

创建一个材质 材质域改成光照函数 , Time让材质动起来 参数B用来控制速度 , Sine 让灯光闪烁 , Frac 增加了闪烁细节 把材质放到灯光材质上 效果还是挺不错的! 可以用于一些恐怖游戏~

OpenCV高级图形用户界面(8)在指定的窗口中显示一幅图像函数imshow()的使用

操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在指定的窗口中显示一幅图像。 函数 imshow 在指定的窗口中显示一幅图像。如果窗口是以 cv::WINDOW_AUTOSIZE 标志创建的,图像将以原…

仕考网:国考笔试没进面还有机会吗?

在国家公务员考试及各省公务员考试中,除了常规的招录程序之外,还有调剂、递补和补录多重机会。 调剂:于笔试结束之后至面试启动之前.针对因报名人数不足未达到预定面试比例的岗位,将开放公开调剂。只要考生的笔试成绩超越了国考设…

React面试题笔记(一)

一、react基础面试题 1.react中keys的作用是什么? key是是用于追踪哪些列表被修改,被添加或者被移除的辅助标识。 在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是…

如何查看默认网关地址:详细步骤

在日常的网络配置与故障排查中,了解并正确查看默认网关地址是一项基础且至关重要的技能。默认网关是连接本地网络与外部网络(如互联网)的关键节点,它扮演着数据包转发的重要角色。无论是家庭网络、办公室网络还是更复杂的网络环境…

Chromium html<iframe>对应c++接口定义

HTML <iframe> 标签 使用 <iframe> 标签 在当前 HTML 文档中嵌入另一个文档&#xff1a; <!DOCTYPE html> <html> <body><h1>iframe 元素</h1><iframe src"https://www.w3school.com.cn" title"W3School 在线教…

一些简单的编程题(Java与C语言)

引言&#xff1a; 这篇文章呢&#xff0c;小编将会举一些简单的编程题用来帮助大家理解一下Java代码&#xff0c;并且与C语言做个对比&#xff0c;不过这篇文章所出现的题目小编不会向随缘解题系列里面那样详细的讲解每一到题&#xff0c;本篇文章的主要目的是帮助小编和读者们…

vcenter的使用

1 配置 1.1 时间配置 报错原因&#xff1a;ESXI主机没有配置DNS无法解析NTP服务器网址。 解决办法&#xff1a;配置ESXI主机DNS&#xff0c;操作如下图。 点击【配置】、【服务】 【编辑启动策略】 我们可以看到当前的【NTP服务状态】处于 已停止的状态 点击【配置】、【时间…

基于直播美颜SDK的实时美颜平台开发指南

随着直播平台的快速发展&#xff0c;用户对视频质量的要求越来越高&#xff0c;尤其是对于美颜效果的需求。为满足这一市场需求&#xff0c;基于直播美颜SDK的实时美颜平台应运而生。本文将探讨如何开发这样一个平台&#xff0c;助力开发者在激烈的竞争中脱颖而出。 一、理解美…

前端容易错的题2

v-if 和 v-show 区别 使用了 v-if 的时候&#xff0c;如果值为 false &#xff0c;那么页面将不会有这个 html 标签生成。 v-show 则是不管值为 true 还是 false &#xff0c;html 元素都会存在&#xff0c;只是 CSS 中的 display 显示或隐藏 $route和$router的区别 $rou…