Linux学习第28天:Platform设备驱动开发(二): 专注与分散

Linux版本号4.1.15   芯片I.MX6ULL                                    大叔学Linux    品人间百味  思文短情长 


三、硬件原理图分析

四、驱动开发

1、platform设备与驱动程序开发

53 /*
54 * 设备资源信息,也就是 LED0 所使用的所有寄存器
55 */
56 static struct resource led_resources[] = {
57 [0] = {
58 .start = CCM_CCGR1_BASE,
59 .end = (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
60 .flags = IORESOURCE_MEM,
61 },
62 [1] = {
63 .start = SW_MUX_GPIO1_IO03_BASE,
64 .end = (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
65 .flags = IORESOURCE_MEM,
66 },
67 [2] = {
68 .start = SW_PAD_GPIO1_IO03_BASE,
69 .end = (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
70 .flags = IORESOURCE_MEM,
71 },
72 [3] = {
73 .start = GPIO1_DR_BASE,
74 .end = (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
75 .flags = IORESOURCE_MEM,
76 },
77 [4] = {
78 .start = GPIO1_GDIR_BASE,
79 .end = (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
80 .flags = IORESOURCE_MEM,
81 },
82 };

        led_resources 数组,也就是设备资源,描述了 LED 所要使用到的寄存器信息,也就是 IORESOURCE_MEM 资源。

85 /*
86 * platform 设备结构体
87 */
88 static struct platform_device leddevice = {
89 .name = "imx6ul-led",
90 .id = -1,
91 .dev = {
92 .release = &led_release,
93 },
94 .num_resources = ARRAY_SIZE(led_resources),
95 .resource = led_resources,
96 };

        platform 设备结构体变量 leddevice,这里要注意 name 字段为“imx6ul-led”,所
以稍后编写 platform 驱动中的 name 字段也要为“imx6ul-led”,否则设备和驱动匹配失败。

98 /*
99 * @description : 设备模块加载
100 * @param : 无
101 * @return : 无
102 */
103 static int __init leddevice_init(void)
104 {
105 return platform_device_register(&leddevice);
106 }

        设备模块加载函数,在此函数里面通过 platform_device_register 向 Linux 内核注册 leddevice 这个 platform 设备。

108 /*
109 * @description : 设备模块注销
110 * @param : 无
111 * @return : 无
112 */
113 static void __exit leddevice_exit(void)
114 {
115 platform_device_unregister(&leddevice);
116 }

        设备模块卸载函数,在此函数里面通过 platform_device_unregister 从 Linux内核中删除掉 leddevice 这个 platform 设备。

34 #define LEDDEV_CNT 1 /* 设备号长度 */
35 #define LEDDEV_NAME "platled" /* 设备名字 */
36 #define LEDOFF 0
37 #define LEDON 1
38
39 /* 寄存器名 */
40 static void __iomem *IMX6U_CCM_CCGR1;
41 static void __iomem *SW_MUX_GPIO1_IO03;
42 static void __iomem *SW_PAD_GPIO1_IO03;
43 static void __iomem *GPIO1_DR;
44 static void __iomem *GPIO1_GDIR;
45
46 /* leddev 设备结构体 */
47 struct leddev_dev{
48 dev_t devid; /* 设备号 */
49 struct cdev cdev; /* cdev */
50 struct class *class; /* 类 */
51 struct device *device; /* 设备 */
52 int major; /* 主设备号 */
53 };
54
55 struct leddev_dev leddev; /* led 设备 */
56
57 /*
58 * @description : LED 打开/关闭
59 * @param - sta : LEDON(0) 打开 LED, LEDOFF(1) 关闭 LED
60 * @return : 无
61 */
62 void led0_switch(u8 sta)
63 {
64 u32 val = 0;
65 if(sta == LEDON){
66 val = readl(GPIO1_DR);
67 val &= ~(1 << 3);
68 writel(val, GPIO1_DR);
69 }else if(sta == LEDOFF){
70 val = readl(GPIO1_DR);
71 val|= (1 << 3);
72 writel(val, GPIO1_DR);
73 }
74 }
75
76 /*
77 * @description : 打开设备
78 * @param – inode : 传递给驱动的 inode
79 * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
80 * 一般在 open 的时候将 private_data 指向设备结构体。
81 * @return : 0 成功;其他 失败
82 */
83 static int led_open(struct inode *inode, struct file *filp)
84 {
85 filp->private_data = &leddev; /* 设置私有数据 */
86 return 0;
87 }
88
89 /*
90 * @description : 向设备写数据
91 * @param – filp : 设备文件,表示打开的文件描述符
92 * @param - buf : 要写给设备写入的数据
93 * @param - cnt : 要写入的数据长度
94 * @param - offt : 相对于文件首地址的偏移
95 * @return : 写入的字节数,如果为负值,表示写入失败
96 */
97 static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
98 {
99 int retvalue;
100 unsigned char databuf[1];
101 unsigned char ledstat;
102
103 retvalue = copy_from_user(databuf, buf, cnt);
104 if(retvalue < 0) {
105 return -EFAULT;
106 }
107
108 ledstat = databuf[0]; /* 获取状态值 */
109 if(ledstat == LEDON) {
110 led0_switch(LEDON); /* 打开 LED 灯 */
111 }else if(ledstat == LEDOFF) {
112 led0_switch(LEDOFF); /* 关闭 LED 灯 */
113 }
114 return 0;
115 }
116
117 /* 设备操作函数 */
118 static struct file_operations led_fops = {
119 .owner = THIS_MODULE,
120 .open = led_open,
121 .write = led_write,
122 };

        以上是传统的字符设备驱动。

124 /*
125 * @description : flatform 驱动的 probe 函数,当驱动与设备
126 * 匹配以后此函数就会执行
127 * @param - dev : platform 设备
128 * @return : 0,成功;其他负值,失败
129 */
130 static int led_probe(struct platform_device *dev)
131 {
132 int i = 0;
133 int ressize[5];
134 u32 val = 0;
135 struct resource *ledsource[5];
136
137 printk("led driver and device has matched!\r\n");
138 /* 1、获取资源 */
139 for (i = 0; i < 5; i++) {
140 ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);
141 if (!ledsource[i]) {
142 dev_err(&dev->dev, "No MEM resource for always on\n");
143 return -ENXIO;
144 }
145 ressize[i] = resource_size(ledsource[i]);
146 }
147
148 /* 2、初始化 LED */
149 /* 寄存器地址映射 */
150 IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
151 SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
152 SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
153 GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
154 GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
155
156 val = readl(IMX6U_CCM_CCGR1);
157 val &= ~(3 << 26); /* 清除以前的设置 */
158 val |= (3 << 26); /* 设置新值 */
159 writel(val, IMX6U_CCM_CCGR1);
160
161 /* 设置 GPIO1_IO03 复用功能,将其复用为 GPIO1_IO03 */
162 writel(5, SW_MUX_GPIO1_IO03);
163 writel(0x10B0, SW_PAD_GPIO1_IO03);
164
165 /* 设置 GPIO1_IO03 为输出功能 */
166 val = readl(GPIO1_GDIR);
167 val &= ~(1 << 3); /* 清除以前的设置 */
168 val |= (1 << 3); /* 设置为输出 */
169 writel(val, GPIO1_GDIR);
170
171 /* 默认关闭 LED1 */
172 val = readl(GPIO1_DR);
173 val |= (1 << 3) ;
174 writel(val, GPIO1_DR);
175
176 /* 注册字符设备驱动 */
177 /*1、创建设备号 */
178 if (leddev.major) { /* 定义了设备号 */
179 leddev.devid = MKDEV(leddev.major, 0);
180 register_chrdev_region(leddev.devid, LEDDEV_CNT,
LEDDEV_NAME);
181 } else { /* 没有定义设备号 */
182 alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,
LEDDEV_NAME);
183 leddev.major = MAJOR(leddev.devid);
184 }
185
186 /* 2、初始化 cdev */
187 leddev.cdev.owner = THIS_MODULE;
188 cdev_init(&leddev.cdev, &led_fops);
189
190 /* 3、添加一个 cdev */
191 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
192
193 /* 4、创建类 */
194 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
195 if (IS_ERR(leddev.class)) {
196 return PTR_ERR(leddev.class);
197 }
198
199 /* 5、创建设备 */
200 leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
201 if (IS_ERR(leddev.device)) {
202 return PTR_ERR(leddev.device);
203 }
204
205 return 0;
206 }

        probe 函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出“led driver and device has matched!”这样语句。在 probe 函数里面初始化 LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。

208 /*
209 * @description :移除 platform 驱动的时候此函数会执行
210 * @param - dev : platform 设备
211 * @return : 0,成功;其他负值,失败
212 */
213 static int led_remove(struct platform_device *dev)
214 {
215 iounmap(IMX6U_CCM_CCGR1);
216 iounmap(SW_MUX_GPIO1_IO03);
217 iounmap(SW_PAD_GPIO1_IO03);
218 iounmap(GPIO1_DR);
219 iounmap(GPIO1_GDIR);
220
221 cdev_del(&leddev.cdev); /* 删除 cdev */
222 unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
223 device_destroy(leddev.class, leddev.devid);
224 class_destroy(leddev.class);
225 return 0;
226 }

        remove 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。

228 /* platform 驱动结构体 */
229 static struct platform_driver led_driver = {
230 .driver = {
231 .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
232 },
233 .probe = led_probe,
234 .remove = led_remove,
235 };

        platform_driver 驱动结构体,注意 name 字段为"imx6ul-led",和我们在leddevice.c 文件里面设置的设备 name 字段一致。

237 /*
238 * @description : 驱动模块加载函数
239 * @param : 无
240 * @return : 无
241 */
242 static int __init leddriver_init(void)
243 {
244 return platform_driver_register(&led_driver);
245 }

        驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。

247 /*
248 * @description : 驱动模块卸载函数
249 * @param : 无
250 * @return : 无
251 */
252 static void __exit leddriver_exit(void)
253 {
254 platform_driver_unregister(&led_driver);
255 }

        驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux
内核卸载 led_driver 驱动。

2、测试APP开发

1 #include "stdio.h"
2 #include "unistd.h"
3 #include "sys/types.h"
4 #include "sys/stat.h"
5 #include "fcntl.h"
6 #include "stdlib.h"
7 #include "string.h"
8 /***************************************************************
9 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
10 文件名 : ledApp.c
11 作者 : 左忠凯
12 版本 : V1.0
13 描述 : platform 驱动驱测试 APP。
14 其他 : 无
15 使用方法 : ./ledApp /dev/platled 0 关闭 LED
16 ./ledApp /dev/platled 1 打开 LED
17 论坛 : www.openedv.com
18 日志 : 初版 V1.0 2019/8/16 左忠凯创建
19 ***************************************************************/
20 #define LEDOFF 0
21 #define LEDON 1
22
23 /*
24 * @description : main 主程序
25 * @param - argc : argv 数组元素个数
26 * @param - argv : 具体参数
27 * @return : 0 成功;其他 失败
28 */
29 int main(int argc, char *argv[])
30 {
31 int fd, retvalue;
32 char *filename;
33 unsigned char databuf[2];
34
35 if(argc != 3){
36 printf("Error Usage!\r\n");
37 return -1;
38 }
39
40 filename = argv[1];
41 /* 打开 led 驱动 */
42 fd = open(filename, O_RDWR);
43 if(fd < 0){
44 printf("file %s open failed!\r\n", argv[1]);
45 return -1;
46 }
47
48 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
49 retvalue = write(fd, databuf, sizeof(databuf));
50 if(retvalue < 0){
51 printf("LED Control Failed!\r\n");
52 close(fd);
53 return -1;
54 }
55
56 retvalue = close(fd); /* 关闭文件 */
57 if(retvalue < 0){
58 printf("file %s close failed!\r\n", argv[1]);
59 return -1;
60 }
61 return 0;
62 }

五、运行测试

1、编译驱动程序和测试APP

4 obj-m := leddevice.o leddriver.o

        设置 obj-m 变量的值为“leddevice.o leddriver.o”。

        输入如下命令编译出驱动模块文件:
                make -j32
        编译成功以后就会生成一个名为“leddevice.ko leddriver.ko”的驱动模块文件。

        输入如下命令编译测试 ledApp.c 这个测试程序:
                arm-linux-gnueabihf-gcc ledApp.c -o ledApp
        编译成功以后就会生成 ledApp 这个应用程序。

2、运行测试

depmod //第一次加载驱动的时候需要运行此命令
modprobe leddevice.ko //加载设备模块
modprobe leddriver.ko //加载驱动模块

        根文件系统中/sys/bus/platform/目录下保存着当前板子 platform 总线下的设备和驱动,其中
devices 子目录为 platform 设备, drivers 子目录为 plartofm 驱动。查看/sys/bus/platform/devices/
目录,看看我们的设备是否存在,我们在 leddevice.c 中设置 leddevice(platform_device 类型)的
name 字段为“imx6ul-led”,也就是设备名字为 imx6ul-led,因此肯定在/sys/bus/platform/devices/
目录下存在一个名字“imx6ul-led”的文件,否则说明我们的设备模块加载失败,结果如图 所示:

        查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在 leddriver.c 中设置
led_driver (platform_driver 类型)的 name 字段为“imx6ul-led”,因此会在/sys/bus/platform/drivers/
目录下存在名为“imx6ul-led”这个文件,结果如图 所示:

驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以
后就会输出如图 所示一行语句:

        驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:

/ledApp /dev/platled 1 //打开 LED 灯


        在输入如下命令关闭 LED 灯:

./ledApp /dev/platled 0 //关闭 LED 灯


        观察一下 LED 灯能否打开和关闭,如果可以的话就说明驱动工作正常,如果要卸载驱动的
话输入如下命令即可:
rmmod leddevice.ko
rmmod leddriver.ko

六、总结:

        本篇笔记主要学习了platform设备驱动开发的相关概念。将分成两次笔记进行学习。本次笔记主要学习platform设备驱动开发相关的理论知识。主要内容包括:Linux驱动的分离与分层、platform平台驱动模型简介。其中驱动的分离与分层有包括驱动的分离、驱动的分层。platform平台驱动模型简介主要包括platform总线、platform驱动与platform设备。
————————————————
版权声明:本文为CSDN博主「大叔学Linux」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/jiage987450/article/details/134125677        


本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自定义SpringBoot启动图标

在SpringBoot项目的resources目录下创建banner.txt文件 在https://www.bootschool.net/网站上复制Ascll艺术字&#xff08;图&#xff09;粘贴到banner.txt中保存。 启动项目就会加载 可以修改颜色&#xff0c;和版本号 ${application.version} 输出版本 ${spring-boot.v…

计网note

其他 未分类文档 CDMA是码分多路复用技术 和CMSA不是一个东西 UPD是只确保发送 但是接收端收到之后(使用检验和校验 除了检验的部分相加 对比检验和是否相等。如果不相同就丢弃。 复用和分用是发生在上层和下层的问题。通过比如时分多路复用 频分多路复用等。TCP IP 应用层的…

uni-app华为审核被拒,驳回原因:您的应用在运行时,未见向用户告知权限申请的目的

华为审核被拒&#xff1a; 您的应用在运行时&#xff0c;未见向用户告知权限申请的目的&#xff0c;向用户索取(相机存)等权限&#xff0c;不符合华为应用市场审核标准。 <uni-popup ref"perpopup" type"center" :mask-clickfalse><view class&qu…

【解锁未来】探索Web3的无限可能-02

文章目录 什么是Web3 &#xff1f;Web3对公司的意义&#xff1f; 什么是Web3 &#xff1f; 简单地说&#xff0c;Web3 是加密货币的延伸&#xff0c;它以新的方式使用区块链来达到新的目的。区块链可以存储钱包中代币的数量、自我执行合同的条款或去中心化应用程序&#xff08;…

《申论技巧》

一、做题过程 做题过程&#xff1a; 四个要素分析题干 一对多&#xff1a;考虑材料之间的灵活运用&#xff1b;问题对策&#xff1b;并列&#xff1b;主材料与辅材料 多个题目对应一个一篇材料&#xff1b;答案各有侧重&#xff0c;不重合 主体内容 二、读材料 2.1 粗读…

2023辽宁省数学建模A题铁路车站的安全标线完整原创论文详细讲解(含matlab代码)

大家好呀&#xff0c;从发布赛题一直到现在&#xff0c;总算完成了辽宁省数学建模A题完整的成品论文。 本论文可以保证原创&#xff0c;保证高质量。绝不是随便引用一大堆模型和代码复制粘贴进来完全没有应用糊弄人的垃圾半成品论文。 B预计下午两点前更新完毕&#xff0c;A全…

手写数字识别--神经网络实验

实验源码自取&#xff1a; 我自己搞的代码&#xff0c;预测精度才94% 神经网络实验报告源码.zip - 蓝奏云 老师给的实验源码答案和资料&#xff0c;预测精度高达99% 深度学习实验报告.zip - 蓝奏云 上深度学习的课程&#xff0c;老师布置了一个经典的实验报告&#xff0c;我做…

“凸函数”是什么?

凸函数&#xff08;英文&#xff1a;Convex function&#xff09;是指函数图形上&#xff0c;任意两点连成的线段&#xff0c;皆位于图形的上方&#xff0c;如单变数的二次函数和指数函数。二阶可导的一元函数为凸&#xff0c;当且仅当其定义域为凸集&#xff0c;且函数的二阶导…

Nginx搭配负载均衡和动静分离:构建高性能Web应用的完美组合

目录 前言 一、Nginx简介 1.Nginx是什么 2.Nginx的特点 3.Nginx在哪使用 4.如何使用Nginx 5.Nginx的优缺点 6.Nginx的应用场景 二、负载均衡和动静分离 1.负载均衡 2.动静分离 三、Nginx搭载负载均衡并提供前后端分离后台接口数据 1.Nginx安装 2.tomcat负载均衡 …

安装anaconda时控制台conda-version报错

今天根据站内的一篇博客教程博客在此安装anaconda时&#xff0c;检查conda版本时报错如下&#xff1a; >>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<< Traceback (most recent call last): File “D:\An…

Java自学第1课:安装JDK+Eclipse

1 引言 在学习前&#xff0c;我想说一句&#xff0c;那就是为什么要学习Java。 每个人的出发点都不同&#xff0c;对于做信息化的工程技术人员来说&#xff0c;java不懂&#xff0c;就没法干项目。 尽管有c和matlab等基础&#xff0c;但java看起来与这些语言都不太一样。 做…

自定义类型联合体

目录 联合体联合体类型的声明联合体的特点相同成员的结构体和联合体对比联合体大小的计算联合体的应用联合的一个练习 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f…

《向量数据库指南》——用了解向量数据库Milvus Cloud搭建高效推荐系统

了解向量数据库 ANN 搜索是关系型数据库无法提供的功能。关系型数据库只能用于处理具有预定义结构、可直接比较值的表格型数据。因此,关系数据库索引也是基于这一点来比较数据。但是 Embedding 向量无法通过这种方式直接相互比较。因为我们不知道向量中的每个值代表什么意思,…

Transformer:开源机器学习项目,上千种预训练模型 | 开源日报 No.66

huggingface/transformers Stars: 113.5k License: Apache-2.0 这个项目是一个名为 Transformers 的开源机器学习项目&#xff0c;它提供了数千种预训练模型&#xff0c;用于在文本、视觉和音频等不同领域执行任务。该项目主要功能包括&#xff1a; 文本处理&#xff1a;支持…

(附源码)基于SSM 车险事故自助理赔小程序-计算机毕设 84607

车险事故自助理赔小程序 摘要 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;车险事故自助理赔小程序被用户普遍…

C++二分查找算法的应用:最小好进制

本文涉及的基础知识点 二分查找 题目 以字符串的形式给出 n , 以字符串的形式返回 n 的最小 好进制 。 如果 n 的 k(k>2) 进制数的所有数位全为1&#xff0c;则称 k(k>2) 是 n 的一个 好进制 。 示例 1&#xff1a; 输入&#xff1a;n “13” 输出&#xff1a;“3” …

Linux 实现文件后半部分的复制

继上次实现文件从后往前数2k的数据进行复制&#xff0c;此次要求是文件的一半且是后半部分。 即复制源文件sour_file的后半部分到dest_file 除了数据上从后2K变化到后一半之外&#xff0c;其他的几乎没有什么变化。 这道题的关键点就在于后一半怎么求&#xff0c;在经历了用 …

玩转AIGC:如何选择最佳的Prompt提示词?

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

LangChain+LLM实战---如何训练大模型(LLM)

英文原文&#xff1a;How to train your own Large Language Models 概要介绍 大型语言模型&#xff0c;如OpenAI的GPT-4或Google的PaLM&#xff0c;已经席卷了人工智能领域。然而&#xff0c;大多数公司目前没有能力训练这些模型&#xff0c;并且完全依赖于只有少数几家大型…

0基础学习PyFlink——时间滑动窗口(Sliding Time Windows)

在《0基础学习PyFlink——时间滚动窗口(Tumbling Time Windows)》我们介绍了不会有重复数据的时间滚动窗口。本节我们将介绍存在重复计算数据的时间滑动窗口。 关于滑动窗口&#xff0c;可以先看下《0基础学习PyFlink——个数滑动窗口&#xff08;Sliding Count Windows&#x…