【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十八章 Platform 设备驱动

i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、

【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)


第四十八章 Platform 设备驱动

本章导读

在前面的章节中我们了解了蜂鸣器等字符设备的驱动,但是当我们参考 Linux 内核源码中其他设备比较复杂的驱动时,并没有找到如此简单形式的驱动,在 Linux 内核中,提出了驱动的分离和分层这样的软件思路,在本章我们先来看一下最常用的 platform 设备驱动框架。

48.1章节讲解了Linux设备驱动分层和分离的思想

48.2章节讲解了Platform设备驱动模型,其下的三个小节分别讲解了Platform 设备,Platform 驱动,Platform总线

48.3章节我们完整的写了一个以蜂鸣器为代表的平台总线模型的驱动。其分为三个小章节,分别编写了device.c driver.c probe编写。并且测试可以控制蜂鸣器响灭。

本章内容对应视频讲解链接(在线观看):

平台总线模型介绍  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=19

注册Platform设备  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=20

注册platform驱动  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=21

平台总线probe函数编写  https://www.bilibili.com/video/BV1Vy4y1B7ta?p=22

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动”路径下。

48.1 Linux设备驱动分层和分离

48.1.1 设备驱动的分层思想

在面向对象的程序设计中,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类

中的函数。如果对于继承的这个事物而言,其某函数的实现与基类一致,那它就可以直接继承基类的函数;相反,它可以重载之。这种面向对象的设计思想极大地提高了代码的可重用能力,是对现实世界事物间关

系的一种良好呈现。

Linux 内核完全由 C 语言和汇编语言写成,但是却频繁用到了面向对象的设计思想。在设备驱动方面,

往往为同类的设备设计了一个框架,而框架中的核心层则实现了该设备通用的一些功能。同样的,如果具

体的设备不想使用核心层的函数,它可以重载之。举个例子:

return_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
if (bottom_dev->funca)
return bottom_dev->funca(param1, param2);
/* 核心层通用的 funca 代码 */
...
}

上述 core_funca 的实现中,会检查底层设备是否重载了 funca(),如果重载了,就调用底层的代码,否

则,直接使用通用层的。这样做的好处是,核心层的代码可以处理绝大多数该类设备的 funca()对应的功能,只有少数特殊设备需要重新实现 funca()。

再看一个例子:

copyreturn_type core_funca(xxx_device * bottom_dev, param1_type param1, param1_type param2)
{
/*通用的步骤代码 A */
...
bottom_dev->funca_ops1();
/*通用的步骤代码 B */
...
bottom_dev->funca_ops2();
/*通用的步骤代码 C */
...
bottom_dev->funca_ops3();
}

上述代码假定为了实现 funca(),对于同类设备而言,操作流程一致,都要经过“通用代码 A、底层 ops1、

通用代码 B、底层 ops2、通用代码 C、底层 ops3”这几步,分层设计明显带来的好处是,对于通用代码 A、

B、C,具体的底层驱动不需要再实现,而仅仅只关心其底层的操作 ops1、ops2、ops3。这样的分层化设计

在 Linux 的 input、RTC、MTD、I2C、SPI、TTY、USB 等诸多设备驱动类型中屡见不鲜。

48.1.2 主机驱动和外设驱动分离思想

在 Linux 设备驱动框架的设计中,除了有分层设计实现以外,还有分隔的思想。举一个简单的例子,假设我们要通过 SPI 总线访问某外设,在这个访问过程中,要通过操作 CPU XXX 上的 SPI 控制器的寄存器来达到访问 SPI 外设 YYY 的目的,最简单的方法是:

copyreturn_type xxx_write_spi_yyy(...)
{
xxx_write_spi_host_ctrl_reg(ctrl);
xxx_ write_spi_host_data_reg(buf);
while(!(xxx_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
...
}

如果按照这种方式来设计驱动,结果是对于任何一个 SPI 外设来讲,它的驱动代码都是 CPU 相关的。

也就是说,当然用在 CPU XXX 上的时候,它访问 XXX 的 SPI 主机控制寄存器,当用在 XXX1 的时候,它访问XXX1 的 SPI 主机控制寄存器:

return_type xxx1_write_spi_yyy(...)
{
xxx1_write_spi_host_ctrl_reg(ctrl);
xxx1_ write_spi_host_data_reg(buf);
while(!(xxx1_spi_host_status_reg()&SPI_DATA_TRANSFER_DONE));
...
}

这显然是不能接受的,因为这意味着外设 YYY 用在不同的 CPU XXX 和 XXX1 上的时候需要不同的驱动。那么,我们可以用如图的思想对主机控制器驱动和外设驱动进行分离。这样的结构是,外设 a、b、c 的驱动与主机控制器 A、B、C 的驱动不相关,主机控制器驱动不关心外设,而外设驱动也不关心主机,外设只是访问核心层的通用的 API 进行数据传输,主机和外设之间可以进行任意的组合。

如果我们不进行上图的主机和外设分离,外设 a、b、c 和主机 A、B、C 进行组合的时候,需要 9 个不同的驱动。设想一共有 m 个主机控制器,n 个外设,分离的结果是需要 m+n 个驱动,不分离则需要 m*n 个驱动。Linux SPI、I2C、USB、ASoC(ALSA SoC)等子系统都典型地利用了这种分离的设计思想。

48.2 Platform 平台驱动模型

在 Linux 2.6 以后的设备驱动模型中,需关心总线、设备和驱动这 3 个实体,总线将设备和驱动绑定。

在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找

与之匹配的设备,而匹配由总线完成。

一个现实的 Linux 设备和驱动通常都需要挂接在一种总线上,对于本身依附于 PCI、USB、I2C、SPI 等的设备而言,这自然不是问题,但是在嵌入式系统里面,在 SoC 系统中集成的独立外设控制器、挂接在 SoC

内存空间的外设等却不依附于此类总线。基于这一背景,Linux 发明了一种虚拟的总线,称为 platform 总线,相应的设备称为 platform_device,而驱动成为 platform_driver。

显然,这样做的好处是,实现了此类设备和驱动的分离,增强设备驱动的可移植性。平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线,不是真实的导线。平台总线模型就是把原来的驱动C文件给分成了两个C文件,一个是device.c,一个是driver.c把稳定不变的放在driver.c里面,需要变得就放在了device.c里面。

我们前几章内容给大家讲了杂项设备和字符设备驱动文件,这俩种驱动文件都有一个特点,它把驱动和设备都写在一个驱动文件里面了,但是如果相同的设备很多,就会出现一系列的问题。比如说我有一个硬件平台,这个硬件平台上有很多的模块,比如说有500个模块,这500个模块上都用到了led灯,如果说我用杂项设备来写,虽然用杂项设备比用字符设备的代码量要少,那么我是不是也要写500份这样的代码然后生成设备节点,供我们上层应用控制在不同模块上的led灯呢?那么写500份代码就带来两个问题,第一个是你写了大量重复性的代码。第二个是代码的重用性不是很好,这些驱动我从NXP的平台上移植到三星的平台上,那么要一个个改我们的驱动,但是实际上每个驱动你改的东西并不多,只改了相应的和硬件相关的部分。平台总线模型就很好地解决了这俩个问题。

(1)可以提高代码的重用性

(2)减少重复性代码。

如下图所示,平台总线模型将设备代码和驱动代码分离,将和硬件设备相关的都放到device.c文件里面,驱动部分代码都放到driver.c文件里面,那么500个led的驱动有重复的代码只要写一遍就可以了。

设备,平台总线,驱动的关系如下图所示:

48.2.1 Platform 设备

首先来看一下在 platform 平台模型下硬件设备信息如何表示和注册。

在 platform 平台下用 platform_device 这个结构体表示 platform 设备,如果内核支持设备树的话就不用使用 platform_device 来描述设备了,因为改用设备树去描述了 platform_device。具体定义在内核源码/include/linux/platform_device.h里面,结构体内容如下:

22 struct platform_device {
23 const char *name;
24 int id;
25 bool id_auto;
26 struct device dev;
27 u32 num_resources;
28 struct resource *resource;
29
30 const struct platform_device_id *id_entry;
31 char *driver_override; /* Driver name to force a match */
32
33 /* MFD cell pointer */
34 struct mfd_cell *mfd_cell;
35
36 /* arch specific additions */
37 struct pdev_archdata archdata;
38 };

第 23 行,platform 设备的名字,用来和 platform 驱动相匹配。名字相同才能匹配成功。

第 24 行,ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为

有时候有这种需求)

第 25 行,内置的 device 结构体。

第 27 行,资源结构体数量。

第 28 行,指向一个资源结构体数组。一般包含设备信息。Linux 内核使用 resource

结构体表示资源,resource 结构体内容如下:

18 struct resource {
19 resource_size_t start;
20 resource_size_t end;
21 const char *name;
22 unsigned long flags;
23 struct resource *parent, *sibling, *child;
24 };

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name

表示资源名字,flags 表示资源类型,可选的资源类型都定义在了文件 include/linux/ioport.h 里面,如下所示:

29 #define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */
30
31 #define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
32 #define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
33 #define IORESOURCE_MEM 0x00000200
34 #define IORESOURCE_REG 0x00000300 /* Register offsets */
35 #define IORESOURCE_IRQ 0x00000400
36 #define IORESOURCE_DMA 0x00000800
37 #define IORESOURCE_BUS 0x00001000
......
104 /* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
105 #define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

第 30 行,用来进行与设备驱动匹配用的 id_table 表

第 37 行,添加自己的私有数据。

常用flags宏定义如下所示:

#define IORESOURCE_IO  IO的内存
#define IORESOURCE_MEM	表述一段物理内存
#define IORESOURCE_IRQ	表示中断

在不支持设备树的 Linux 内核版本中需要在通过 platform_device 结构体来描述设备信息,然后使用

platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

如果内核支持设备树的话就不用使用 platform_device 来描述设备了,因为改用设备树去描述了 platform_device。

48.2.2 Platform 驱动

在 Linux 内核中,用 platform_driver 结构体表示platform 驱动,platform_driver结构体定义指定名称的平台设备驱动注册函数和平台设备驱动注销函数,此结构体定义在文件include/linux/platform_device.h 中,内容如下:

struct platform_driver {
/*当driver和device匹配成功的时候,就会执行probe函数*/int (*probe)(struct platform_device *);
/*当driver和device任意一个remove的时候,就会执行这个函数*/int (*remove)(struct platform_device *);
/*当设备收到shutdown命令的时候,就会执行这个函数*/void (*shutdown)(struct platform_device *);
/*当设备收到suspend命令的时候,就会执行这个函数*/int (*suspend)(struct platform_device *, pm_message_t state);
/*当设备收到resume命令的时候,就会执行这个函数*/int (*resume)(struct platform_device *);
//   内置的device_driver 结构体struct device_driver driver;
//  该设备驱动支持的设备的列表  他是通过这个指针去指向 platform_device_id 类型的数组const struct platform_device_id *id_table;bool prevent_deferred_probe;
};

id_table 表保存了很多 id 信息。这些 id 信息存放着这个 platform驱动所支持的驱动类型。id_table 是个表(也就是数组),每个元素的类型为 platform_device_id,platform_device_id 结构体内容如下:

struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
device_driver 结构体定义在 include/linux/device.h,device_driver 结构体内容如下:
1 struct device_driver {
2 const char *name;
3 struct bus_type *bus;
4
5 struct module *owner;
6 const char *mod_name; /* used for built-in modules */
7
8 bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
9
10 const struct of_device_id *of_match_table;
11 const struct acpi_device_id *acpi_match_table;
12
13 int (*probe) (struct device *dev);
14 int (*remove) (struct device *dev);
15 void (*shutdown) (struct device *dev);
16 int (*suspend) (struct device *dev, pm_message_t state);
17 int (*resume) (struct device *dev);
18 const struct attribute_group **groups;
19
20 const struct dev_pm_ops *pm;
21
22 struct driver_private *p;
23 };

0 行,of_match_table 就是采用设备树的时候驱动使用的匹配表,同样是数组,每个匹配项都为

of_device_id 结构体类型,此结构体定义在文件 include/linux/mod_devicetable.h 中,内容如下:

 struct of_device_id {char name[32];char type[32];char compatible[128];const void *data;};

compatible 成员,在支持设备树的内核中,就是通过设备节点的 compatible 属性值和of_match_table 中每个项目的 compatible 成员变量进行比较,如果有相等的就表示设备和此驱动匹配成功。

在编写 platform 驱动的时候,首先定义一个 platform_driver 结构体变量,然后实现结构体中的各个

成员变量,重点是实现匹配方法以及 probe 函数。当驱动和设备匹配成功以后 probe 函数就会执行,驱动

程序具体功能的实现在 probe 函数里面编写。

platform 驱动的注册使用 platform_driver_register 函数来实现,函数原型如下:

int platform_driver_register (struct platform_driver *driver)

参数 driver 为预先创建的 platform_driver 结构体。

通过 platform_driver_unregister 函数来卸载 platform 驱动,函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)

48.2.3 Platform 总线

48.2.3.1 Platform总线简介

前面讲了 platform 设备和 platform 驱动,这就相当于把设备和驱动分离了,那么他们要如何进行匹配呢,这就需要 platform 总线,前面的 platform 设备和 platform 驱动进行内核注册时,也都是注册到总线上。打个比方,就好比相亲,总线是红娘,设备是男方,驱动是女方。

a -- 红娘(总线)负责男方(设备)和女方(驱动)的撮合;     

b -- 男方(女方)找到红娘,说我来登记一下,看有没有合适的姑娘(汉子)—— 设备或驱动的注册;

c -- 红娘这时候就需要看看有没有八字(二者的name 字段)匹配的姑娘(汉子)——match 函数进行匹配,看name是否相同;

d -- 如果八字不合,就告诉男方(女方)没有合适的对象,先等着 —— 设备和驱动会等待,直到匹配成功;

e -- 终于遇到八字匹配的了,那结婚呗,结完婚,男方就向女方交代,我有多少存款,我的房子在哪,钱放在哪等等( struct resource    *resource),女方说好啊,于是去房子里拿钱,去给男方买菜啦,给自己买衣服、化妆品、首饰啊等等(int (*probe)(struct platform_device *) 匹配成功后驱动执行的第一个函数),当然如果男的跟小三跑了(设备卸载),女方也不会继续待下去的(  int (*remove)(struct platform_device *))。

platform总线模型如下图所示:

 

当内核中有驱动注册时,总线就会在右侧的设备中查找,是否有匹配的设备,同样的,当有设备注册

到内核中时,也会在总线左侧查找是否有匹配的驱动。我们来看一下 platform 总线是如何定义的。

在 Linux 内核中使用 bus_type 结构体表示总线,此结构体定义在文件 include/linux/device.h,bus_type 结构体内容如下:

1 struct bus_type {
2 const char *name; /* 总线名字 */
3 const char *dev_name;
4 struct device *dev_root;
5 struct device_attribute *dev_attrs;
6 const struct attribute_group **bus_groups; /* 总线属性 */
7 const struct attribute_group **dev_groups; /* 设备属性 */
8 const struct attribute_group **drv_groups; /* 驱动属性 */
9
10 int (*match)(struct device *dev, struct device_driver *drv);
11 int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
12 int (*probe)(struct device *dev);
13 int (*remove)(struct device *dev);
14 void (*shutdown)(struct device *dev);
15
16 int (*online)(struct device *dev);
17 int (*offline)(struct device *dev);
18 int (*suspend)(struct device *dev, pm_message_t state);
19 int (*resume)(struct device *dev);
20 const struct dev_pm_ops *pm;
21 const struct iommu_ops *iommu_ops;
22 struct subsys_private *p;
23 struct lock_class_key lock_key;
24}

第 10 行,match 函数,此函数就是完成设备和驱动之间的匹配任务,,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

platform 总线是 bus_type 的一个具体实例,定义在文件 drivers/base/platform.c,platform 总线定义如下:

1 struct bus_type platform_bus_type = {
2 .name = "platform",
3 .dev_groups = platform_dev_groups,
4 .match = platform_match,
5 .uevent = platform_uevent,
6 .pm = &platform_dev_pm_ops,
7 };

其中 platform_bus_type 结构体实例就表示 platform 总线,其中重要的就是 platform_match 匹配函数,用来匹配注册到 platform 总线的设备和驱动。我们来看一下 platform 总线上的设备和驱动是如何匹配的,platform_match 函数定义在文件 drivers/base/platform.c 中,函数内容如下所示:

1 static int platform_match(struct device *dev, struct device_driver *drv)
2 {
3 struct platform_device *pdev = to_platform_device(dev);
4 struct platform_driver *pdrv = to_platform_driver(drv);
5
6 /*When driver_override is set,only bind to the matching driver*/
7 if (pdev->driver_override)
8 return !strcmp(pdev->driver_override, drv->name);
9
10 /* Attempt an OF style match first */
11 if (of_driver_match_device(dev, drv))
12 return 1;
13
14 /* Then try ACPI style match */
15 if (acpi_driver_match_device(dev, drv))
16 return 1;
17
18 /* Then try to match against the id table */
19 if (pdrv->id_table)
20 return platform_match_id(pdrv->id_table, pdev) != NULL;
21
22 /* fall-back to driver name match */
23 return (strcmp(pdev->name, drv->name) == 0);
24 }

从上面代码可以看出,platform 总线上设备和驱动的匹配方法一共有四种。

第 11、12 行,OF 类型的匹配,也是设备树采用的匹配方式,of_driver_match_device 函数定义在文件

include/linux/of_device.h 中。device_driver 结构体(表示设备驱动)中有个名为 of_match_table 的成员变量,

此成员变量保存着驱动的 compatible 匹配表,设备树中的每个设备节点的 compatible 属性会和

of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设

备和驱动匹配成功以后 probe 函数就会执行。第 15、16 行,ACPI 匹配方式。第 19、20 行,id_table 匹配方式,每个 platform_driver 结构体有一个 id_table 成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。在上一小节 platform 驱动中也注意到了,在驱动程序中需要创建 platform_device_id 结构体,并指定驱动名称信息。第 23 行,如果 id_table 不存在的话,就直接比较驱动和设备的 name 字段,是不是相等,如果相等的话就匹配成功。

48.2.3.2 编写probe函数的思路

(1)从device.c里面获得硬件资源,因为我们的平台总线将驱动拆成了俩部分,第一部分是device.c,另一部分是driver.c。那么匹配成功了之后,driver.c要从device.c中获得硬件资源,那么driver.c就是在probe函数中获得的。

(2)获得硬件资源之后,就可以在probe函数中注册杂项/字符设备,完善file_operation结构体,并生成设备节点。

获得硬件资源有两种方法:

方法一: 直接获取,不推荐

int beep_probe(struct platform_device *pdev){printk("beep_probe\n");return 0;	
}

beep_probe函数里面有一个形参pdev,他指向了platform_device,那么我们可以直接通过指针访问结构体platform_device的成员变量。比如说我要访问beep_device中的beep_res中的name,参考如下的代码

struct resource beep_res[] = {[0] ={.start = 0x020AC000,    .end  = 0x020AC003,.flags = IORESOURCE_MEM,.name  = "GPIO5_DR",}   
};
struct platform_device beep_device = { .name = "beep_test",.id = -1,       .resource=beep_res,.num_resources =ARRAY_SIZE(beep_res),.dev = {.release = beep_release}
};

那么probe函数可以直接获取,如下所示:

int beep_probe(struct platform_device *pdev){

printk("beep_probe\n");

printk("beep_res is %s\n",pdev->resource[0].name);

return 0;

}

 方法二:使用函数来获取资源

我们也可以使用函数来获取资源,函数如下表所示:

函数

extern struct resource *platform_get_resource(struct platform_device *,unsigned int, unsigned int);

第一个

参数

平台设备名

第二个

参数

资源类型 

第三个

参数

索引号,资源处在同类资源的哪个位置上,大家注意理解同类资源是指flags一模一样。

48.2.3.3 申请I/O内存

申请I/O内存使用request_region函数,其定义在内核源码/include/linux/ioport.h里面,如下图所示:

 

#define request_mem_region(start,n,name) __request_region(&iomem_resource, (start), (n), (name), 0)

第一个参数:起始地址,第二个参数:长度,第三个参数:名字

Linux把基于I/O映射方式的I/O端口和基于内存映射方式的I/O端口资源统称为“I/O区域”(I/O Region)。I/O Region仍然是一种I/O资源,因此它仍然可以用resource结构类型来描述。

Linux是以一种倒置的树形结构来管理每一类I/O资源(如:I/O端口、外设内存、DMA和IRQ)的。每一类I/O资源都对应有一颗倒置的资源树,树中的每一个节点都是一个resource结构,而树的根结点root则描述了该类资源的整个资源空间。其实说白了,request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源。

48.3 实验程序

平台总线模型就是将我们之前写的驱动分成了两个部分,第一个部分就是我们device部分,还有一个部分是driver部分。device.c里面写的是硬件资源,这里硬件资源是指寄存器的地址,中断号,时钟等硬件资源。但是硬件资源不是指具体的硬件资源。比如说我要描述led的硬件资源,他描述的不是led灯,他描述的led灯使用了哪些管脚,这些管脚又涉及到了哪些寄存器。我们以iTOP-iMX8MM开发板为例,写一个基于platform平台驱动模型的蜂鸣器设备的驱动。

48.3.1 编写device.c

iTOP-i.MX8MM开发板是底板加核心板的结构,底板原理图在“8MM开发板\iTOP-i.MX8MM开发板\01-i.MX8MM开发板光盘资料\20210830\01-硬件资料\02-原理图\底板原理图”下载。iTOP-i.MX8MM开发板底板上默认的LED灯被使用了,所以我们可以找个空闲的gpio引脚连接led灯来进行实验。打开底板原理图找到U60 插槽,如下图所示:

 

 我们将led小灯正极插在GPIO_IO13上,负极插在2号引脚接地,硬件连接如下图所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\09\001”路径下。

通过前面的学习,我们已经把基本概念搞懂了。我们在ubuntu 的/home/topeet/imx8mm/09/001目录下新建device.c文件。这里我们以iTOP-IMX8MM开发板为例,写一个LED设备的驱动,修改代码为如下所示:

/** @Author: topeet* @Description: 基于平台设备模型的device.c*/
#include <linux/init.h>            //初始化头文件
#include <linux/module.h>          //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //平台设备所需要的头文件
/*** @description: 释放 flatform 设备模块的时候此函数会执行* @param {structdevice} *dev:要释放的设备* @return {*}*/
void led_release(struct device *dev)
{printk("led_release \n");
}
// 设备资源信息,也就是LED所使用的所有寄存器
struct resource led_res[] = {[0] = {.start = 0x30200000,.end = 0x30200003,.flags = IORESOURCE_MEM,.name = "GPIO1_IO13",},[1] = {.start = 0x30200004,.end = 0x30200007,.flags = IORESOURCE_MEM,.name = "GPIO1_IO13_GDIR",},};
// platform 设备结构体
struct platform_device led_device = {.name = "led_test",.id = -1,.resource = led_res,.num_resources = ARRAY_SIZE(led_res),.dev = {.release = led_release}};
/*** @description:  设备模块加载* @param {*}无* @return {*}无*/
static int device_init(void)
{// 设备信息注册到 Linux 内核platform_device_register(&led_device);printk("platform_device_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void device_exit(void)
{// 设备信息卸载platform_device_unregister(&led_device);printk("gooodbye! \n");
}
module_init(device_init);
module_exit(device_exit);
MODULE_LICENSE("GPL");

接下来将编写的device.c编译为驱动模块。我们将device.c文件拷贝到Ubuntu的/home/topeet/imx8mm/09/001目录下。将上次编译helloworld的Makefile文件和build.sh文件拷贝到device.c同级目录下,修改Makefile为:

obj-m += device.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:make -C $(KDIR) M=$(PWD) clean

文件如下图所示:

驱动编译成功如下图所示: 

驱动编译完,我们通过nfs将编译好的驱动程序加载模块。

我们首先查看下开发板/sys/bus/platform/devices/下是否有平台设备led_test,输入以下命令:

ls /sys/bus/platform/devices/

如下图所示,没有平台设备led_test

 

再次输入以下命令查看,如下图所示,发现有平台设备led_test

ls /sys/bus/platform/devices/

 

我们可以输入以下命令卸载驱动,如下图所示:

rmmod device

 

48.3.2 编写 driver.c

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\002”路径下。

通过48.2.2章节的学习,我们已经把基本概念搞懂了。我们在ubuntu 的/home/topeet/imx8mm/09/002目录下新建driver.c文件。我们可以将第一次编写的helloworld.c里面的代码拷贝到driver.c文件。这里我们以iTOP-iMX8MM开发板为例,写一个设备的驱动,修改代码为如下所示:

/** @Author:topeet* @Description: 基于平台设备模型的driver.c*/
#include <linux/init.h>            //初始化头文件
#include <linux/module.h>          //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //平台设备所需要的头文件
/*** @description: led_probe,驱动和设备匹配成功会进入此函数* @param {structplatform_device} *pdev* @return {*}*/
int led_probe(struct platform_device *pdev)
{printk("led_probe\n");return 0;
}
/*** @description: led_remove,当driver和device任意一个remove的时候,就会执行这个函数* @param {structplatform_device} *pdev* @return {*}*/
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}// platform 驱动结构体
struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test"},
};
/*** @description: 设备模块加载* @param {*}无* @return {*}无*/
static int led_driver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void led_driver_exit(void)
{// platform驱动卸载platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

接下来我们将driver.c驱动编译成模块。将上次编译helloworld的Makefile文件和build.sh拷贝到driver.c同级目录下,修改Makefile为:

obj-m += driver.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:make -C $(KDIR) M=$(PWD) clean

驱动编译成功如下图所示:

驱动编译完,我们通过nfs将编译好的驱动程序加载模块。

我们进入共享目录,加载第48.3.2章节编译好的device.ko和本章节编译好的driver.ko,如下图所示:

insmod driver.ko

insmod device.ko

由上图可知打印信息可知,设备和驱动匹配成功进入probe函数。

 

我们将driver驱动模块卸载掉,修改一下driver.c代码, platform_driver结构体中const struct platform_device_id *id_table比device_driver结构体中的name的优先级要高,优先和id_table进行匹配,我们修改driver.c的代码来验证一下,修改后的driver.c如下所示,程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\09\003”路径下。 

/** @Author:topeet* @Description: 基于平台设备模型的driver.c*/
#include <linux/init.h>            //初始化头文件
#include <linux/module.h>          //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //平台设备所需要的头文件
/*** @description: led_probe,驱动和设备匹配成功会进入此函数* @param {structplatform_device} *pdev* @return {*}*/
int led_probe(struct platform_device *pdev)
{printk("led_probe\n");return 0;
}
/*** @description: led_remove,当driver和device任意一个remove的时候,就会执行这个函数* @param {structplatform_device} *pdev* @return {*}*/
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}//该设备驱动支持的设备的列表 ,他是通过这个指针去指向 platform_device_id 类型的数组
const struct platform_device_id led_idtable = {.name = "123", //设备名字叫“123”
};// platform 驱动结构体
struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test"},.id_table=&led_idtable};
/*** @description: 设备模块加载* @param {*}无* @return {*}无*/
static int led_driver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void led_driver_exit(void)
{// platform驱动卸载platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

修改完driver.c文件,编译通过后,加载驱动模块,如下图所示:

所以得出结论,设置了id_table后,优先和id_table进行匹配,因为id_table中name属性为“123”,和device.c中的name匹配不上,所以不会进入probe函数。那么如果我们将id_table中name属性设为和device.c中的name一样呢?我们修改driver.c文件试试看。程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\09\004”路径下。 

/** @Author:topeet* @Description: 基于平台设备模型的driver.c*/
#include <linux/init.h>            //初始化头文件
#include <linux/module.h>          //最基本的文件,支持动态添加和卸载模块。
#include <linux/platform_device.h> //平台设备所需要的头文件
/*** @description: led_probe,驱动和设备匹配成功会进入此函数* @param {structplatform_device} *pdev* @return {*}*/
int led_probe(struct platform_device *pdev)
{printk("led_probe\n");return 0;
}
/*** @description: led_remove,当driver和device任意一个remove的时候,就会执行这个函数* @param {structplatform_device} *pdev* @return {*}*/
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}//该设备驱动支持的设备的列表 ,他是通过这个指针去指向 platform_device_id 类型的数组
const struct platform_device_id led_idtable = {.name = "led_test", //设备名字叫“123”
};// platform 驱动结构体
struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test"},.id_table=&led_idtable};
/*** @description: 设备模块加载* @param {*}无* @return {*}无*/
static int led_driver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void led_driver_exit(void)
{// platform驱动卸载platform_driver_unregister(&led_driver);printk("goodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

修改完driver.c文件,编译通过后,加载驱动模块,如下图所示:

由上图可知,driver.c中id_table中name属性设为和device.c中的name一样的话,就可以匹配进入probe函数了。不论我们先加载driver.ko还是device.ko都是一样的。

48.3.3 编写probe

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\09\005”路径下。

通过48.2.3章节编写probe函数理论基础的学习,我们已经把基本概念搞懂了。我们在第48.3.2章driver.c的基础上继续编写probe函数。

/** @Author: topeet* @Description: 基于平台设备模型的driver.c,在probe函数中获取硬件资源*/
//初始化头文件
#include <linux/init.h>
//最基本的文件,支持动态添加和卸载模块。
#include <linux/module.h>
//平台设备所需要的头文件 
#include <linux/platform_device.h>
#include <linux/ioport.h>
/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
//文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/fs.h>
//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h>
//包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/io.h>
#define GPIO1_IO13 0x30200000      //led数据寄存器物理地址
#define GPIO1_IO13_GDIR 0x30200004 //led数据寄存器方向寄存器物理地址
unsigned int *vir_gpio1_io13;      //存放映射完的虚拟地址的首地址
unsigned int *vir_gpio1_io13_gdir; //存放映射完的虚拟地址的首地址
int ret; //返回值struct resource *led_mem;
struct resource *led_mem_gdir;
struct resource *led_mem_tmp;/***************************************************************************************** @brief led_probe : 与设备信息层(device.c)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{printk("led_probe\n");/*获取硬件资源方法一: 不推荐*///printk("led_res is %s\n",pdev->resource[0].name);//return 0;/*获取硬件资源方法二: 推荐*/led_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);led_mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);if (led_mem == NULL||led_mem_gdir == NULL){printk("platform_get_resource is error\n");return -EBUSY;}printk("led_res start is 0x%llx \n", led_mem->start);printk("led_res end is 0x%llx \n", led_mem->end);printk("led_res start is 0x%llx \n", led_mem_gdir->start);printk("led_res end is 0x%llx \n", led_mem_gdir->end);return 0;
}
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}const struct platform_device_id led_idtable = {.name = "led_test",
};
// platform 驱动结构体
struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "123"},.id_table = &led_idtable};
/*** @description: 设备模块加载* @param {*}无* @return {*}无*/
static int led_driver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void led_driver_exit(void)
{platform_driver_unregister(&led_driver);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

接下来我们将driver.c驱动编译成模块,我们将driver.c文件拷贝到Ubuntu的/home/topeet/imx8mm/09/005目录下。将上次编译使用的Makefile文件和build.sh文件拷贝到driver.c同级目录下,修改Makefile为:

obj-m += driver.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:make -C $(KDIR) M=$(PWD) clean

我们通过nfs将编译好的驱动程序加载模块,我们进入共享目录,加载第48.3.1章编译好的device.ko和本章节编译好的driver.ko,如下图所示:

从上图可以看到,有打印led_res的内容,已经获取到了硬件资源。那么是不是就可以直接操作相关的寄存器地址了呢?是不是直接ioremap,像我们注册杂项设备那样来操作相关的寄存器呢?

接下来我们需要注册一个杂项设备,完整的驱动代码如下所示:

程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\09-Platform设备驱动\09\006”路径下。

 

/** @Author: topeet* @Description: 基于平台设备模型的driver.c,在probe函数中获取硬件资源后,注册一个杂项设备*/
//初始化头文件
#include <linux/init.h>
//最基本的文件,支持动态添加和卸载模块。
#include <linux/module.h>
//平台设备所需要的头文件 
#include <linux/platform_device.h>
#include <linux/ioport.h>
/*注册杂项设备头文件*/
#include <linux/miscdevice.h>
//文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/fs.h>
//包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/uaccess.h>
//包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/io.h>
#define GPIO1_IO13 0x30200000      //led数据寄存器物理地址
#define GPIO1_IO13_GDIR 0x30200004 //led数据寄存器方向寄存器物理地址
unsigned int *vir_gpio1_io13;      //存放映射完的虚拟地址的首地址
unsigned int *vir_gpio1_io13_gdir; //存放映射完的虚拟地址的首地址
int ret;                           //返回值struct resource *led_mem;
struct resource *led_mem_gdir;
struct resource *led_mem_tmp;
/***************************************************************************************** @brief misc_read : 用户空间向设备写入数据时执行此函数* @param file  : 文件* @param ubuf  : 指向用户空间数据缓冲区* @return 成功返回 0       ****************************************************************************************/
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{printk("misc_read\n ");return 0;
}
/***************************************************************************************** @brief misc_write : 用户空间向驱动模块写入数据时执行此函数,对数据进行判断,控制LED亮灭* @param file  : 文件* @param ubuf  : 指向用户空间数据缓冲区* @return 成功返回 0 ,失败返回 -1      ****************************************************************************************/
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){char kbuf[64] = {0}; //保存的是从应用层读取到的数据if (copy_from_user(kbuf, ubuf, size) != 0){printk("copy_from_user error \n ");return -1;}printk("kbuf is %d\n ", kbuf[0]);*vir_gpio1_io13_gdir |= (1 << 13);if (kbuf[0] == 1) //如果传递进来数据为1,则打开LED{*vir_gpio1_io13 |= (1 << 13);}else if (kbuf[0] == 0) //如果传递进来数据为0,关闭LED*vir_gpio1_io13 &= ~(1 << 13);return 0;
}
/***************************************************************************************** @description:misc_release 释放 platform 设备模块的时候此函数会执行* @param {structinode} *inode* @param {structfile} *file * @return {*}****************************************************************************************/
int misc_release(struct inode *inode, struct file *file)
{printk("hello misc_relaease bye bye \n ");return 0;
}
/***************************************************************************************** @brief misc_open : 打开设备节点时执行此函数,并初始化GPIO* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int misc_open(struct inode *inode, struct file *file)
{printk("hello misc_open\n ");return 0;
}struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops,
};
/***************************************************************************************** @brief led_probe : 与设备信息层(device.c)匹配成功后自动执行此函数,* @param inode : 文件索引* @param file  : 文件* @return 成功返回 0           ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{printk("led_probe\n");/*获取硬件资源方法一: 不推荐*///printk("led_res is %s\n",pdev->resource[0].name);//return 0;/*获取硬件资源方法二: 推荐*/led_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);led_mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);if (led_mem == NULL || led_mem_gdir == NULL){printk("platform_get_resource is error\n");return -EBUSY;}printk("led_res start is 0x%llx \n", led_mem->start);printk("led_res end is 0x%llx \n", led_mem->end);printk("led_res start is 0x%llx \n", led_mem_gdir->start);printk("led_res end is 0x%llx \n", led_mem_gdir->end);/*led_mem_tmp =  request_mem_region(led_mem->start,led_mem->end-led_mem->start+1,"led");if(led_mem_tmp == NULL){printk("  request_mem_region is error\n");goto err_region;err_region:release_mem_region(led_mem->start,led_mem->end-led_mem->start+1);return -EBUSY;}*//*****************************************************************///映射GPIO资源vir_gpio1_io13 = ioremap(led_mem->start, 4);if (vir_gpio1_io13 == NULL){printk("GPIO1_IO13 ioremap is error \n");return EBUSY;}printk("GPIO1_IO13 ioremap is ok \n");vir_gpio1_io13_gdir = ioremap(led_mem_gdir->start, 4);if (vir_gpio1_io13_gdir == NULL){printk("GPIO1_IO13_GDIR ioremap is error \n");return EBUSY;}printk("GPIO1_IO13_GDIR ioremap is ok \n");//注册杂项设备ret = misc_register(&misc_dev);if (ret < 0){printk("misc registe is error \n");}printk("misc registe is succeed \n");return 0;
}
int led_remove(struct platform_device *pdev)
{printk("led_remove\n");return 0;
}const struct platform_device_id led_idtable = {.name = "led_test",
};
// platform 驱动结构体
struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.owner = THIS_MODULE,.name = "led_test"},.id_table = &led_idtable};
/*** @description: 设备模块加载* @param {*}无* @return {*}无*/
static int led_driver_init(void)
{int ret = 0;// platform驱动注册到 Linux 内核ret = platform_driver_register(&led_driver);if (ret < 0){printk("platform_driver_register error \n");}printk("platform_driver_register ok \n");return 0;
}
/*** @description: 设备模块注销* @param {*}无* @return {*}无*/
static void led_driver_exit(void)
{platform_driver_unregister(&led_driver);misc_deregister(&misc_dev);iounmap(vir_gpio1_io13);iounmap(vir_gpio1_io13_gdir);printk("gooodbye! \n");
}
module_init(led_driver_init);
module_exit(led_driver_exit);
MODULE_LICENSE("GPL");

 编译driver.c文件为驱动模块,如下图所示:

加载driver.ko,如下图所示: 

 

输入命令查看是否生成设备节点

ls /dev/hello_misc

 

编写应用程序app.c,如下所示: 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int fd;char buf[64] = {0};fd = open("/dev/hello_misc",O_RDWR);if(fd < 0){perror("open error \n");return fd;}buf[0] = atoi(argv[1]);//atoi()//read(fd,buf,sizeof(buf));write(fd,buf,sizeof(buf));//printf("buf is %s\n",buf);close(fd);return 0;
}

我们将app.c拷贝到Ubuntu的/home/topeet/imx8mm/09/006目录下,并输入命令编译为app

 

我们在开发板上面运行应用程序,如下图所示:

./app 1

./app 0

当我们输入命令“./app 1”,可以看到led亮了,当我们输入命令“./app 0”,led灭了,如下图所示。到目前为止48.3章节我们完整的写了一个以LED为代表的平台总线模型的驱动。

 

 

 

 

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

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

相关文章

java的DOS命令

目录 1.DOS命令了解 DOS介绍 常用的dos命令1 DOS的基本原理 相对路径与绝对路径 常用的dos命令2 2.本章作业 1.编写hello&#xff0c;world程序 2.输出个人基本信息 3.jdk&#xff0c;jre&#xff0c;jvm关系 4.环境变量path配置及作用 5.java编写步骤 6.java编写7…

昇思25天学习打卡营第4天 | 网络构建

在学习和实践MindSpore神经网络模型构建的过程中&#xff0c;我深刻理解了MindSpore中如何通过nn.Cell类来构建和管理复杂的神经网络模型。通过这次的实践&#xff0c;我对神经网络的基本构建和应用有了更加全面的认识&#xff0c;以下是我学习过程中所总结的几点心得&#xff…

科普文:云计算服务类型IaaS, PaaS, SaaS, BaaS, Faas说明

概叙 基本概念 IaaS, PaaS, SaaS, BaaS, 和 FaaS 是云计算服务的不同类型&#xff0c;‌它们各自提供了不同的服务层次和功能。‌ IaaS (Infrastructure as a Service基础设施即服务) 提供基础设施服务&#xff0c;‌包括服务器、‌存储、‌网络等硬件资源。‌用户可以在这些…

Linux嵌入式学习——数据结构——概念和Seqlist

数据结构 相互之间存在一种或多种特定关系的数据元素的集合。 逻辑结构 集合&#xff0c;所有数据在同一个集合中&#xff0c;关系平等。 线性&#xff0c;数据和数据之间是一对一的关系。数组就是线性表的一种。 树&#xff0c; 一对多 图&#xff0c;多对多 …

项目策划不再愁,可道云teamOS流程图助你轻松上阵

在当今这个快节奏、高协同的工作环境中&#xff0c;每一项任务的推进都离不开清晰、高效的沟通与规划。 在线流程图工具&#xff0c;作为数字时代团队协作的得力助手&#xff0c;以其直观易懂的呈现方式、灵活多变的编辑功能&#xff0c;极大地简化了复杂项目的策划与执行流程…

ip地址设置了重启又改变了怎么回事

在数字世界的浩瀚星海中&#xff0c;IP地址就如同每个设备的“身份证”&#xff0c;确保它们在网络中准确无误地定位与通信。然而&#xff0c;当我们精心为设备配置好IP地址后&#xff0c;却时常遭遇一个令人费解的现象&#xff1a;一旦设备重启&#xff0c;原本设定的IP地址竟…

5.Fabric的共识机制

在Fabric中,有以下3中典型共识机制。 Solo共识 solo共识机制只能用于单节点模式,即只能有一个Orderer节点,因此,其共识过程很简单,每接收到一个交易信息,就在共识模块的控制下产生区块并广播给节点存储到账本中。 Solo 模式下的共识只适用于一个Orderer节点,所以可以在…

C/C++ 内存管理

C/C 内存管理 1. C/C内存分布2. C语言中动态内存管理方式&#xff1a;malloc/calloc/realloc/free3. C内存管理方式3.1 new/delete操作内置类型3.2 new和delete操作自定义类型 4. operator new与operator delete函数&#xff08;重要点进行讲解&#xff09;4.1 operator new与o…

【Java】:洗牌功能和杨辉三角的实现

洗牌 此操作包含的基本功能有&#xff1a; 组牌&#xff1a;组建 52 张扑克牌 四种花色&#xff1a;“♥️”&#xff0c;“♠️”&#xff0c;“⬛️”&#xff0c;“♣️”每种花色 13 张牌&#xff1a;1~13 洗牌&#xff1a;将 52 张扑克牌打乱顺序发牌&#xff1a;给三个人…

【深度学习入门篇 ⑪】自注意力机制

【&#x1f34a;易编橙&#xff1a;一个帮助编程小伙伴少走弯路的终身成长社群&#x1f34a;】 大家好&#xff0c;我是小森( &#xfe61;ˆoˆ&#xfe61; ) &#xff01; 易编橙终身成长社群创始团队嘉宾&#xff0c;橙似锦计划领衔成员、阿里云专家博主、腾讯云内容共创官…

Vue3 SvgIcon组件开发

在前面自定义tree组件继续功能迭代前&#xff0c;我们先开发一个通用的ScgIcon组件&#xff0c;用于后续组件模板中小图标的展示。 引入iconfont 官网&#xff1a;https://www.iconfont.cn/ 选取图标进行下载&#xff0c;只取iconfont.js文件 在prettier中忽略该文件&#x…

【YOLOv5/v7改进系列】引入CoordConv——坐标卷积

一、导言 与标准卷积层相比&#xff0c;CoordConv 的主要区别在于它显式地考虑了位置信息。在标准卷积中&#xff0c;卷积核在输入上滑动时&#xff0c;仅关注局部区域的像素强度&#xff0c;而忽略其绝对位置。CoordConv 通过在输入特征图中添加坐标信息&#xff0c;使得卷积…

STM32CubeIDE(CAN)

目录 一、概念 1、简述 2、CAN 的几种模式 二、实践 1、环回模式轮询通信 1.1 软件配置 1.2 代码编写 2、环回模式中断通信 2.1 软件配置 2.2 代码编写 一、概念 1、简述 STM32微控制器系列包含多个型号&#xff0c;其中一些型号集成了CAN&#xff08;Controller Are…

Vuex--全局共享数据

目录 一 是什么? 二 怎么用&#xff1f; 三 注意点 一 是什么? 在此之前&#xff0c;我们使用vue的数据全部放在每个组件的data区域里面&#xff0c;这里return里面存的都是这个组件要用到的数据&#xff0c;但是这里面的数据是局部的数据&#xff0c;也就是说这些数据是这…

Chrome v8 pwn 前置

文章目录 参考用到啥再更新啥简介环境搭建depot_tools和ninjaturbolizer 调试turbolizer使用结构数组 ArrayArrayBufferDataViewWASMJSObject结构Hidden Class命名属性-快速属性Fast Properties命名属性-慢速属性Slow Properties 或 字典模式Dictionary Mode编号属性 (Elements…

基于springboot+vue+uniapp的宿舍管理系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

van-dialog 组件调用报错

报错截图 报错原因 这个警告表明 vue 在渲染页面时遇到了一个未知的自定义组件 <van-dialog>&#xff0c;并且提示可能是由于未正确注册该组件导致的。在 vue 中&#xff0c;当我们使用自定义组件时&#xff0c;需要先在 vue 实例中注册这些组件&#xff0c;以便 vue 能…

Json结构解析比较

文章目录 前言正文一、项目简介二、核心代码1、 JavaBeanParser2、 JsonStructCompare3、 Client 测试结果 前言 本次练习&#xff0c;主要是针对于两个Json的结构差异。 多用于测试场景&#xff0c;比如一个很大的Json报文&#xff0c;需要和现有的Json报文对比&#xff0c;看…

【快速逆向二/无过程/有源码】掌上高考—2024高考志愿填报服务平台

逆向日期&#xff1a;2024.07.21 使用工具&#xff1a;Node.js 加密工具&#xff1a;Crypto-js标准库 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&…

百日筑基第二十八天-23种设计模式-行为型总汇

百日筑基第二十八天-23种设计模式-行为型总汇 文章目录 百日筑基第二十八天-23种设计模式-行为型总汇前言模板方法模式简介模板方式的特点模板方法模式结构类图模板方式模式案例分析模板方法模式应用源码分析模板方法模式的注意事项和细节 迭代器模式迭代器模式结构类图迭代器模…