韦东山Linux驱动入门实验班(5)LED驱动---驱动分层和分离,平台总线模型
前言
(1)前面已经已经详细介绍了LED驱动如何进行编写的代码。如果韦东山Linux驱动入门实验班(4)LED驱动已经看懂了,驱动入门实验班后面的那些模块实验,其实和单片机操作差不太多了。我就不再浪费时间进行讲解了。
(2)本文主要进行讲解驱动的分层和分离,平台总线模型。
(3)对于韦东山老师的代码,我进行了微调,因为他代码写的比较着急,所以我感觉有些地方感觉有点冗余了就自作主张的进行了调整。但是原来他的部分没有删除,如果你认为韦东山老师的比我的好就可以注释掉我微调的部分。(微调部分我会进行说明)
(4)代码仓库:gitee仓库;github仓库
(5)注意:大家下载我仓库里面的代码再阅读本文会跟好理解一点。我仓库里面的代码依旧加上了详细的注释,觉得本文过于冗余。可以看我仓库代码和正点原子的文档学习
驱动分层
什么是驱动分层
(1)通过前面的学习,我们会发现,Linux开发将一个程序分成了两个层,应用层和驱动层。但是我们在编写驱动层程序的时候,会发现,驱动还是和指定的引脚以及开发板有关。
(2)这时候肯定会有人说了,我只需要你改一下哪个数组就可以了啊,这有什么难的吗?
(3)不难,但是对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。
(4)可能有些人会说了,我之前编写的程序,也没有重复啊。不过就只是要改一下驱动程序的gpios[]结构体的定义。但是,你要知道,在开发大型项目的时候,你将设备信息存入驱动程序中。每次调整都需要打开对应的驱动程序,如果我们不小心动了驱动程序的某个地方,产生了bug,排查是非常麻烦的。所以说,为了让Linux系统不变的臃肿,同时,为了安全性,于是Linux决定将驱动程序拆分成驱动程序和硬件描述程序,驱动程序一旦编写了,基本就不再需要再打开了。

(5)现在我们知道了,驱动程序与硬件描述程序的剥离之后。Linux于是提出了平台总线模型。我们将驱动程序和硬件描述信息都挂载在这个总线上面。
(6)有了这个总线,我们就可以先把我们设备上的一些信息挂载在总线上。然后给这些设备命一个名字。
(7)当我们需要使用指定设备的时候,驱动就可以通过给设备命的名字来找到设备信息。一旦驱动和设备匹配上,就可以执行指定的程序了。
(8)这样做有什么好处?这样我们的驱动程序可以直接进行移植,不需要受限于任何设备。而我们拿到一块开发板之后,可以直接把驱动移植过来,然后再编写设备描述信息即可,驱动代码几乎不再需要进行什么调整。

设备如何挂载在平台总线上
上面我们说了,Linux发明了平台总线,我们只需要将设备和驱动挂载在总线上,如果匹配上了,就会自动执行程序。那么设备是如何挂载在平台总线上。
platform_device_register()
(1)platform_device_register()这个函数可以将设备信息挂载在平台总线上。我们只需要传入struct platform_device结构体类型指针。
(2)如下是platform_device()结构体的定义,虽然参数很多,但是真正需要关注的只有name,id,resource,num_resources,dev。
<1>name:设备的名字, 用来和驱动相匹配。 名字相同才能匹配成功
<2>id:ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备, 因为有时候有这种需求)
<3>resource:资源结构体数组。我们需要写的设备信息写在这个数组里面。
<4>num_resources:资源结构体数量。表明这个设备结构体有多少个,其实就是一个宏定义#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
<5>dev:与平台设备相关联的设备对象。这个只需要知道要给.release参数传入一个函数指针即可。当设备被卸载的时候,会调用这个函数。
// platform 设备结构体
struct platform_device led_device = {.name = "led_device", //platform 设备的名字, 用来和 platform 驱动相匹配。 名字相同才能匹配成功.id = -1, // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备, 因为有时候有这种需求).resource = led_resource, //指向一个资源结构体数组。 一般包含设备信息.num_resources = ARRAY_SIZE(led_resource), //资源结构体数量.dev = {.release = LED_release,}
};
platform_device_unregister()
(1)这个用于将设备从平台总线上卸载。当这个设备不需要的时候,我们就可以对他进行卸载。
(2)与platform_device_register()相同,也是只需要传入struct platform_device结构体类型指针。
设备程序的入口和出口
(1)设备程序和驱动程序一样,都是使用insmod和rmmod进行装载和卸载。程序入口都是使用module_init()宏来进行定义,程序出口也都是使用module_exit()来定义。
(2)他也必须使用调用MODULE_LICENSE(“GPL”);指定模块为GPL协议
驱动如何挂载在平台总线上
platform_driver_register()
(1)这个函数只需要传入一个static struct platform_driver类型结构体指针。
(2)这个驱动的结构体,比设备的那个结构体还是好理解很多的。
<1>我们只需要知道platform_driver结构体".driver"中的".name"和platform_device结构体中的".name"一模一样就可以了。
<2>当设备和驱动匹配上了之后,我们就会执行probe函数。如果设备和驱动一旦断开联系了,就执行remove函数。你可以这么理解,一对男女,男生和女生结婚了,那么就会颁发结婚证(执行probe函数)。有一天,他们俩因为一些事情,感情破裂了,就会给他们一个离婚证(执行remove函数)。
static struct platform_driver led_driver = {.driver = {.name = "led_device", //根据这个名字,找到设备.owner = THIS_MODULE,},.probe = led_drver_probe, //注册平台之后,内核如果发现支持某一个平台设备,这个函数就会被调用。入口函数.remove = led_drver_remove, //出口函数
};
platform_driver_unregister()
这个是用于在平台上卸载驱动的。和platform_driver_register()一样,都是传入一个static struct platform_driver结构体指针。
和上一篇博客的区别
硬件信息不同
(1)这里主要就是把原来放在gpios[] 数组中的硬件描述信息,放在另外一个文件中。让驱动程序与硬件平台剥离。
(2)原来的硬件描述是存放在一个数组中,而现在的硬件描述是放在一个结构体指针里面,然后通过Linux中的平台总线获得设备信息。
/********** 原来的硬件描述 **********/
static struct gpio_desc gpios[] = {{131, "led0" }, //引脚编号,名字
};
/********** 现在的硬件描述 **********/
static struct gpio_desc *gpios; //描述gpio
初始化程序不同
(1)在上一篇博客中,我们的驱动初始化函数都是放在module_init()这个宏里面。
(2)但是现在不一样了,module_init()这个宏里面的那个函数,只做一件事情,就是将static struct platform_driver结构体注册到平台总线上。
/********** 原来的module_init()中的函数任务 **********/
static int __init gpio_drv_init(void)
{//申请GPIO//将GPIO设置为输出//注册字符驱动程序//创建类//创建设备名/dev目录下的设备名
}
/********** 现在的module_init()中的函数任务 **********/
//注册时候的入口函数
static int __init led_drver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver); //注意,这里是driver表示是驱动if(ret<0){printk("platform_driver_register error \n");return ret;}printk("platform_driver_register ok \n");return ret;
}
初始化程序执行条件不同
(1)原来,我们初始化程序,只需要装载驱动程序即可。
(2)现在不一样了,我们需要注册相互匹配的设备程序和驱动程序,初始化程序probe函数才会执行。
(3)注意,设备程序和驱动程序的注册没有顺序。随便你先注册谁。
获取GPIO数量信息不同
(1)下面这里有些人可能对pdev有疑问,这个是啥玩意?这个东西很简单,pdev就是被匹配上的设备struct platform_device结构体指针。
(2)当驱动和设备匹配上之后,执行probe函数。而这个函数的传入值struct platform_device *pdev就是被匹配上的设备struct platform_device结构体指针。
(3)因为struct platform_device结构体的num_resources记录了资源结构体的数量。所以可以通过pdev -> num_resources获取GPIO数量。
(4)我们看韦东山老师的代码,会发现好长,好麻烦。一开始我也是这么想的,命名一条指令就可以解决,搞这么长干嘛?后面简单的思考了一下。代码些这么长,还是一定作用的。
(5)我们来看platform_get_resource()这个函数,就是用于返回struct platform_device结构体中资源resource中flags被标记为IORESOURCE_IRQ的GPIO的GPIO信息。当我们这个GPIO的flags被标记为IORESOURCE_IRQ,就会返回一个指针指向这里。
(6)如果我们的设备,有些GPIO的flags没有被标记为了IORESOURCE_IRQ,那么就不会被count统计在内。
/********** 原来获取GPIO数量 **********/
int count = sizeof(gpios)/sizeof(gpios[0]); //统计有多少个GPIO
/********** 现在获取GPIO数量(作者的方法) **********/
count = pdev -> num_resources;
/********** 现在获取GPIO数量(韦东山老师的方法) **********/
while (1)
{/* 下面这9行,是用于统计有多少个GPIO的* dev:一个指向 platform_device 结构的指针,表示要获取资源信息的设备。* type:一个无符号整数,表示要获取的资源类型。在 Linux 内核中,资源类型使用常量来表示,例如 IORESOURCE_MEM 表示内存资源,IORESOURCE_IRQ 表示中断资源等。你可以根据需要选择适当的资源类型。* num:一个无符号整数,表示要获取的资源的索引号。在一个设备中可能存在多个相同类型的资源,通过索引号可以区分它们。* 返回值:返回一个指向 resource 结构的指针,表示获取到的资源信息。resource 结构包含了资源的起始地址、大小等信息。如果没有找到指定的资源,函数将返回 NULL。*/led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, count);if (led_resource){count++;}else{break;}
}
设备结构体gpios空间分配不同
(1)在原来的代码里面,我们的gpios被定义成了一个数组gpios[]。所以当我们在这个数组里面写入设备信息的时候,这个数组会自动改变空间大小。
(2)但是这里的设备信息不在驱动文件里面了,因此我们需要使用内存的动态分配了。我们知道GPIO有多少个之后,调用内存动态分配函数获取一个空间,然后把这个空间的首地址指针返回给gpios。
(3)需要注意的一点是,驱动的动态内存分配不能使用malloc,而是需要使用kmalloc。这个和驱动不能使用printf函数,只能使用printk函数是一个道理。
/********** 原来gpios空间分配 **********/
static struct gpio_desc gpios[] = {{131, "led0" }, //引脚编号,名字
};
/********** 现在gpios空间分配 **********/
static struct gpio_desc *gpios; //描述gpio
/* 作用 : kmalloc是Linux内核中的一个内存分配函数,用于在内核空间中动态分配内存。* 它类似于C语言中的malloc函数,但是在内核中使用kmalloc而不是 malloc,因为内核空间和用户空间有不同的内存管理机制。* size : 要分配的内存大小,以字节为单位。* flags : 分配内存时的标志,表示内存的类型和分配策略,是一个 gfp_t 类型的值。常常采用GFP_KERNEL* GFP_KERNEL是内存分配的标志之一,它表示在内核中以普通的内核上下文进行内存分配。* 返回值 : 如果内存分配成功,返回指向分配内存区域的指针。如果内存分配失败(例如内存不足),返回NULL。
*/
gpios = kmalloc(count * sizeof(struct gpio_desc), GFP_KERNEL);
设备结构体gpios硬件信息获取不同
(1)原来,我们直接向gpios[]数组里面写入硬件信息就可以了。
(2)因为现在我们将硬件信息写入了设备程序里面了,所以需要其他办法获得。从设备程序中获得硬件信息有两种方法。
<1>通过pedv指针直接获得硬件信息。
<2>通过platform_get_resource()函数获得。
(3)
<1>我们对比下面的代码会发现,led_resource和pdev->resource[i]是一个东西啊。
<2>可以这么说,但是还是有一定的区别。如果pdev->resource[i]的flags不是IORESOURCE_IRQ,那么就不会被提取出来成led_resource。
/********** 原来gpios获得硬件信息 **********/
static struct gpio_desc gpios[] = {{131, "led0" }, //引脚编号,名字
};
/********** 现在gpios获得硬件信息 **********/
//通过pedv指针直接获得硬件信息。
gpios[i].gpio = pdev->resource[i].start;
sprintf(gpios[i].name, "%s", pdev->resource[i].name); //将platform_device.resource.name传递给gpios[i].name//通过platform_get_resource()函数获得。
struct resource *led_resource;
led_resource = platform_get_resource(pdev, IORESOURCE_IRQ, i); //从节点里面取出第i项
gpios[i].gpio = led_resource->start; //将需要操作的IO号传递给gpios[i].gpio
sprintf(gpios[i].name, "%s", led_resource->name); //将platform_device.resource.name传递给gpios[i].name相关文章:
韦东山Linux驱动入门实验班(5)LED驱动---驱动分层和分离,平台总线模型
前言 (1)前面已经已经详细介绍了LED驱动如何进行编写的代码。如果韦东山Linux驱动入门实验班(4)LED驱动已经看懂了,驱动入门实验班后面的那些模块实验,其实和单片机操作差不太多了。我就不再浪费时间进行讲…...
【雕爷学编程】MicroPython动手做(02)——尝试搭建K210开发板的IDE环境
知识点:简单了解K210芯片 2018年9月6日,嘉楠科技推出自主设计研发的全球首款基于RISC-V的量产商用边缘智能计算芯片勘智K210。该芯片依托于完全自主研发的AI神经网络加速器KPU,具备自主IP、视听兼具与可编程能力三大特点,能够充分适配多个业务场景的需求。作为嘉楠科…...
C#——Thread与Task的差异比较及使用环境
C#——Thread与Task的差异比较及使用环境 前言一、差异1. 创建和管理:2. 异步编程:3. 返回值:4. 异常处理:5. 线程复用: 总结 前言 前面两篇文章,分别通过各自的实例讲了关于Task以及Thread的相关的使用特…...
刷题 31-35
三十一、 747. 至少是其他数字两倍的最大数 给你一个整数数组 nums ,其中总是存在 唯一的 一个最大整数 。 请你找出数组中的最大元素并检查它是否 至少是数组中每个其他数字的两倍 。如果是,则返回 最大元素的下标 ,否则返回 -1 。 示例 1&a…...
【mysql】—— 数据类型详解
序言: 本期我将大家认识关于 mysql 数据库中的基本数据类型的学习。通过本篇文章,我相信大家对mysql 数据类型的理解都会更加深刻。 目录 (一)数据类型分类 (二)数值类型 1、tinyint类型 2、bit类型 …...
kafka常用命令
查看主题 ./kafka-topics.sh --list --bootstrap-server 10.1.1.2:9092 创建主题 ./kafka-topics.sh --bootstrap-server 10.1.1.2:9092 --create --topic test_topic --partitions 1 查看消费者列表--list ./kafka-consumer-groups.sh --bootstrap-server 10.1.1.2:9092 -…...
数字图像处理(番外)图像增强
图像增强 图像增强的方法是通过一定手段对原图像附加一些信息或变换数据,有选择地突出图像中感兴趣的特征或者抑制(掩盖)图像中某些不需要的特征,使图像与视觉响应特性相匹配。 图像对比度 图像对比度计算方式如下: C ∑ δ δ ( i , j …...
flutter:轮播
前言 介绍几个比较有不错的轮播库 swipe_deck 与轮播沾边,但是更多的是一种卡片式的交互式界面设计。它的主要概念是用户可以通过左右滑动手势浏览不同的卡片,每张卡片上都有不同的信息或功能。 Swipe deck通常用于展示图片、产品信息、新闻文章、社…...
高忆管理:股票投资策略是什么?有哪些?
在进行股票买卖过程中,出资者需求有自己的方案和出资战略,并且主张严格遵从出资战略买卖,不要跟风操作。那么股票出资战略是什么?有哪些?下面就由高忆管理为我们剖析: 股票出资战略简略来说便是能够协助出资…...
为公网SSH远程Ubuntu配置固定的公网TCP端口地址主图
文章目录 为公网SSH远程Ubuntu配置固定的公网TCP端口地址 为公网SSH远程Ubuntu配置固定的公网TCP端口地址 在上篇文章中,我们通过cpolar建立的临时TCP数据隧道,成功连接了位于其他局域网下的Ubuntu系统,实现了不同操作系统、不同网络下的系统…...
【前端知识】React 基础巩固(四十一)——手动路由跳转、参数传递及路由配置
React 基础巩固(四十一)——手动路由跳转、参数传递及路由配置 一、实现手动跳转路由 利用 useNavigate 封装一个 withRouter(hoc/with_router.js) import { useNavigate } from "react-router-dom"; // 封装一个高阶组件 function withRou…...
Qt几种字符类型的相互转换
Qt几种字符类型的相互转换 将const QString转换为const char*将const char*转换为const QStringQstring转换为string把string转换为QstringQt中弹出一个窗口 将const QString转换为const char* #include <QString> #include <iostream>int main() {const QString …...
软件测试员的非技术必备技能
成为软件测试人员所需的技能 非技术技能 以下技能对于成为优秀的软件测试人员至关重要。 将您的技能组合与以下清单进行比较,以确定软件测试是否适合您 - 分析技能:优秀的软件测试人员应具备敏锐的分析能力。 分析技能将有助于将复杂的软件系统分解为…...
渗透测试:Linux提权精讲(二)之sudo方法第二期
目录 写在开头 sudo expect sudo fail2ban sudo find sudo flock sudo ftp sudo gcc sudo gdb sudo git sudo gzip/gunzip sudo iftop sudo hping3 sudo java 总结与思考 写在开头 本文在上一篇博客的基础上继续讲解渗透测试的sudo提权方法。相关内容的介绍与背…...
ansible安装lnmp(集中式)
文章目录 一、安装nginx二、安装mysql三、安装php测试: 一、安装nginx - name: the nginx playhosts: webserversremote_user: roottasks:- name: stop firewalld #关闭防火墙service: namefirewalld statestopped enabledno- name: selinux stopc…...
Tomcat的基本使用,如何用Maven创建Web项目、开发完成部署的Web项目
Tomcat 一、Tomcat简介二、Tomcat基本使用三、Maven创建Web项目3.1 Web项目结构3.2开发完成部署的Web项目3.3创建Maven Web项目3.3.1方式一3.3.2方式二(个人推荐) 总结 一、Tomcat简介 Web服务器: Web服务器是一个应用程序(软件&…...
微信小程序测试要点
一、什么是小程序? 可以将小程序理解为轻便的APP,不用安装就可以使用的应用。用户通过扫一扫或者搜索的方式,就可以打开应用。 小程序最主要的特点是内嵌于微信之中,而使用小程序的目的是为了能够方便用户不在受下载多个APP的烦…...
TCP网络通信编程之netstat
【netstat指令】 【说明】 (1)Listening 表示某个端口在监听 (2)如果有一个外部程序(客户端)连接到该端口,就会显示一条连接信息 (3)指令netstat -anb 可以参看是那个…...
Stable Diffusion:网页版 体验 / AI 绘图
一、官网地址 Stable Diffusion Online 二、Stable Diffusion AI 能做什么 Stable Diffusion AI绘图是一种基于Stable Diffusion模型的生成式AI技术,能够生成各种类型的图像,包括数字艺术、照片增强和图像修复等。以下是一些可能的应用: …...
一文了解JavaScript 与 TypeScript的区别
TypeScript 和 JavaScript 是两种互补的技术,共同推动前端和后端开发。在本文中,我们将带您快速了解JavaScript 与 TypeScript的区别。 一、TypeScript 和 JavaScript 之间的区别 JavaScript 和 TypeScript 看起来非常相似,但有一个重要的区…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
工程地质软件市场:发展现状、趋势与策略建议
一、引言 在工程建设领域,准确把握地质条件是确保项目顺利推进和安全运营的关键。工程地质软件作为处理、分析、模拟和展示工程地质数据的重要工具,正发挥着日益重要的作用。它凭借强大的数据处理能力、三维建模功能、空间分析工具和可视化展示手段&…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
