不少人在调试RISC-V core时,面对异常的出现不知所措,不知道如何定位代码问题。这里将从RISC-V异常机制以及几个异常实例学习下。

1 异常机制

1.1 什么是异常

异常是软件程序员不得不要深入了解的,首先在学习异常机制前,对异常要有一个明确的理解。

狭义上来说异常和中断的最大区别在于:中断往往是外部原因导致,而异常则是因为处理器内部原因或者程序执行引起,譬如硬件故障、程序故障,或者执行系统调用引起,简而言之异常来源于内因。

实际上,广义上来说,中断也是异常的一种,站在处理器的角度来看,无论是异常还是中断发生时,处理器都会暂停当前执行的程序,转而去处理中断或者异常的程序,处理完成之后视情况恢复之前被暂停的程序。
处理器广义上的异常,通常只分为同步异常(Synchronous Exception)异步异常(Asynchronous Exception)

  • 同步异常
    同步异常时由于执行程序指令流或者试图执行程序指令流造成的异常。这种异常能够通过异常指令(PC)直接定位到,另外这种异常时稳定复现的。(比如指令非对齐、非法指令或者访问地址属性出错等)
  • 异步异常
    异步异常是由“外因”引起,比如“外部中断”,或者执行程序时堆栈溢出,导致的异常。另外对于异步异常可以分为两种:
    • 精确异步异常( Precise Asynchronous Exception),指令在响应异常后,处理器状态能够精确反应为某一条指令的边界,比如中断。
    • 非精确异步异常( Imprecise Asynchronous Exception ),指令在响应异常后,处理器状态无法精确反应为某一条指令的边界,比如读写存储器异常。

当然,一般现在商用的CPU很难说用户发现了硬件异常,因为发布之前已经都做了充分的验证了,一般用户拿到,常见的都是因为软件原因造成的。

通常情况下,对于软件工程师来说,理论上可以把中断当作异常的来看,但实际上各厂家的设计中断一般还是异常分开处理的,所以使用上还是基于狭义上的异常概念来区分异常和中断,前面有讲过RISC-V的中断机制和应用:
RISC-V CLINT、PLIC及芯来ECLIC中断机制分析 —— RISC-V中断机制(一)
ECLIC中断流程及实际应用 —— RISC-V中断机制(二)

1.2 相关CSR

RISC-V有四种特权模式:M/H/S/U,之前有介绍过 RISC-V特权模式及切换_risc-v 模式,感兴趣的可以移步学习,RISC-V提供了M模式和S模式下分别的异常处理相关寄存器。

  • **​​M模式 (Machine Mode)**​​ 是最高特权级别,所有 RISC-V 处理器都必须实现。它提供了一整套以 ‘m’ 开头的异常寄存器(如 mtvec, mepc, mcause, mstatus, mie, mip, mtval, mscratch),用于处理最底层的硬件异常和中断。此模式是系统启动和初始化的默认环境,简单嵌入式系统可能仅运行于此模式。

  • **​​S模式 (Supervisor Mode)**​​ 旨在支持运行类 Unix 等现代操作系统。它配备了一套以 ‘s’ 开头的异常寄存器(如 stvec, sepc, scause, sstatus, sie, sip, stval, sscratch),其功能与 M 模式下的相应寄存器类似,但用于操作系统内核的异常处理。

默认情况下,所有异常首先由 M 模式处理。但通过 ​​异常委托机制​​(使用 medeleg 和 mideleg 寄存器),可以选择性地将部分中断和同步异常委托给 S 模式处理,从而减少特权模式切换的开销,提升操作系统的处理效率,这里也以M模式下异常处理机制为例介绍。

1.2.1 mcause(Machine Exception Cause)

RISC-V标准的mcause格式如下:其中最高bit Interrupt用来指示当前是中断还是异常,位bit用来记录异常code。
在这里插入图片描述
异常code
在这里插入图片描述
另外,如果一条指令引发多个同步异常,下面图示指明了mcause异常code的优先顺序。

在这里插入图片描述
另外之前在中断时有介绍过RISC-V官方还未将CLIC中断纳入标准,但是有些业界RISC-V设计公司是基于CLIC做了设计,比如芯来科技的ECLIC,支持中断嵌套和中断咬尾,对mcause进行了修改,如下:
在这里插入图片描述
EXCCODE字段在异常时为异常编码,中断时为中断号,其他位域不再详细说明,可以参见中断相关博客。

1.2.2 mtvec(Machine Trap-Vector Base-Address Register)

该寄存器用于保存异常向量地址,由向量基址和向量模式组成
在这里插入图片描述
向量模式字段,其中非向量中模式,全部异常指向同一地址,而非向量模式,异步中断地址指向BASE+4xcause(中断号)
在这里插入图片描述
另外,要求异常的BASE地址必须4字节对齐,前面也有介绍过芯来科技中断架构,也是对mtvec做了修改。
具体时如何实现向量和非向量中断的,同样参考中断相关博客。
在这里插入图片描述

1.2.3 mepc (Machine Exception Program Counter)

机器模式异常程序计数器,它指向发生异常的指令。对于同步异常,mepc 指向导致异常的指令;对于中断,它指向中断处理后应该恢复执行的位置。
另外,值得注意的是,虽然 mepc 寄存器会在异常发生时自动被硬件更新,但是 mepc 寄存器本身也是一个(在 Machine Mode 下)可读可写的寄存器,因此软件也可以直接写该寄存器以修改它的值。
在这里插入图片描述

1.2.4 mie(Machine Interrupt Enable)和mip(Machine Interrupt Pending)

MEIE/MEIP、MTIE/MTIP、MSIE/MSIE,分别对应M模式下的外部中断、timer中断、软中断的enable和pending。

在这里插入图片描述
在这里插入图片描述
如果使用芯来的ECLIC,是不需要使用mie和mip的,具体参考中断相关博客

1.2.5 mstatus(Machine Status)

机器模式(M-mode)下的一个核心​​控制与状态寄存器​​(CSR)。它主要负责全局中断管理、特权模式切换及处理器状态监控。
在这里插入图片描述
SD:status dirty 状态脏位,
MIE/SIE:机器/监督者模式全局中断使能
MPIE/SPIE:发生异常前MIE/SIE的使能状态,会被保存到这里。(P:previous)
MPP/SPP:发生异常时,硬件将异常前的特权模式保持到这里。执行mret或者sret,处理器将恢复为MPP/SPP所指定的模式。
FS/XS/VS:浮点单元状态/扩展单元状态/向量单元状态(RVV扩展)
UBE:字节序控制。0表示小端,1表示大端。通常固定为小端。
SUM:允许S模式下访问U模式的页面(用于操作系统读写用户程序数据)
MXR:使能可执行读取,置1表示所有可读页表变为可执行
TSR/TW/TVM/MPRV笔者还没有使用过,不在列举,可以自行搜索了解。

1.2.6 mtval(Machine Trap Value Register)

在异常发生时,由硬件自动更新的​​控制状态寄存器​​(CSR)。它的主要作用是​​提供与异常相关的附加信息​​,帮助软件诊断和处理异常。
在这里插入图片描述
mtval 提供的是​​辅助信息​​,确定异常的根本原因主要还需结合 ​​mcause​​(异常原因寄存器)和 ​​mepc​​(异常程序计数器)的值,另外mtval的具体行为​​取决于硬件实现​​,并非所有异常或所有芯片都一定会填充有效值

1.2.7 mscratch(Machine Scratch)

mscratch 的具体使用方式​​很大程度上取决于软件的实现​​,比如:可以在异常时暂存某些通用寄存器的值,防止关键数据被破坏;还可以在调试时利用mscratch里存储临时调试信息或者断点信息等

1.3 异常处理流程

前面说中断也是异常的一种,所以异常的处理流程和中断一样。在bootloader阶段,软件初始化了mtvec寄存器,把异常handler的地址初始化到mtvec。然后软件配合硬件来完成异常处理,具体流程如下:
在这里插入图片描述
当异常发生时,处理过程分为​​硬件自动执行​​和​​软件处理​​两大部分:

  • 第一阶段:硬件自动响应(处理器单元)
    一旦检测到异常,硬件会​​自动且立即​​执行以下操作
  1. 关键信息保存​​:
    • 将当前 PC 值存入 ​​mepc​​,作为返回地址。
    • 将异常原因写入 ​​mcause​​。
    • 将异常相关的附加信息(如出错的地址或指令)写入 ​​mtval​​。
  2. ​状态切换​​:
    • 将当前权限模式保存到 ​​mstatus.MPP​​,然后切换到 ​​M 模式​​(Machine Mode)。
    • 将当前全局中断使能位 ​​mstatus.MIE​​ 保存到 ​​mstatus.MPIE​​,然后​​清除 MIE(关闭全局中断)​​,防止处理过程被新的中断打断。
  3. ​​跳转执行​​:
    • 处理器从 ​​mtvec​​ 寄存器指向的地址开始取指执行,即跳转到预先设置好的异常处理程序。
  • 第二阶段:软件处理(操作系统/固件)
    这是操作系统或固件编写者需要实现的代码逻辑,主要步骤包括:
  1. ​​保存执行上下文​​:
    • 硬件​​不会自动保存​​通用寄存器(x0-x31)。​​软件必须​​首先将所有的通用寄存器压入栈(通常是内核栈)中,以防止破坏被中断程序的现场。
  2. 诊断异常原因​​:
    • 软件读取 ​​mcause​​ 寄存器,根据其中的异常编码判断具体的异常或中断类型。
  3. ​执行处理程序​​:
    • 根据异常类型,跳转到相应的处理例程(如系统调用处理、中断服务程序等)。
  4. 恢复现场并返回​​:
    • 处理完成后,从栈中​​恢复所有通用寄存器​​的原始值。
    • 执行 ​​mret​​ 指令。

最后,mret指令会触发硬件:

  • 将 ​​mepc​​ 中的值载入 PC,从而返回到原来的执行流。
  • 根据 ​​mstatus​​ 中保存的信息(MPIE, MPP)恢复之前的权限模式和中断使能状态。

这里有几点需要注意:

  • 1、RISC-V标准中,中断和异常硬件处理是一样的,处理函数入口都是在mtvec,软件根据mcause Interrupt字段的值来区分异常中断
  • 2、但这里效率不高,社区开源的CLIC对这块进行了优化,把中断和异常分开处理,mtvec自作为异常的处理入口,中断单独定义一组寄存器,可以参考我之前的博客:
  • 3、一般出现异常时,我们就会在异常服务函数里dump出来一些关键信息,然后把core给停掉(已经异常了,要去debug异常问题去了,在跑下去也没什么意义的),所以就不会有后面的流程(恢复寄存器状态、推出异常处理流程等),比如跑linux时经常会看见oops一堆的打印,就是内核在异常时系统抛出的信息,以方便定位问题。

2 异常定位

RISC-V异常机制是很直接的,前面有提到会在异常处理函数时打印出来关键信息方便分析定位问题,下面就针对常见的几种异常举例说明。
我这里使用的芯来科技的QEMU来实现的异常,平台搭建可以参考:
RISC-V汇编学习(四)—— RISCV QEMU平台搭建(基于芯来平台)

2.1 异常处理函数

可以看到在进入异常之后,会把异常相关的CSR、通用寄存器和堆栈信息打印出来。

/*** \brief      System Default Exception Handler* \details* This function provides a default exception and NMI handler for all exception ids.* By default, It will just print some information for debug, Vendor can customize it according to its requirements.* \param [in]  mcause    code indicating the reason that caused the trap in machine mode* \param [in]  sp        stack pointer*/static void system_default_exception_handler(unsigned long mcause, unsigned long sp)
{/* TODO: Uncomment this if you have implement printf function */printf("MCAUSE : 0x%lx\r\n", mcause);printf("MDCAUSE: 0x%lx\r\n", __RV_CSR_READ(CSR_MDCAUSE));printf("MEPC   : 0x%lx\r\n", __RV_CSR_READ(CSR_MEPC));printf("MTVAL  : 0x%lx\r\n", __RV_CSR_READ(CSR_MTVAL));printf("HARTID : %u\r\n", (unsigned int)__get_hart_id());Exception_DumpFrame(sp, PRV_M);
#if defined(SIMULATION_MODE)extern void simulation_exit(int status);simulation_exit(1);
#else#ifdef CFG_SIMULATIONsimulation_fail();#endifwhile (1);
#endif
}/*** \brief      Dump Exception Frame* \details* This function provided feature to dump exception frame stored in stack.* \param [in]  sp    stackpoint* \param [in]  mode  privileged mode to decide whether to dump msubm CSR*/
void Exception_DumpFrame(unsigned long sp, uint8_t mode)
{EXC_Frame_Type *exc_frame = (EXC_Frame_Type *)sp;
#ifndef __riscv_32eprintf("ra: 0x%lx, tp: 0x%lx, t0: 0x%lx, t1: 0x%lx, t2: 0x%lx, t3: 0x%lx, t4: 0x%lx, t5: 0x%lx, t6: 0x%lx\n"            "a0: 0x%lx, a1: 0x%lx, a2: 0x%lx, a3: 0x%lx, a4: 0x%lx, a5: 0x%lx, a6: 0x%lx, a7: 0x%lx\n"            "cause: 0x%lx, epc: 0x%lx\n", exc_frame->ra, exc_frame->tp, exc_frame->t0,            exc_frame->t1, exc_frame->t2, exc_frame->t3, exc_frame->t4, exc_frame->t5, exc_frame->t6,            exc_frame->a0, exc_frame->a1, exc_frame->a2, exc_frame->a3, exc_frame->a4, exc_frame->a5,            exc_frame->a6, exc_frame->a7, exc_frame->cause, exc_frame->epc);
#elseprintf("ra: 0x%lx, tp: 0x%lx, t0: 0x%lx, t1: 0x%lx, t2: 0x%lx\n"            "a0: 0x%lx, a1: 0x%lx, a2: 0x%lx, a3: 0x%lx, a4: 0x%lx, a5: 0x%lx\n"            "cause: 0x%lx, epc: 0x%lx\n", exc_frame->ra, exc_frame->tp, exc_frame->t0,            exc_frame->t1, exc_frame->t2, exc_frame->a0, exc_frame->a1, exc_frame->a2, exc_frame->a3,            exc_frame->a4, exc_frame->a5, exc_frame->cause, exc_frame->epc);
#endifif (PRV_M == mode) {/* msubm is exclusive to machine mode */printf("msubm: 0x%lx\n", exc_frame->msubm);}
}

2.2 读写访问异常定位

该异常发生时,异常打印如下,我们来分析定位下:
在这里插入图片描述
当然一开始我们并不清楚是什么异常,并且是哪里,什么造成的原因造成的这种异常;接下来就来分析下。
mcause:最高bit是Interrupt域,值为0,表明当前是一个异常,EXCODE=5,一个load access 异常,也就是说程序里读了一个非法地址(最高byte 0x3是MPP表示中断前就是在M模式)
mdcause:这个是芯来科技RISC-V core自定义的CSR,用来进一步查看异常的原因(该兴趣自行找资料了解下)
在这里插入图片描述
mepc:0x8800120e,异常地址,但读写异常时非精确的异常,该地址并不能精确定位异常位置(一般异常位置在该地址之前)。
mtval:0xff00b000,异常地址,该地址可以正确反映到异常访问地址的,说明我们读了一个0xff00b000的非法地址。
ra是返回地址,当前执行结束之后会跳到该地址,也就是说在ra前出现了访问异常。

当然到这里已经很清晰了,实际就是我们读了一个非法地址,这里故意读了下0xff00b000,

uint32_t addr_load_test(void)
{uint32_t * test_addr = (uint32_t *) 0xff00b000;uint32_t value = REG32( test_addr);
}

汇编:

880011f8 <addr_load_test>:
880011f8:	1101                	add	sp,sp,-32
880011fa:	ce06                	sw	ra,28(sp)
880011fc:	cc22                	sw	s0,24(sp)
880011fe:	1000                	add	s0,sp,32
88001200:	ff00b7b7          	lui	a5,0xff00b
88001204:	fef42623          	sw	a5,-20(s0)
88001208:	fec42783          	lw	a5,-20(s0)
8800120c:	439c                	lw	a5,0(a5)
8800120e:	fef42423          	sw	a5,-24(s0)
88001212:	0001                	nop
88001214:	853e                	mv	a0,a5
88001216:	40f2                	lw	ra,28(sp)
88001218:	4462                	lw	s0,24(sp)
8800121a:	6105                	add	sp,sp,32
8800121c:	8082                	ret

mepc是0x8800120e,实际是上一条 8800120c: 439c lw a5,0(a5) 执行报错,这里从内存地址 a5 + 0(0xff00b000)处读取一个 32 位的字(4 字节),并将其写入寄存器 a5。

这里通用寄存器是可以正确反映异常前的信息的,如果想要从通用寄存器来定位,就需要直到RISC-V的abi规则了,后面会展示下。

当然写异常也是一样的。
只需要修改下代码,向非法地址0xff00b000中写入数据即可。

uint32_t addr_load_test(void)
{uint32_t * test_addr = (uint32_t *) 0xff00b000;REG32( test_addr) = 0x1;
}

运行代码将会出现下面为store非法地址异常打印:
在这里插入图片描述
读写异常当然并非一定是访问了非法地址,比如访问的IP模块没有时钟或者复位被拉住,此时访问IP内部的寄存器或者memory一样会产生读写异常。

2.3 非法指令异常

2.3.1 text段被异常改写

在这里插入图片描述
异常前后的汇编:

88001274:	301027f3          	csrr	a5,misa
88001278:	fcf42e23          	sw	a5,-36(s0)
8800127c:	fdc42783          	lw	a5,-36(s0)
88001280:	fef42023          	sw	a5,-32(s0)

通过gdb来读取异常地址处的指令值,如下:
0x88001274处期望的指令0x301027f3被改写为了0x00001234
在这里插入图片描述
接下来可以通过watchpoint来监控0x88001274地址的改动,便可以发现有代码(这里故意修改)修改了text段的代码(当然也是我们故意造的异常点)
在这里插入图片描述

2.3.2 栈帧被异常修改

一般我们会故意修改text段代码,但有时间,软件代码不合理,造成了栈溢出、数据污染等也会造成指令异常。
在这里插入图片描述
查看上面的异常打印,通过CSR寄存器mcause知道是指令异常,但如何其他CSR比如MEPC,MTVAL都不是预期的(不在正常的内存分配地址),可能很多人看到这里无从下手,不好定位异常位置,当然原因是,不熟悉RISCV abi规则,对通用寄存器使用不熟悉的。

我之前在
RISC-V汇编学习(五)—— 汇编实战、GCC内联汇编(基于芯来平台)的博客中有深入分析过riscv的abi规则,可以移步学习。

如果调试经验多的话,容易分析,当前可能是因为堆栈溢出,导致了数据污染。我们可以看到打印里已经有很多寄存器包括ra,tp等寄存器的值已经不真实了,还有哪些是可信的呢?s0-sp表示当前使用的栈帧(RISC-V用s0和sp来填充栈帧)。
此时通过gdb回到异常现场,读取s0和sp的值:
在这里插入图片描述
注意异常入口必须把软件处理部分干掉,不然此时将会进入异常的栈帧,并可能破坏掉当前异常的栈帧。
在这里插入图片描述
同样可以用watchpoint来定位软件code,发现有两处用到了该栈地址。
在这里插入图片描述
很容易就可以定位到问题代码的位置,实际上是我们定义了一个10个无符号整形变量,但初始化15个地址,栈帧溢出,导致地址踩脏。实际代码如下:

void test(void)
{uint32_t test_data[10] = {0};for(int i = 0; i < 15;i++){test_data[i] = i;}
}

把上面代码修改正确查看下该函数栈帧内容,如下:
在这里插入图片描述
这里是把返回地址和上一个栈顶指针地址覆盖了,导致了指令异常。

当然异常场景还有不少,这里仅展示几个常见的;实际无论什么样的异常都是可以从软硬件的角度,去分析问题,前提是对ISA相对比较熟悉;另外一般裸机或者简单rtos下的代码量比较小时,通过异常机制可以帮忙快速定位问题;如果时linux下多线程任务的异常,当然也可以用,只是定位会相对会比较麻烦很多,如果有trance来dump指令流,将会事半功倍。

参考:
手把手教你设计CPU——RISC-V处理器篇(胡振波)
RISC-V汇编学习(五)—— 汇编实战、GCC内联汇编(基于芯来平台)

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

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

相关文章

c++中导出函数调用约定为__stdcall类型函数并指定导出函数名称

开发环境在Visual studio 2022版本下&#xff0c;为防止编译器重命名函数名称&#xff08;会加上8等等乱七八糟的东西&#xff09;&#xff0c;我们对函数名称进行指定&#xff1a;一、新建.def文件&#xff0c;名称须与dll名称相同&#xff0c;并放在与cpp文件相同文件夹下&am…

Vision Transformer (ViT) :Transformer在computer vision领域的应用(二)

METHOD,论文主要部分 In model design we follow the original Transformer (Vaswani et al., 2017) as closely as possible. An advantage of this intentionally simple setup is that scalable NLP Transformer architectures – and their efficient implementations –…

AI 论文周报丨红队测试语言模型/多视角 3D 点追踪方法/蛋白质表示学习框架/密码学漏洞检测新框架……

近年来&#xff0c;已有若干方法尝试从单目视频实现 3D 点跟踪&#xff0c;然而由于在遮挡和复杂运动等挑战性场景中难以准确估计 3D 信息&#xff0c;这些方法的性能仍难以满足实际应用对高精度与鲁棒性的要求。 基于此&#xff0c;苏黎世联邦理工学院、卡内基梅隆大学联合提出…

STM32 通过USB的Mass Storage Class读写挂载的SD卡出现卡死问题

问题描述&#xff1a;使用stm32cubemx生成的sdio和usb Mass Storage Class的代码后&#xff0c;在USB_DEVICE\App\usbd_storage_if.c文件里面的接口调用以下函数出现卡死问题&#xff1a; SD_Driver.disk_initialize(0); SD_Driver.disk_read(lun, buf, blk_addr, blk_len) SD_…

Go语言中 error 接口与自定义错误类型的深入解析

在 Go 语言开发中&#xff0c;我们经常需要处理各种错误情况。Go 语言通过 error 接口提供了一套简洁而强大的错误处理机制。然而&#xff0c;当涉及到自定义错误类型时&#xff0c;许多开发者会遇到一些令人困惑的问题。本文将通过一个实际案例来深入探讨这个问题。 问题背景 …

字幕编辑工具推荐,Subtitle Edit v4.0.13发布:增强语音识别+优化翻译功能

大家好呀&#xff0c;不知道大家有没有做自媒体相关工作的呢&#xff0c;你们是不是也觉得剪辑视频时最头疼的往往不是画面而是字幕&#xff0c;时间轴对不上、格式不兼容、需要手动翻译&#xff0c;这些琐碎工作消耗的精力甚至超过剪辑本身。 当你试遍各种在线工具却发现要么…

【Java后端】Spring Boot 集成雪花算法唯一 ID

Spring Boot 实现基于雪花算法的分布式唯一 ID 生成器在分布式系统中&#xff0c;我们经常需要生成 全局唯一 ID&#xff0c;比如用户 ID、订单号、消息 ID 等。常见的方式有&#xff1a;数据库自增主键、UUID、Redis/Zookeeper 分布式 ID 服务、百度 UidGenerator、美团 Leaf …

C语言初尝试——洛谷

一、C数组&#xff1a;C 语言支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。声明数组在 C 中要声明一个数组&#xff0c;需要指定元素的类型和元素的数量&#xf…

C++八大排序

C排序算法一、概览二、代码实现1.冒泡排序2.插入排序3.希尔排序4.堆排序5.选择排序6.快速排序7.归并排序三、排序时间、空间复杂度总结排序&#xff0c;是C各大算法当中非常常见的一个步骤&#xff08;过程&#xff09;&#xff0c;通常我们使用便捷的algorithmalgorithmalgori…

每天五分钟深度学习:深层神经网络的优势

本文重点 在人工智能领域,深层神经网络(DNN)的崛起标志着技术范式的根本性转变。相较于传统浅层神经网络(如单层感知机、线性回归模型),深层网络通过引入多层隐藏层,实现了对复杂数据模式的深度解析与高效建模。 深层神经网络 神经网络中输入层表示神经网络的第0层,…

相机几何 空间点到像素平面转换

一个空间中点到像素平面转换&#xff0c;需要经过1. 空间坐标系转换到相机坐标系2. 相机坐标系下3D点到相机平面转换3. 相机平面到像素平面转换相机三维空间到像素平面转换1. 3D点到相机平面转换2. 相机平面到像素平面转换涉及到单位的转换&#xff0c;和像素原点到相机平面原点…

webpack5 vue3同一仓库,不同命令切换项目

技术方案&#xff1a;手动输入不同的命令&#xff0c;启动不同项目。实现这种能力本篇文章是通过不同路由划分&#xff0c;进而实现不同项目的划分。所以简单来说就是通过输入不同命令行在webpack中找到不同项目的路由&#xff0c;进而打不同项目的包&#xff0c;实现项目隔离。…

PowerBI实战-制作带有同比及趋势线的双柱状图

一、引言 今天的PowerBI报表的制作相对有一点复杂&#xff0c;我们直接根据最终展示图来讲解&#xff1a; 可以看到&#xff0c;我们今天要制作的图像需要包括以下几点&#xff1a;时间维度的趋势、两种不同维度的数据对比、不同数据标签的展示、不同年份间环比的标签展示以及…

物联网智能网关配置教程:实现注塑机数据经基恩士PLC上传至云平台

一、项目背景随着制造业向智能化、信息化方向快速发展&#xff0c;注塑车间作为塑料制品制造的核心环节&#xff0c;面临着设备协议多样、数据孤岛严重、系统集成困难等问题。某大型注塑企业计划对其老旧车间进行数字化改造&#xff0c;实现设备数据采集、远程监控与MES系统对接…

【实战】预警算法--噪声添加机制

1. 背景 在多变量自联想预测或异常检测场景中&#xff0c;我们常使用带噪自编码器&#xff08;Denoising AutoEncoder&#xff0c;DAE&#xff09;来训练模型&#xff0c;使模型能够从带噪输入中重构原始数据。噪声的添加方式对训练效果、稳定性以及模型用途有显著影响。 2. 两…

ChromaDB探索

关于 ChromaDB、向量与 RAG 系统的核心知识问答总结 ​​Q1: ChromaDB 是什么&#xff1f;它在数据库领域中扮演什么角色&#xff1f;​​​​A:​​ ChromaDB 是一款开源的​​向量数据库​​。它的核心角色是专门为 AI 应用&#xff08;如语义搜索、推荐系统、RAG&#xff09…

C# 基于halcon的视觉工作流-章33-矩状测量

C# 基于halcon的视觉工作流-章33-矩状测量 本章目标&#xff1a; 一、gen_measure_rectangle2准备提取垂直于矩形的直边&#xff1b; 二、measure_pos 提取垂直于矩形或环形弧的直线边缘&#xff1b; 三、measure_pairs提取垂直于矩形或环形弧长轴的直边对&#xff1b; 四、匹配…

Day05_苍穹外卖——Redis店铺营业状态设置

目录1.1 Redis简介1.2 Redis下载与安装1.2.1 Redis下载1.2.2 Redis安装1.3 Redis服务启动与停止1.3.1 服务启动命令1.3.2 客户端连接命令1.3.3 修改Redis配置文件1.3.4 Redis客户端图形工具2. Redis数据类型2.1 五种常用数据类型介绍2.2 各种数据类型特点3. Redis常用命令3.1 字…

双指针:字符串

题目&#xff1a;字符串 题目概述&#xff1a;找包含所有小写字母的最短字符串。 重点思路&#xff1a; right是 < len-1字符 - ‘26’转换成整形再判断&#xff08;写字符a也可以&#xff0c;更准确&#xff09;。 #include <iostream> #include <algorithm>…

HarmonyOS 应用开发深度实践:精通 Stage 模型与 UIAbility 生命周期

好的&#xff0c;请看这篇关于 HarmonyOS Stage 模型与 UIAbility 深度实践的技术文章。 HarmonyOS 应用开发深度实践&#xff1a;精通 Stage 模型与 UIAbility 生命周期 引言 随着 HarmonyOS 4、5 的广泛部署和 HarmonyOS NEXT (API 12) 的发布&#xff0c;华为的分布式操作系…