Cortex-M 内核的 OS 特性
目录
- 一、通用堆栈知识
- 二、双堆栈用法
- 三、PendSV 中断介绍和用法
- 四、SVC 软中断介绍和用法
- 五、特权级和非特权级使用方法
一、通用堆栈知识
在前面讲解 STM32 启动文件的时候就已经提到过,有关堆栈大小的设置是在启动文件中设置的:
Heap
主要用于 Malloc、Free,动态内存申请和释放。
Stack
也非常重要,程序编译后所包含的大量 PUSH
和 POP
指令操作,系统根据 SP
(堆栈指针) 寄存器访问当前对应栈内存,通过栈保存临时数据。大部分的栈都是向下生长的(也有向上生长的)。M 内核的栈是从高地址向低地址生长的,下面通过一个例子演示一下:
__IO uint32_t a = 0x11111111;
__IO uint32_t b = 0x22222222;
__IO uint32_t c = 0x33333333;
__IO uint32_t d = 0x44444444;
__IO uint32_t e = 0x55555555;
__IO uint32_t f = 0x66666666;
__IO uint32_t g = 0x77777777;
__IO uint32_t h = 0x88888888;
然后进入调试状态,可以看到现在 SP 指针是 0x20000618
。
可当我们实际点击单步调试的时候,SP 指针现在又变成了 0x200005F8
,0x20000618
- 0x200005F8
等于十进制的 32。也就是说少了 32 个字节。这里是因为程序一来就把这 8 个数据,共 32 个字节的内存分配好了。
当你把 __IO
去掉后,就不会出现以上的情况。__IO
其实就是 volatile
,所以在这里就是告诉编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。
下面继续执行,可以看到,数据是从高地址向低地址存储的。
当然,内存空间只是连续字节数据的抽象,本身并不区分堆和栈的概念,它做的只是存储和读写信息。因此,如何定义堆栈、初始化建立堆栈环境,在嵌入式软件运行前便显得尤为重要。这涉及到处理器提供的堆栈机制、操作系统内存管理和进程切换等方方面面。
二、双堆栈用法
下图取自权威指南:
在搭载实时操作系统内核的嵌入式软件中,栈往往分为两大类,除了满足系统基本的主栈(main stack)外,往往还需要进程/线程栈(process stack)。这两部分内存空间是独立存在的:
- 主栈位于系统的栈区(stack)
- 而线程堆栈往往定义在堆区(heap)或静态区(static)
无论是静态还是动态创建,线程栈都不会占用栈区的主栈空间。即线程栈并不在栈区,可能在数据区或堆区,再次说明主栈和线程栈内存空间独立。
理解这一点,是理解 MCU 堆栈的关键前提。
所谓双堆栈,本质上都是 R13
,不过在不同的运行状态下使用的栈是不同的,并且在同一时刻,只有其中的一个栈可用。在系统复位后、进入线程环境前,默认使用主堆栈,中断服务程序(ISR)中也是使用主堆栈。RTOS 各线程中的应用代码,则使用线程堆栈。
- 如果使用了双堆栈,那么在中断里只能使用
MSP
;而在中断以外,可以使用MSP
,也可以使用PSP
。(不过在 RTOS 里,中断以外全部使用PSP
,后面讲原因)。
上电后,系统仅初始化了 MSP
,需要通过额外的汇编代码建立完整的双堆栈系统,当实时内核准备就绪,线程调度正常运行,双堆栈机制开始工作。进中断时系统根据当前状态自动切换堆栈,进程上下文切换时会更新不同线程的 PSP
,通过修改 EXC_RETURN
可以手动切换 MSP/PSP
。
双堆栈机制使得内核/ISR 堆栈和线程应用堆栈分开管理,通过不同的堆栈指针寄存器完成切换,大大提高了系统的效率,在绝大部分的嵌入式实时操作系统中,都使用了双堆栈机制,如 ucos、FreeRTOS、RT-Thread 等。
在一些简单的应用中,例如裸机程序,可以从头到尾都只使用主堆栈,只要确保分配足够的空间即可。
还是一样,通过例程来看,在刚才的程序下面添加:
__set_PSP(__get_MSP()); /* 设置PSP位置 */
__set_CONTROL(0x02); /* bit1 = 1表示使用PSP,bit1 = 0表示使用MSP */
{__IO uint32_t a1 = 0x11111111;__IO uint32_t b1 = 0x22222222;__IO uint32_t c1 = 0x33333333;__IO uint32_t d1 = 0x44444444;__IO uint32_t e1 = 0x55555555;__IO uint32_t f1 = 0x66666666;__IO uint32_t g1 = 0x77777777;__IO uint32_t h1 = 0x88888888;printf("a1 = %x, b1 = %x\r\n", a1, b1);
}
下面继续执行:
这里将 PSP 指向 MSP 一样的地址,然后设置使用 PSP:
然后继续执行:
可以看到,现在只是改成使用 PSP 了,其它和 MSP 没有什么区别。
下面我在我的 SysTick_Handler
函数中打一个断点,然后全速执行:
栈指针又切换回 MSP 了。而退出中断后,又会自动切换回 PSP:
这也印证了刚才所说的,在任务中使用 PSP,在中断中使用 MSP。使用双堆栈,把任务和中断使用的栈区分开,这样安全性会更好一些。
三、PendSV 中断介绍和用法
PendSV
典型使用场合是在上下文切换时(在不同任务之间切换)。上下文切换的本质就是保存当前执行现场 A,切换到另一个任务 B 里面。切换回来时又要恢复执行现场 A(将一系列的寄存器入栈、出栈)。
由于 PendSV
的特点就是支持缓期执行,所以 RTOS 可以利用它这个特点,进行任务调度过程的上下文切换。而为什么要使用缓期执行的特点来进行上下文切换呢?简单的说就是任何 RTOS,都需要尽量不打断外设中断。 我们来举例说明,假如一个系统中有两个就绪的任务,上下文切换被切换的场合可以是:
- 执行一个系统调用
- 系统滴答定时器 SYSTICK 中断,触发了任务的调度。
假如我们在 Systick 中断服务程序中,启动上下文切换,流程图如下:
但若在产生 SysTick 异常时正在响应一个中断,则 SysTick 异常会抢占其ISR。在这种情况下,OS 是不能执行上下文切换的,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。因此,在 CM3 中也是,如果 OS 在某中断活跃时尝试切入线程模式,将触犯用法 fault 异常。
为解决此问题,早期的 OS 大多会检测当前是否有中断在活跃中,只有在无任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick 在执行后不得作上下文切换,只能等待下一次 SysTick 异常),尤其是当某中断源的频率和 SysTick 异常的频率比较接近时,会发生“共振”,使上下文切换迟迟不能进行。
如果将 SysTick 的优先级设置为最低,然后在 SysTick 中进行上下文切换,然后任务调度。这样不会触发 Fault。但是也有一个问题:
- 一般 OS 在调度任务时,会关闭中断,也就是进入临界区,而 OS 任务调度是要耗时的,这就会出现一种情况: 在任务调度期间,如果新的外部 IRQ 发生,CPU 将不能够快速响应处理。
而现在有了 PendSV
,PendSV
异常会自动延迟上下文切换的请求(缓期执行),直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把PendSV
为最低优先级的异常。
如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,以便缓期执行上下文切换。
下面用一个数组模拟栈空间,来展示一下函数的切换:
uint32_t PspStack[100]; /* PspStack栈空间 */int main(void)
{...p_stk = &PspStack[100];/* 硬件自动入栈 */*(--p_stk) = (uint32_t)0x01000000; /* xPSR */*(--p_stk) = (uint32_t)PrintfLogo; /* PC */*(--p_stk) = (uint32_t)0x00000000; /* R14 (LR) */*(--p_stk) = (uint32_t)0xAAAAAAAA; /* R12 */*(--p_stk) = (uint32_t)0xBBBBBBBB; /* R3 */*(--p_stk) = (uint32_t)0xCCCCCCCC; /* R2 */*(--p_stk) = (uint32_t)0xDDDDDDDD; /* R1 */*(--p_stk) = (uint32_t)0xEEEEEEEE; /* R0 : argument *//* 根据需要手动入栈的 */*(--p_stk) = (uint32_t)0x11111111; /* R11*/*(--p_stk) = (uint32_t)0x22222222; /* R10*/*(--p_stk) = (uint32_t)0x33333333; /* R9 */*(--p_stk) = (uint32_t)0x44444444; /* R8 */*(--p_stk) = (uint32_t)0x55555555; /* R7 */*(--p_stk) = (uint32_t)0x66666666; /* R6 */*(--p_stk) = (uint32_t)0x77777777; /* R5 */*(--p_stk) = (uint32_t)0x88888888; /* R4 *//* 使能PendSV中断 */SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;/*##- 测试SVC软中断 ####################################################*/SVCTest(0x11223344, 0x55667788, 0xaabbddee);...
}void PendSV_Handler(void)
{__asm("BKPT 0");__set_PSP((uint32_t)&PspStack[92]);
}
在 M 内核,R0、R1、R2、R3、R12、R14(LR)、R15(PC)、xPSR,这 8 个寄存器是自动入栈的。而其余的寄存器是手动入栈的。
开启了 PendSV
中断后,当来到 SVCTest
(后面会介绍)时就会触发 PendSV
异常。
在 PendSV_Handler
函数中,我们使 PSP 指向 &PspStack[92]
,也就是 PrintfLogo
。所以当触发 PendSV
中断的时候,我们会将 PSP 指针指向 PrintfLogo
函数。也就是说,原本正常情况下,PC 指针保存的是当前 main 函数中下一条指令的地址,这里我们手动将其修改成了另一个函数的地址。这就是任务切换的本质。
来看现象:
再执行一步的时候就会跳转到 PendSV_Handler
。
当跳出 PendSV_Handler
函数后,正常情况下我们应该继续在 main 函数中向后执行,而这里会跳转到 PrintfLogo
函数中:
旁边的寄存器的值和我们硬件自动入栈的值也是对应的:
四、SVC 软中断介绍和用法
SVC
用于生成系统函数调用的请求,例如,用户程序不允许直接访问硬件,操作系统可以通过 SVC
提供对硬件的访问。因此,当用户程序想要使用某些硬件时,可以使用 SVC
指令,然后执行操作系统中的软件异常处理程序,并提供用户应用程序请求的服务。通过这种方式,对硬件的访问由操作系统控制,操作系统可以阻止用户应用程序直接访问硬件,从而提供更可靠的系统。
SVC
还可以使软件更具可移植性,因为用户程序不需要知道硬件的编程细节。用户程序只需要知道应用程序编程接口(API)函数 ID 和参数,而实际的硬件级编程是由设备驱动程序处理的。
SVC
异常由 SVC
指令产生,该指令需要一个立即数作为参数,根据这个参数执行不同的 SVC
处理函数。例:
SVC #0x3 ; 调用SVC 函数3
SVC 0x3 ; 传统的语法(没有#)也可行
在 C 语言中,可以使用编译器关键字函数 __svc
或者使用内联汇编代码来执行 SVC
指令。
对于操作系统来说,当 SVC
处理程序被执行时,我们可以通过读取堆栈中的 PC
值来确定 SVC
指令中的立即数据值,然后从该地址读取指令并屏蔽不需要的位。如果使用的PSP堆栈,则还需要通过LR寄存器判断当前使用的是哪个堆栈。
在 ARM7 中有一个软件中断指令 SWI
(Software interrupt instruction)。实际上,SVC
指令的二进制编码与 ARM7 中的 SWI
是相同的。由于异常模型发生了变化,这条指令被重命名,以确保程序员能够正确地将软件代码从 ARM7 移植到 Cortex-M3。
当 SVC
指令被执行后,处理器会根据中断向量表中的信息跳转到相应的 SVC
中断处理函数,执行 SVC
指令时,处理器会自动保存当前的上下文,并将处理器的模式切换为特权模式,从而允许执行受限的操作。
简单来说,RTOS 内核中的各个 API,不是在任务里执行的,而是在 SVC 软中断里执行的。这样一来,就把 RTOS 内核和任务隔离开了。
__svc
是 MDK 内置的一个定义前缀,用于设置 SVC 软中断的序号(0~255),可以用来区分不同的使用场景
void __svc(0) SVCTest(uint32_t a, uint32_t b, uint32_t c); /* SVC软中断 */.../*##- 测试SVC软中断 ####################################################*/
SVCTest(0x11223344, 0x55667788, 0xaabbddee);
单步运行后直接跳转,并且把函数数据也一并保存在 R0、R1、R2 寄存器中:
当我们调用 OS 内核的 API 的时候,一调用就会进入到 SVC 中断,让这个函数直接在 SVC 中断中执行,这样把 OS 的函数和我们自己写的应用程序隔离开了。
五、特权级和非特权级使用方法
特权级和非特权级只是针对中断外的任务来说的,中断全部都是特权级
如果代码是在中断里执行,则只能是特权级;中断以外可以是特权级,也可以是非特权级。
非特权级主要是 RTX 在使用。如果是在非特权级的模式下,则不能访问和配置 Cortex-M 内核相关的寄存器(例如 SCB),否则会触发异常。
/*##- 测试非特权级 ####################################################*/
__set_CONTROL(0x01); /* bit0 = 1表示应用程序工作在非特权级模式,bit1 = 0表示应用程序工作在特权级模式 */
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; /* 操作SCB就会进入异常 */
例程中先开启非特权模式,然后再操作 SCB(系统控制块)寄存器时会触发异常:
直接就来到 HardFault
了。
那非特权级模式下核心外设寄存器如何初始化?
如果用户将 RTX 操作系统的任务设置在非特权级模式下运行,那么核心外设寄存器应该放在哪里进行初始化呢,主要有以下两种方法:
- 使用 SVC(Supervisor Call)软中断。
- 在初始化和开启RTX多任务前做核心外设的初始化。
那在 Cortex-M3/M4/M7 内核如何切换两种模式?Cortex-M3/M4/M7 中的特殊功能寄存器包括:
- 程序状态寄存器组(PSRs、xPSR)
- 中断屏蔽寄存器组(PRIMASK、FAULTMASK、BASEPRI)
- 控制寄存器(CONTROL)
其中控制寄存器 CONTROL 是用来设置特权级和非特权级切换的,CONTROL 寄存器定义如下:
前面的例子中也演示了如何设置 CONTROL 寄存器:
__set_CONTROL(uint32_t control)
相关文章:

Cortex-M 内核的 OS 特性
目录 一、通用堆栈知识二、双堆栈用法三、PendSV 中断介绍和用法四、SVC 软中断介绍和用法五、特权级和非特权级使用方法 一、通用堆栈知识 在前面讲解 STM32 启动文件的时候就已经提到过,有关堆栈大小的设置是在启动文件中设置的: Heap 主要用于 Mal…...

第十六章 RabbitMQ延迟消息之延迟插件优化
目录 一、引言 二、优化方案 三、核心代码实现 3.1. 生产者代码 3.2. 消息处理器 3.3. 自定义多延迟消息封装类 3.4. 订单实体类 3.5. 消费者代码 四、运行效果 一、引言 上一章节我们提到,直接使用延迟插件,创建一个延迟指定时间的消息&…...

[单master节点k8s部署]32.ceph分布式存储(三)
基于ceph rbd生成pv 在集群中认证ceph 用下面代码生成ceph的secret .创建 ceph 的 secret,在 k8s 的控制节点操作: 回到 ceph 管理节点创建 pool 池: [rootmaster1-admin ~]# ceph osd pool create k8stest 56 pool k8stest created [rootm…...

git 相关问题解决一一记录
文章目录 gitssh.github.com: Permission denied (publickey)1. 检查 SSH 密钥生成新的 SSH 密钥添加 SSH 密钥到 GitHub 2. 配置 SSH 代理启动 SSH 代理添加私钥到 SSH 代理 3. 检查 SSH 配置文件4. 测试 SSH 连接5. 检查防火墙和网络设置6. 检查 GitHub 账户设置详细步骤 更新…...

UE4 材质学习笔记04(着色器性能优化)
一.着色器性能优化 1.衡量着色器的性能 衡量着色器性能的主要方法有三个 第一个:可以使用场景的视图模式的优化视图模式的着色器复杂度 下面的滑条代表了着色器指令的复杂度 如果场景大部分是绿色的,说明着色器耗能低,反之白色则是很糟糕…...

3、Redis Stack扩展功能
文章目录 一、了解Redis产品二、申请RedisCloud实例三、Redis Stack体验1、RedisStack有哪些扩展?2、Redis JSON1、Redis JSON是什么2、Redis JSON有什么用3、Redis JSON的优势 3、Search And Query1、传统Scan搜索2、Search And Query搜索 4、Bloom Filter1、布隆过…...

Flythings学习(二)控件相关
文章目录 1 前言2 通用属性2.1 控件ID值2.2 控件位置2.3 背景色2.4 背景图2.5 显示与隐藏2.6 控件状态2.7 蜂鸣器控制 3 文本类TextView4 按键类 Button4.1 系统按键4.2 处理按钮长按事件4.3 处理按键触摸事件 5 复选框CheckBox6 单选组 RadioGroup7 进度条,滑块7.1…...

关于multiprocessing使用freeze_support()方法
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、freeze_support()?二、使用方法总结 **注意下面** 如果有车载讨论需要的小伙伴,可以私信加我微信,拉你进群,…...

基于rk356x u-boot版本功能分析及编译相关(一)
🎏技术驱动源于热爱,祝各位学有所成。 文章目录 uboot的分支是next-dev历史版本v2017-09uboot支持DM框架uboot前级pre-loader支持及引导下级uboot分区支持uboot支持固件格式secure bootuboot编译脚本位置build.shuboot/make.shrkbin仓库uboot的分支是next-dev历史版本v2017-…...

Jenkins---01
什么是敏捷开发 敏捷开发以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发。在敏捷开 发中,软件项目在构建初期被切分成多个子项目,各个子项目的成果都经过测试,具备可视、 可集成和可运行使用的特征。换言之&…...

第十五届蓝桥杯C++B组省赛
文章目录 1.握手问题解题思路1(组合数学)解题思路2(暴力枚举) 2.小球反弹做题思路 3.好数算法思路(暴力解法)---不会超时 4.R格式算法思路 5.宝石组合算法思路---唯一分解定理 6.数字接龙算法思路----DFS 7…...

线程 vs 虚拟线程:深入理解及区别
Java 提供了两种线程机制:普通线程(平台线程)和 虚拟线程。普通线程是 Java 中经典的并发处理方式,而虚拟线程是随着 Java 21 引入的新特性,旨在提升并发性能和开发体验。本文将详细探讨它们的区别,并帮助你…...

【WEB应用安全测试指南–蓝队安全测试2】--超详细-可直接进行实战!!!亲测-可进行安全及渗透测试
安全基础理论入门知识参考上一篇《WEB应用安全测试指南蓝队安全测试1》 WEB应用安全测试指南2 一、文件 I/O 类1.1、任意文件上传1.2、任意文件下载1.3、文件包含 二、接口安全类2.1、短信炸弹2.2、邮件炸弹2.3、短信内容可控2.4、邮件内容可控 三、逻辑流程类3.1、越权3.2、未…...

使用HTML、CSS和JavaScript创建滚动弹幕效果
使用HTML、CSS和JavaScript创建滚动弹幕效果 在现代网页设计中,滚动文本是一种常见的动态效果,可以吸引用户的注意力并增强交互体验。在这篇博客文章中,我们将详细介绍如何使用HTML、CSS和JavaScript实现滚动文本效果。 效果 步骤1…...

【C语言】--数组
😊个人主页: 起名字真南 😋个人专栏:【数据结构初阶】 【C语言】 【C】 目录 1 数组的概念2 一维数组的创建和初始化2.2 数组的初始化2.3 数组类型 3 一维数组的使用3.1 数组下标3.2 数组的输入 4 一维数组在内存中的存储5 sizeof计算数组中的元素6 二维…...

面向B2B市场的Spring Boot医疗病历系统开发
第1章绪论 计算机已经从科研院所,大中型企业,走进了平常百姓家,Internet遍及世界各地,在网上能够用计算机进行文字草拟、修改、打印清样、文件登陆、检索、综合统计、分类、数据库管理等,用科学的方法将无序的信息进行…...

闭着眼学机器学习——支持向量机分类
引言: 在正文开始之前,首先给大家介绍一个不错的人工智能学习教程:https://www.captainbed.cn/bbs。其中包含了机器学习、深度学习、强化学习等系列教程,感兴趣的读者可以自行查阅。 1. 算法介绍 支持向量机(Support Vector Mach…...

今日指数项目day8实战权限管理器(上)
3.权限管理器 3.1 权限列表展示功能 1)原型效果 2)接口说明 功能描述: 查询所有权限集合 服务路径: /api/permissions 服务方法:Get 请求参数:无响应数据格式: {"code": 1,"data":…...

《机器学习与数据挖掘综合实践》实训课程教学解决方案
一、引言 随着信息技术的飞速发展,人工智能已成为推动社会进步的重要力量。作为人工智能的核心技术之一,机器学习与数据挖掘在各行各业的应用日益广泛。本方案旨在通过系统的理论教学、丰富的实践案例和先进的实训平台,帮助学生掌握机器学习…...

linux中软连接和硬链接的区别
定义与概念 硬链接(Hard Link):硬链接是文件系统中的一个概念,它直接指向文件系统中的物理数据块。可以把硬链接看作是原始文件的一个别名,它们共享相同的inode(索引节点)编号。在Linux文件系统…...

#Swift 对比 Static 在Swift 和 OC中的用法
在 Objective-C 和 Swift 中,static 关键字都用于定义类型级别的成员,但它们的用法和行为在两个语言中有所不同。让我们来详细对比一下 Objective-C 和 Swift 中 static 的使用方式和特性。 1. Objective-C 中的 static 在 Objective-C 中,…...

yakit使用教程(三,端口探测和指纹扫描)
本文仅作为学习参考使用,本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 前言: 前文链接:yakit下载安装教程。 1.端口扫描的作用。 对目标端口进行扫描可以知道目标服务器开启了什么服务,以便于针对其所存在的服务展开…...

一维数组的引用
#define SIZE 5 int main(void) { int i 0; int arr[SIZE] { 86,85,85,896,45 };//同理五个数据只是偶然,可能会更多 //输入 for (i 0;i < SIZE;i) { printf("请输入你的第%d个值:",i1); scanf_s(&…...

Vue3 watch 监视属性
作用:监视数据的变化(和Vue2中的watch作用一致)特点:Vue3中的watch只能监视以下四种数据: ref定义的数据。reactive定义的数据。函数返回一个值(getter函数)。一个包含上述内容的数组。 我们在V…...

大数据-158 Apache Kylin 安装配置详解 集群模式启动
点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…...

PHP商会招商项目系统一站式服务助力企业腾飞
商会招商项目系统——一站式服务,助力企业腾飞 🚀💼 🚀 开篇:企业成长的加速器,商会招商项目系统来袭 在竞争激烈的市场环境中,企业如何快速找到适合自己的发展路径,实现腾飞&…...

pnpm 和 npm
pnpm 和 npm 是 JavaScript 生态系统中常用的包管理工具,它们各自有不同的特性和优缺点。下面是这两者的详细比较: 1. 基本概念 npm (Node Package Manager): 是 Node.js 的默认包管理器,提供安装、更新、卸载 JavaScript 包的功…...

笔试算法总结
文章目录 题目1题目2题目3题目4 题目1 使用 StringBuilder 模拟栈的行为,通过判断相邻2个字符是否相同,如果相同就进行删除 public class Main {public static String fun(String s) {if (s null || s.length() < 1) return s;StringBuilder builde…...

mybatisPlus对于pgSQL中UUID和UUID[]类型的交互
在PGSQL中,有的类型是UUID和UUID[]这种类型,在mybatis和这些类型交互的时候需要手动设置类型处理器才可以,这里记录一下类型处理器的设置 /*** UUID类型处理器*/ public class UUIDTypeHandler extends BaseTypeHandler<UUID> {/*** 获…...

vue3 高德地图标注(飞线,呼吸点)效果
装下这两个 npm 忘了具体命令了,百度一下就行 “loca”: “^1.0.1”, “amap/amap-jsapi-loader”: “^1.0.1”, <template><div id"map" style"width: 100%;height: 100%;"></div> </template><script setup> …...