当前位置: 首页 > news >正文

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石⑤

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石⑤

  • 第十九章 驱动程序基石⑤
    • 19.9 mmap
      • 19.9.1 内存映射现象与数据结构
      • 19.9.2 ARM架构内存映射简介
        • 19.9.2.1 一级页表映射过程
        • 19.9.2.2 二级页表映射过程
      • 19.9.3 怎么给APP新建一块内存映射
        • 19.9.3.1 mmap调用过程
        • 19.9.3.2 cache和 buffer
        • 19.9.3.3 驱动程序要做的事
      • 19.9.4 编程
      • 19.9.4.1
        • 19.9.4.2 驱动编程
        • 19.9.4.3 上机测试

第十九章 驱动程序基石⑤

在这里插入图片描述

19.9 mmap

应用程序和驱动程序之间传递数据时,可以通过 read、write函数进行。这涉及在用户态 buffer和内核态 buffer之间传数据,如下图所示: 在这里插入图片描述

应用程序不能直接读写驱动程序中的 buffer,需要在用户态 buffer和内核态 buffer之间进行一次数据拷贝。这种方式在数据量比较小时没什么问题;但是数据量比较大时效率就太低了。比如更新 LCD显示时,如果每次都让 APP传递一帧数据给内核,假设 LCD采用 102460032bpp的格式,一帧数据就有102460032/8=2.3MB左右,这无法忍受。
改进的方法就是让程序可以直接读写驱动程序中的 buffer,这可以通过 mmap实现(memory map),把内核的 buffer映射到用户态,让 APP在用户态直接读写。

19.9.1 内存映射现象与数据结构

假设有这样的程序,名为 test.c: #include <stdio.h>

#include <unistd.h> 
#include <stdlib.h> int a; 
int main(int argc, char **argv) 
{ if (argc != 2) { printf("Usage: %s <number>\n", argv[0]); return -1; } a = strtol(argv[1], NULL, 0); printf("a's address = 0x%lx, a's value = %d\n", &a, a);  while (1) { sleep(10); } return 0; 
} 

在 PC上如下编译(必须静态编译):
gcc -o test test.c -staitc
分别执行 test程序 2次,最后执行 ps,可以看到这 2个程序同时存在,这 2个程序里 a变量的地址相同,但是值不同。如下图:
在这里插入图片描述

观察到这些现象:
① 2个程序同时运行,它们的变量 a的地址都是一样的:0x6bc3a0;
② 2个程序同时运行,它们的变量 a的值是不一样的,一个是 12,另一个是 123。
疑问来了:
① 这 2个程序同时在内存中运行,它们的值不一样,所以变量 a的地址肯定不同;

② 但是打印出来的变量 a的地址却是一样的。
怎么回事?
这里要引入虚拟地址的概念:CPU发出的地址是虚拟地址,它经过 MMU(Memory Manage Unit,内存管理单元)映射到物理地址上,对于不同进程的同一个虚拟地址,MMU会把它们映射到不同的物理地址。如下图: 在这里插入图片描述

当前运行的是 app1时,MMU会把 CPU发出的虚拟地址 addr映射为物理地址 paddr1,用 paddr1去访问内存。
当前运行的是 app2时,MMU会把 CPU发出的虚拟地址 addr映射为物理地址 paddr2,用 paddr2去访问内存。
MMU负责把虚拟地址映射为物理地址,虚拟地址映射到哪个物理地址去?
可以执行 ps命令查看进程 ID,然后执行“cat /proc/325/maps”得到映射关系。
每一个 APP在内核里都有一个 tast_struct,这个结构体中保存有内存信息:mm_struct。而虚拟地址、物理地址的映射关系保存在页目录表中,如下图所示:
在这里插入图片描述
解析如下:
① 每个 APP在内核中都有一个 task_struct结构体,它用来描述一个进程;
② 每个 APP都要占据内存,在 task_struct中用 mm_struct来管理进程占用的内存;
内存有虚拟地址、物理地址,mm_struct中用 mmap来描述虚拟地址,用 pgd来描述对应的物理地址。
注意:pgd,Page Global Directory,页目录。
③ 每个 APP都有一系列的 VMA:virtual memory
比如 APP含有代码段、数据段、BSS段、栈等等,还有共享库。这些单元会保存在内存里,它们的地址空间不同,权限不同(代码段是只读的可运行的、数据段可读可写),内核用一系列的vm_area_struct来描述它们。
vm_area_struct中的 vm_start、vm_end是虚拟地址。
④ vm_area_struct中虚拟地址如何映射到物理地址去?
每一个 APP的虚拟地址可能相同,物理地址不相同,这些对应关系保存在 pgd中。

19.9.2 ARM架构内存映射简介

ARM架构支持一级页表映射,也就是说 MMU根据 CPU发来的虚拟地址可以找到第 1个页表,从第 1个页表里就可以知道这个虚拟地址对应的物理地址。一级页表里地址映射的最小单位是 1M。
ARM架构还支持二级页表映射,也就是说 MMU根据 CPU发来的虚拟地址先找到第 1个页表,从第 1个页表里就可以知道第 2级页表在哪里;再取出第 2级页表,从第 2个页表里才能确定这个虚拟地址对应的物理地址。二级页表地址映射的最小单位有 4K、1K,Linux使用 4K。
一级页表项里的内容,决定了它是指向一块物理内存,还是指问二级页表,如下图:
在这里插入图片描述

19.9.2.1 一级页表映射过程

一线页表中每一个表项用来设置 1M的空间,对于 32位的系统,虚拟地址空间有 4G,4G/1M=4096。所以一级页表要映射整个 4G空间的话,需要 4096个页表项。
第 0个页表项用来表示虚拟地址第 0个 1M(虚拟地址为 0~0x1FFFFF)对应哪一块物理内存,并且有一些权限设置;
第 1个页表项用来表示虚拟地址第 1个 1M(虚拟地址为 0x100000~0x2FFFFF)对应哪一块物理内存,并且有一些权限设置;
依次类推。
使用一级页表时,先在内存里设置好各个页表项,然后把页表基地址告诉 MMU,就可以启动 MMU了。
以下图为例介绍地址映射过程:
① CPU发出虚拟地址 vaddr,假设为 0x12345678
② MMU根据 vaddr[31:20]找到一级页表项:
虚拟地址 0x12345678是虚拟地址空间里第 0x123个 1M,所以找到页表里第 0x123项,根据此项内容知道它是一个段页表项。
段内偏移是 0x45678。
③ 从这个表项里取出物理基地址:Section Base Address,假设是 0x81000000
④ 物理基地址加上段内偏移得到:0x81045678
所以 CPU要访问虚拟地址 0x12345678时,实际上访问的是 0x81045678的物理地址
在这里插入图片描述

19.9.2.2 二级页表映射过程

首先设置好一级页表、二级页表,并且把一级页表的首地址告诉 MMU。

以下图为例介绍地址映射过程:
① CPU发出虚拟地址 vaddr,假设为 0x12345678
② MMU根据 vaddr[31:20]找到一级页表项:
虚拟地址 0x12345678是虚拟地址空间里第 0x123个 1M,所以找到页表里第 0x123项。根据此项内容知道它是一个二级页表项。
③ 从这个表项里取出地址,假设是 address,这表示的是二级页表项的物理地址;
④ vaddr[19:12]表示的是二级页表项中的索引 index即 0x45,在二级页表项中找到第 0x45项;
⑤ 二级页表项格式如下:
在这里插入图片描述

里面含有这 4K或 1K物理空间的基地址 page base addr,假设是 0x81889000:
它跟 vaddr[11:0]组合得到物理地址:0x81889000 + 0x678 = 0x81889678。
所以 CPU要访问虚拟地址 0x12345678时,实际上访问的是 0x81889678的物理地址
在这里插入图片描述

19.9.3 怎么给APP新建一块内存映射

19.9.3.1 mmap调用过程

从上面内存映射的过程可以知道,要给 APP新开劈一块虚拟内存,并且让它指向某块内核 buffer,我们要做这些事:
① 得到一个 vm_area_struct,它表示 APP的一块虚拟内存空间;
很幸运,APP调用 mmap系统函数时,内核就帮我们构造了一个 vm_area_stuct结构体。里面含有虚拟地址的地址范围、权限。
② 确定物理地址:
你想映射某个内核 buffer,你需要得到它的物理地址,这得由你提供。 ③ 给 vm_area_struct和物理地址建立映射关系:
也很幸运,内核提供有相关函数。
APP里调用 mmap时,导致的内核相关函数调用过程如下:
在这里插入图片描述

19.9.3.2 cache和 buffer

本小节参考:
ARM的 cache和写缓冲器(write buffer)
https://blog.csdn.net/gameit/article/details/13169445
使用 mmap时,需要有 cache、buffer的知识。下图是 CPU和内存之间的关系,有 cache、buffer(写缓冲器)。Cache是一块高速内存;写缓冲器相当于一个 FIFO,可以把多个写操作集合起来一次写入内存。
在这里插入图片描述

程序运行时有“局部性原理”,这又分为时间局部性、空间局部性。
① 时间局部性:
在某个时间点访问了存储器的特定位置,很可能在一小段时间里,会反复地访问这个位置。
② 空间局部性:
访问了存储器的特定位置,很可能在不久的将来访问它附近的位置。
而 CPU的速度非常快,内存的速度相对来说很慢。CPU要读写比较慢的内存时,怎样可以加快速度?根据“局部性原理”,可以引入 cache。

① 读取内存 addr处的数据时:
先看看 cache中有没有 addr的数据,如果有就直接从 cache里返回数据:这被称为 cache命中。
如果 cache中没有 addr的数据,则从内存里把数据读入,注意:它不是仅仅读入一个数据,而是读入一行数据(cache line)。
而 CPU很可能会再次用到这个 addr的数据,或是会用到它附近的数据,这时就可以快速地从 cache中获得数据。
② 写数据:
CPU要写数据时,可以直接写内存,这很慢;也可以先把数据写入 cache,这很快。
但是 cache中的数据终究是要写入内存的啊,这有 2种写策略:
a. 写通(write through):
数据要同时写入 cache和内存,所以 cache和内存中的数据保持一致,但是它的效率很低。能改进吗?可以!使用“写缓冲器”:cache大哥,你把数据给我就可以了,我来慢慢写,保证帮你写完。
有些写缓冲器有“写合并”的功能,比如 CPU执行了 4条写指令:写第 0、1、2、3个字节,每次写 1字节;写缓冲器会把这 4个写操作合并成一个写操作:写 word。对于内存来说,这没什么差别,但是对于硬件寄存器,这就有可能导致问题。
所以对于寄存器操作,不会启动 buffer功能;对于内存操作,比如 LCD的显存,可以启用 buffer功能。
b. 写回(write back):
新数据只是写入 cache,不会立刻写入内存,cache和内存中的数据并不一致。
新数据写入 cache时,这一行 cache被标为“脏”(dirty);当 cache不够用时,才需要把脏的数据写入内存。
使用写回功能,可以大幅提高效率。但是要注意 cache和内存中的数据很可能不一致。这在很多时间要小心处理:比如 CPU产生了新数据,DMA把数据从内存搬到网卡,这时候就要 CPU执行命令先把新数据从cache刷到内存。反过来也是一样的,DMA从网卡得过了新数据存在内存里,CPU读数据之前先把 cache中的数据丢弃。
是否使用 cache、是否使用 buffer,就有 4种组合(Linux内核文件 arch\arm\include\asm\pgtable-2level.h):
在这里插入图片描述

上面 4种组合对应下表中的各项,一一对应(下表来自 s3c2410芯片手册,高架构的 cache、buffer更复杂,但是这些基础知识没变):
在这里插入图片描述
在这里插入图片描述

第 1种是不使用 cache也不使用 buffer,读写时都直达硬件,这适合寄存器的读写。
第 2种是不使用 cache但是使用 buffer,写数据时会用 buffer进行优化,可能会有“写合并”,这适合显存的操作。因为对显存很少有读操作,基本都是写操作,而写操作即使被“合并”也没有关系。
第 3种是使用 cache不使用 buffer,就是“write through”,适用于只读设备:在读数据时用 cache加速,基本不需要写。
第 4种是既使用 cache又使用 buffer,适合一般的内存读写。

19.9.3.3 驱动程序要做的事

驱动程序要做的事情有 3点:
① 确定物理地址
② 确定属性:是否使用 cache、buffer ③ 建立映射关系
参考 Linux源文件,示例代码如下:
在这里插入图片描述
还有一个更简单的函数:
在这里插入图片描述

19.9.4 编程

使用 GIT命令载后,本节源码位于这个目录下:

01_all_series_quickstart\ 
05_嵌入式 Linux驱动开发基础知识\source\ 
07_mmap 

目的:我们在驱动程序中申请一个 8K的 buffer,让 APP通过 mmap能直接访问。

19.9.4.1

APP编程
APP怎么写?open驱动、buf=mmap(……)映射内存,直接读写 buf就可以了,代码如下:

22      /* 1. 打开文件 */ 
23      fd = open("/dev/hello", O_RDWR); 
24      if (fd == -1) 
25      { 
26              printf("can not open file /dev/hello\n"); 
27              return -1; 
28      } 
29 
30      /* 2. mmap 
31       * MAP_SHARED  : 多个 APP都调用 mmap映射同一块内存时, 对内存的修改大家都可以看到。 32       *               就是说多个 APP、驱动程序实际上访问的都是同一块内存 
33       * MAP_PRIVATE : 创建一个 copy on write的私有映射。 
34       *               当 APP对该内存进行修改时,其他程序是看不到这些修改的。 
35       *               就是当 APP写内存时, 内核会先创建一个拷贝给这个 APP, 
36       *               这个拷贝是这个 APP私有的, 其他 APP、驱动无法访问。 
37       */ 
38      buf =  mmap(NULL, 1024*8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
39      if (buf == MAP_FAILED) 
40      { 
41              printf("can not mmap file /dev/hello\n"); 
42              return -1; 
43      } 

最难理解的是 mmap函数 MAP_SHARED、MAP_PRIVATE参数。使用 MAP_PRIVATE映射时,在没有发生写操作时,APP、驱动访问的都是同一块内存;当 APP发起写操作时,就会触发“copy on write”,即内核会先创建该内存块的拷贝,APP的写操作在这个新内存块上进行,这个新内存块是 APP私有的,别的 APP、驱动看不到。
仅用 MAP_SHARED参数时,多个 APP、驱动读、写时,操作的都是同一个内存块,“共享”。
MAP_PRIVATE映射是很有用的,Linux中多个 APP都会使用同一个动态库,在没有写操作之前大家都使用内存中唯一一份代码。当 APP1发起写操作时,内核会为它复制一份代码,再执行写操作,APP1就有了专享的、私有的动态库,在里面做的修改只会影响到 APP1。其他程序仍然共享原先的、未修改的代码。
有了这些知识后,下面的代码就容易理解了,请看代码中的注释:

44 
45      printf("mmap address = 0x%x\n", buf); 
46      printf("buf origin data = %s\n", buf); /* old */ 
47 
48      /* 3. write */ 
49      strcpy(buf, "new"); 
50 
51      /* 4. read & compare */ 
52      /* 对于 MAP_SHARED映射:  str = "new" 
53       * 对于 MAP_PRIVATE映射: str = "old" 
54       */ 
55      read(fd, str, 1024); 
56      if (strcmp(buf, str) == 0) 
57      { 
58              /* 对于 MAP_SHARED映射,APP写的数据驱动可见 
59               * APP和驱动访问的是同一个内存块 
60               */ 
61              printf("compare ok!\n"); 
62      } 
63      else 
64      { 
65              /* 对于 MAP_PRIVATE映射,APP写数据时, 是写入另一个内存块(是原内存块的"拷贝") 66               */ 
67              printf("compare err!\n"); 
68              printf("str = %s!\n", str);  /* old */ 
69              printf("buf = %s!\n", buf);  /* new */ 
70      } 

执行测试程序后,查看到它的进程号 PID,执行这样的命令查看这个程序的内存使用情况: cat /proc/PIC/maps

19.9.4.2 驱动编程

驱动程序要做什么?
① 分配一块 8K的内存
使用哪一个函数分配内存?
在这里插入图片描述

我们应该使用 kmalloc或 kzalloc,这样得到的内存物理地址是连续的,在 mmap时后 APP才可以使用同一个基地址去访问这块内存。(如果物理地址不连续,就要执行多次 mmap了)。
② 提供 mmap函数
关键在于 mmap函数,代码如下:
在这里插入图片描述
要注意的是,remap_pfn_range中,pfn的意思是“Page Frame Number”。在 Linux中,整个物理地址空间可以分为第 0页、第 1页、第 2页,诸如此类,这就是 pfn。假设每页大小是 4K,那么给定物理地址phy,它的 pfn = phy / 4096 = phy >> 12。内核的 page一般是 4K,但是也可以配置内核修改 page的大小。所以为了通用,pfn = phy >> PAGE_SHIFT。
APP调用 mmap后,会导致驱动程序的 mmap函数被调用,最终 APP的虚拟地址和驱动程序中的物理地址就建立了映射关系。APP可以直接访问驱动程序的 buffer。

19.9.4.3 上机测试

在 Ubuntu中编译好驱动、测试程序,放到开发板。 在开发板上安装驱动、执行测试程序。

相关文章:

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石⑤

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石⑤ 第十九章 驱动程序基石⑤19.9 mmap19.9.1 内存映射现象与数据结构19.9.2 ARM架构内存映射简介19.9.2.1 一级页表映射过程19.9.2.2 二级页表映射过程 19.9.3 怎么给APP新建一块内存映射19.9.3.1 mmap调用过程19.9.3.2 cach…...

数据分析技能点-独立性检验拟合优度检验

在这个数据驱动的时代,数据分析已经成为了一个不可或缺的工具,无论是在商业决策、医疗研究还是日常生活中。然而数据分析并不仅仅是一堆数字和图表;它是一个需要严谨的科学方法和逻辑推理的过程。 本文将重点介绍两种广泛应用于数据分析的统计检验方法:独立性检验和拟合优…...

了解汽车ecu组成

常用ecu框架组成&#xff1a; BCM(body control module)-车身控制模块: 如英飞凌tc265芯片&#xff1a; 车身控制单元&#xff08;BCM&#xff09;适合应用于12V和24V两种电压工作环境&#xff0c;可用于轿车、大客车和商用车的车身控制。输入模块通过采集电路采集各路开关量和…...

用AI原生向量数据库Milvus Cloud 搭建一个 AI 聊天机器人

搭建聊天机器人 一切准备就绪后,就可以搭建聊天机器人了。 文档存储 机器人需要存储文档块以及使用 Towhee 提取出的文档块向量。在这个步骤中,我们需要用到 Milvus。 安装轻量版 Milvus Lite,使用以下命令运行 Milvus 服务器: (chatbot_venv) [egoebelbecker@ares milvus_…...

【OpenCV-Torch-dlib-ubuntu】Vm虚拟机linux环境摄像头调用方法与dilb模型探究

前言 随着金秋时节的来临&#xff0c;国庆和中秋的双重喜庆汇聚成一片温暖的节日氛围。在这个美好的时刻&#xff0c;我们有幸共同迎来一次长达8天的假期&#xff0c;为心灵充电&#xff0c;为身体放松&#xff0c;为未来充实自己。今年的国庆不仅仅是家国团聚的时刻&#xff…...

(二)详解观察者模式

一.使用场景 当我们需要一个类&#xff0c;在他的内部元素发生变化的时候可以主动通知其他类的时候&#xff0c;同时要保持良好的可拓展性&#xff0c;可以采用观察者模式。 二.核心 观察者模式出版者订阅者 我们拥有一个主题对象&#xff0c;和一些其他对象&#xff0c;包…...

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石④

嵌入式Linux应用开发-基础知识-第十九章驱动程序基石④ 第十九章 驱动程序基石④19.7 工作队列19.7.1 内核函数19.7.1.1 定义 work19.7.1.2 使用 work&#xff1a;schedule_work19.7.1.3 其他函数 19.7.2 编程、上机19.7.3 内部机制19.7.3.1 Linux 2.x的工作队列创建过程19.7.3…...

2023 彩虹全新 SUP 模板,卡卡云模板修复版

2023 彩虹全新 SUP 模板&#xff0c;卡卡云模板&#xff0c;首页美化&#xff0c;登陆页美化&#xff0c;修复了 PC 端购物车页面显示不正常的问题。 使用教程 将这俩个数据库文件导入数据库&#xff1b; 其他的直接导入网站根目录覆盖就好&#xff1b; 若首页显示不正常&a…...

【AI视野·今日NLP 自然语言处理论文速览 第四十一期】Tue, 26 Sep 2023

AI视野今日CS.NLP 自然语言处理论文速览 Tue, 26 Sep 2023 Totally 75 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Physics of Language Models: Part 3.1, Knowledge Storage and Extraction Authors Zeyuan Allen Zhu, Yuanz…...

【iptables 实战】05 iptables设置网络转发实验

一、网络架构 实验效果&#xff0c;通过机器B的转发功能&#xff0c;将机器A的报文转发到机器C 本实验准备三台机器分别配置如下网络 机器A ip:192.168.56.104 机器C ip:10.1.0.10 机器B 两张网卡&#xff0c;分别的ip是192.168.56.106和10.1.0.11 如图所示 如下图所示 二、…...

pygame - 贪吃蛇小游戏

蛇每吃掉一个身体块&#xff0c;蛇身就增加一个长度。为了统一计算&#xff0c;界面的尺寸和游戏元素的位置都是身体块长度的倍数 1. 上下左右方向键&#xff08;或者ASDW键&#xff09;控制蛇的移动方向 2. 空格键暂停和继续蛇的身体图片文件&#xff0c;复制到项目的asset\im…...

基于 QT 实现 Task Timer,高效利用时间

一、开发环境 Ubuntu 20.04 QT6.0 二、新建 Qt Wigets Application 这里的基类选择 Wigets&#xff0c; pro 配置文件添加 sql 模块&#xff0c;需要用到 sqlite&#xff0c; QT sql 三、添加数据库连接头文件 // connection.h #ifndef CONNECTION_H #define CONNECTION_…...

图像处理与计算机视觉--第五章-图像分割-霍夫变换

文章目录 1.霍夫变换(Hough Transform)原理介绍2.霍夫变换(Hough Transform)算法流程3.霍夫变换(Hough Transform)算法代码4.霍夫变换(Hough Transform)算法效果 1.霍夫变换(Hough Transform)原理介绍 Hough Transform是一种常用的计算机视觉图形检验方法&#xff0c;霍夫变换一…...

linux下文件操作命令

title: linux下文件操作命令 createTime: 2020-10-29 18:05:52 updateTime: 2020-10-29 18:05:52 categories: linux tags: Linux下文件操作命令 tar命令 使用tar命令一般打包分为两种*.tar ,*.tar.gz 相信大家也使用过tar -zcvf test.tar test/tar -zcvf test.tar.gz test/…...

Golang语法、技巧和窍门

Golang简介 命令式语言静态类型语法标记类似于C&#xff08;但括号较少且没有分号&#xff09;&#xff0c;结构类似Oberon-2编译为本机代码&#xff08;没有JVM&#xff09;没有类&#xff0c;但有带有方法的结构接口没有实现继承。不过有type嵌入。函数是一等公民函数可以返…...

Grander因果检验(格兰杰)原理+操作+解释

笔记来源&#xff1a; 1.【传送门】 2.【传送门】 前沿原理介绍 Grander因果检验是一种分析时间序列数据因果关系的方法。 基本思想在于&#xff0c;在控制Y的滞后项 (过去值) 的情况下&#xff0c;如果X的滞后项仍然有助于解释Y的当期值的变动&#xff0c;则认为 X对 Y产生…...

Python-Flask:编写自动化连接demo脚本:v1.0.0

主函数&#xff1a; # _*_ Coding : UTF-8 _*_ # Time : 13:14 # Author : YYZ # File : Flask # Project : Python_Project_爬虫 import jsonfrom flask import Flask,request,jsonify import sshapi Flask(__name__)# methods: 指定请求方式 接口解析参数host host_info[…...

kafka客户端应用参数详解

一、基本客户端收发消息 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka_2.13</artifactId><version>3.4.0</version></depend…...

Apache Doris 行列转换可以这样玩

行列转换在做报表分析时还是经常会遇到的&#xff0c;今天就说一下如何实现行列转换吧。 行列转换就是如下图所示两种展示形式的互相转换 1. 行转列 我们来看一个简单的例子&#xff0c;我们要把下面这个表的数据&#xff0c;转换成图二的样式 image-20230914151818953.png …...

【Qt图形视图框架】自定义QGraphicsItem和QGraphicsView,实现鼠标(移动、缩放)及键盘事件、右键事件

自定义QGraphicsItem和QGraphicsView 说明示例myitem.hmyitem.cppmyview.hmyview.cpp调用main.cpp 效果 说明 在使用Qt的图形视图框架实现功能时&#xff0c;一般会在其基础上进行自定义功能实现。 如&#xff1a;滚轮对场景的缩放&#xff0c;鼠标拖动场景中的项&#xff0c;…...

C语言结构体指针学习

结构体变量存放内存中&#xff0c;也有起始地址&#xff0c;定义一个变量来存放这个地址&#xff0c;那这个变量就是结构体指针&#xff1b; typedef struct mydata{int a1;int a2;int a3; }mydata;void CJgtzzView::OnDraw(CDC* pDC) {CJgtzzDoc* pDoc GetDocument();ASSERT…...

华为云云耀云服务器L实例评测|部署在线轻量级备忘录 memos

华为云云耀云服务器L实例评测&#xff5c;部署在线轻量级备忘录 memos 一、云耀云服务器L实例介绍1.1 云服务器介绍1.2 产品优势1.3 应用场景1.4 支持镜像 二、云耀云服务器L实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 memos3.1 memos介绍3.2 Docker 环境搭建…...

详解Avast Driver Updater:电脑驱动更新工具的利器还是多余的软件?

亲爱的读者朋友们&#xff0c;你是不是经常为电脑的驱动问题而烦恼&#xff1f;如果是的话&#xff0c;你可能会对这款软件——Avast Driver Updater 电脑驱动更新工具感兴趣。但在你决定尝试之前&#xff0c;不妨先和我一起深入探讨一下它的优点、缺点以及它适用的使用场景。 …...

大数据Flink(九十五):DML:Window TopN

文章目录 DML:Window TopN DML:Window TopN Window TopN 定义(支持 Streaming):Window TopN 是一种特殊的 TopN,它的返回结果是每一个窗口内的 N 个最小值或者最大值。 应用场景...

使用OKHttpClient访问网络

使用OKHttpClient前要引入依赖&#xff1a; 在build.gradle(Moduel :app)中添加 implementation com.squareup.okhttp3:okhttp:3.14.1 implementation com.squareup.okhttp3:logging-interceptor:3.14.1 implementation com.squareup.okio:okio:1.6.0 1. GET&#xff08;同步…...

maui 开发AMD CPU踩的坑。

刚换的 amd R7735HS 笔记本&#xff0c;8核16线程&#xff0c;32GB内存。性能得实强悍 。 当需要发布iOS版本时发现&#xff0c;我没有macos &#xff0c;那就安装个vmware 吧。看了一下Apple 要求以后的发布的APP需要以xcode14.3或以后版本开发的版本&#xff0c;但xcode14.3…...

宝塔反代openai官方API接口详细教程,502 Bad Gateway问题解决

一、前言 宝塔反代openai官方API接口详细教程&#xff0c;实现国内使用ChatGPT502 Bad Gateway问题解决&#xff0c; 此方法最简单快捷&#xff0c;没有复杂步骤&#xff0c;不容易出错&#xff0c;即最简单&#xff0c;零代码、零部署的方法。 二、实现前提 一台海外VPS服务…...

【leetocde】128. 最长连续序列

给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1a;4 …...

【Vue3】动态 class 类

如果你想在 Vue.js 中动态设置元素的 class 类名&#xff0c;你可以使用以下两种主要方式&#xff1a; 绑定一个动态的 class 对象&#xff1a;你可以使用 v-bind 或简写的 : 来绑定一个包含类名的对象&#xff0c;其中类名的键是类名字符串&#xff0c;值是一个布尔值或计算属…...

【Redis】redis基本数据类型详解(String、List、Hash、Set、ZSet)

目录 RedisString(字符串)List(列表)Hash(字典)Set(集合)ZSet(有序集合) Redis Redis有5种基本的数据结构&#xff0c;分别为&#xff1a;string&#xff08;字符串&#xff09;、list&#xff08;列表&#xff09;、set&#xff08;集合&#xff09;、hash&#xff08;哈希&a…...

新手学做网站 下载/网站seo优化运营

Openlayers中加载大数据量的vectors时 其渲染效果就会很差&#xff0c;解决这一问题的有效方法就是对这些vectors进行Cluster 在Openlayers的官方Examples中有相关的示例&#xff1a;http://openlayers.org/dev/examples/strategy-cluster-threshold.html 我对这个示例做了下修…...

打造wordpress日志管理系统/今日国际军事新闻最新消息

描述&#xff1a; 根据后端返回来的数据&#xff0c;判断列表页某一条数据是否置顶&#xff0c;如果置顶&#xff0c;则显示icon小图标&#xff0c;点击置顶消息跳到详情页&#xff0c;在详情页里显示icon和时间。这里对详情页的icon和时间显示位置做讲解&#xff0c;因为我使…...

男男床做第一次视频网站/辽源seo

《五年级信息技术期末测试题(答案)》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《五年级信息技术期末测试题(答案)(2页珍藏版)》请在人人文库网上搜索。1、五年级信息技术期末测试题一、填空题(每空 4 分&#xff0c;共 20 分)1、创建新网页的快捷键是 CtrlN2、如果…...

南充二手房/seo推广外包

学习总结 当我们在两个分支中对同一个文件的同一个部分进行了不同的修改&#xff0c;Git就没有办法顺利的合并他们&#xff0c;会在合并的时候产生合并冲突。比如我们在issue102分支和master分支下对issue102.md文件进行了修改&#xff0c;当我们将issue102分支融合到主分支上…...

亚马逊跨境电商好做吗/杭州seo俱乐部

Java 理论与实践: 正确使用 Volatile 变量Java™ 语言包含两种内在的同步机制&#xff1a;同步块(或方法)和 volatile 变量。这两种机制的提出都是为了实现代码线程的安全性。其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低)&#xff0c;而且其使用也更容易出错。…...

网站策划专员/岳阳网站建设推广

1. 顶点缓存和索引缓存(vertex/index buffer) 一个顶点缓存是一个包含顶点数据的连续内存空间&#xff1b;一个索引缓存是包含索引数据的连续内存空间。顶点缓存和索引缓存可以被放置到显存(video memory)中。进行绘制时&#xff0c;使用显存中的数据将获得比使用系统内存中的数…...