TinyEMU源码分析之中断处理

TinyEMU源码分析之中断处理

  • 1 触发中断
  • 2 查询中断
    • 2.1 查询中断使能与pending状态(mie和mip)
    • 2.2 查询中断总开关与委托(mstatus和mideleg)
      • 2.2.1 M模式
      • 2.2.2 S模式
      • 2.2.3 U模式
  • 3 处理中断
    • 3.1 获取中断编号
    • 3.2 检查委托
    • 3.3 进入中断
      • 3.3.1 配置mtvec
      • 3.3.2 配置stvec
    • 3.4 执行中断服务程序
    • 3.5 退出中断
      • 3.5.1 处理mret指令
      • 3.5.2 处理sret指令
  • 4 总结

本文属于《 TinyEMU模拟器基础系列教程》之一,欢迎查看其它文章。
本文中使用的代码,均为伪代码,删除了部分源码。

本文,以TinyEMU中M模式下的时钟中断为例,进行说明。

1 触发中断

mtimer是实现在M模式下的定时器,它位于CLINT控制器内部。
并给该计时器,定义了两个64 位宽的寄存器mtime和mtimecmp。

  • mtime,用于反映当前计时器的计数值
  • mtimecmp,用于设置计时器的比较值

当mtime 中的计数值 >= mtimecmp 中设置的比较值时,计时器便会产生时钟中断

时钟中断,会一直拉高,直到软件重新写mtimecmp 寄存器的值,使得mtimecmp值大于mtime值,从而将计时器中断清除。

在TinyEMU源码,riscv_machine.c中riscv_machine_get_sleep_duration函数,如下:

static int riscv_machine_get_sleep_duration(VirtMachine *s1, int delay)
{delay1 = m->timecmp - rtc_get_time(m);if (delay1 <= 0) {riscv_cpu_set_mip(s, MIP_MTIP);delay = 0;} else {/* convert delay to ms */delay1 = delay1 / (RTC_FREQ / 1000);if (delay1 < delay)delay = delay1;}...
}

当mtimecmp >= 当前时间时,调用riscv_cpu_set_mip函数,将0x80写入mip寄存器(即mip.MTIP=1),表示M模式下时钟中断处于等待响应状态。

2 查询中断

在riscv_cpu_template.h中,取指、译码、执行主循环处理glue函数,如下:

static void no_inline glue(riscv_cpu_interp_x, XLEN)(RISCVCPUState *s,int n_cycles1)
{for(;;) {// 获取PCs->pc = GET_PC(); // check pending interruptsraise_interrupt(s);// 取指、译码、执行...}
}

调用riscv_cpu.c中raise_interrupt函数,来处理中断,如下:

static __exception int raise_interrupt(RISCVCPUState *s)
{mask = get_pending_irq_mask(s); // 检测是否有中断或异常if (mask == 0)return 0;irq_num = ctz32(mask); // mask转为中断号或异常号raise_exception(s, irq_num | CAUSE_INTERRUPT); // 处理中断或异常return -1;
}

在处理中断前,我们需要调用get_pending_irq_mask函数,来检查是否有中断需要处理,返回非0,表示有中断待处理。
接下来,介绍get_pending_irq_mask函数的具体实现。

2.1 查询中断使能与pending状态(mie和mip)

get_pending_irq_mask函数,如下所示:

static inline uint32_t get_pending_irq_mask(RISCVCPUState *s)
{uint32_t pending_ints, enabled_ints;// part1:查询mip和mie寄存器pending_ints = s->mip & s->mie;if (pending_ints == 0)return 0; // 未发生中断...
}

mie寄存器,可使能和关闭中断(1为使能,0为关闭),如下所示:
在这里插入图片描述

  • SSIE:表示S模式下,软件中断使能位
  • MSIE:表示M模式下,软件中断使能位
  • STIE:表示S模式下,时钟中断使能位
  • MTIE:表示M模式下,时钟中断使能位
  • SEIE:表示S模式下,外部中断使能位
  • MEIE:表示M模式下,外部中断使能位

mip寄存器,可指示中断已发生(1为发生,0为未发生),如下所示:
在这里插入图片描述

  • SSIP:表示S模式下的,软件中断处于等待响应状态
  • MSIP:表示M模式下的,软件中断处于等待响应状态
  • STIP:表示S模式下的,时钟中断处于等待响应状态
  • MTIP:表示M模式下的,时钟中断处于等待响应状态
  • SEIP:表示S模式下的,外部中断处于等待响应状态
  • MEIP:表示M模式下的,外部中断处于等待响应状态

当M模式下时钟中断发生时,则:

  • mie.MTIE,必然为1;
  • mip.MTIP,必然也为1。

因此,只有当mie&mip不为0时,才表示发生了中断,需要进行中断处理。
这里代码中,pending_ints = 0x80,表明发生了M模式下时钟中断,该中断需要被处理。

2.2 查询中断总开关与委托(mstatus和mideleg)

查询委托,也是在get_pending_irq_mask函数,如下所示:

static inline uint32_t get_pending_irq_mask(RISCVCPUState *s)
{	// part2:查询mstatus和mideleg寄存器enabled_ints = 0;switch(s->priv) {case PRV_M:if (s->mstatus & MSTATUS_MIE)enabled_ints = ~s->mideleg;break;case PRV_S:enabled_ints = ~s->mideleg; // s->mideleg = 0x222,enabled_ints = 0xfffffdddif (s->mstatus & MSTATUS_SIE) // s->mstatus.sie = 1enabled_ints |= s->mideleg; // enabled_ints = 0xffffffffbreak;default:case PRV_U:enabled_ints = -1;break;}return pending_ints & enabled_ints;
}

接下来,分别介绍,各模式下的判断逻辑。

2.2.1 M模式

    case PRV_M:if (s->mstatus & MSTATUS_MIE)enabled_ints = ~s->mideleg;break;

mstatus寄存器的mie位域,表示M模式下,全局中断开关;只有打开时,才会处理中断,否则抛弃。
若当前运行,在M模式下时:

  • 若mideleg.mie关闭,则enabled_ints为0,表明在M模式下,接收到任何中断,都被抛弃。
  • 若mideleg.mie打开,表明允许处理M模式下中断,但是需排除mideleg中指定委托到S模式处理的中断,用取反操作,来屏蔽掉这些中断的bit位,并置位未委托的中断bit位。得到的enabled_ints,该值中bit位为1,对应的这些中断,就是需要在M模式下处理的。

最后,返回值为(pending_ints & enabled_ints),该值为非0时,表示在M模式下可处理的中断。

换言之,在M模式下,可处理的中断,必须满足:

  • mie中对应bit为1:表示打开xx模式yy中断开关
  • mip中对应bit为1:表示xx模式yy中断等待处理
  • mstatus.mie为1:表示打开M模式中断总开关
  • mideleg中对应bit为0:表示xx模式yy中断未委托给S模式处理

注意:
mie、mip、mideleg这三个寄存器的字段结构定义,是完全一样的,理解了这一点,有助于理解本函数,这些逻辑与或操作的含义。
在这里插入图片描述

2.2.2 S模式

    case PRV_S:enabled_ints = ~s->mideleg; // s->mideleg = 0x222,enabled_ints = 0xfffffdddif (s->mstatus & MSTATUS_SIE) // s->mstatus.sie = 1enabled_ints |= s->mideleg; // enabled_ints = 0xffffffffbreak;

mstatus寄存器的sie位域,表示S模式下,全局中断开关;只有打开时,才会处理中断,否则抛弃。
若当前运行,在S模式下时:

  • 若mideleg.sie为0,表示关闭S模式中断,因此委托到S模式的这些中断,统统不能处理,需要忽略。~s->mideleg表示只处理未委托的中断(默认在M模式处理),后续可从S陷入M,去处理这些中断。
  • 若mideleg.sie为1,表示打开S模式中断,因此委托到S模式的这些中断,可以处理;并且未委托的中断(默认在M模式处理),可通过后续从S陷入M,去处理的。这两类中断,都可以处理,因此使用enabled_ints |= s->mideleg

最后,返回值为(pending_ints & enabled_ints),该值为非0时,表示在S模式下可处理的中断。

换言之,在S模式下,可处理的中断,必须满足:

  • mie中对应bit为1:表示打开xx模式yy中断开关
  • mip中对应bit为1:表示xx模式yy中断等待处理
  • mstatus.sie
    (1) sie为0时,只能处理未委托的中断(mideleg对应bit为0),后续通过S陷入M处理。
    (2) sie为1时,可处理未委托的中断(mideleg对应bit为0),后续通过S陷入M处理;以及委托的中断(mideleg对应bit为1),就在S下直接处理。

运行在S模式下时,对于非委托中断,其默认处理方式,就是陷入M模式;因此在S模式下,对这些非委托中断,均做了放过处理,未拦截。

这里,处理M模式时钟中断时,当前运行在S模式下,所以应该走这条分支,以继续处理。

2.2.3 U模式

    case PRV_U:enabled_ints = -1; // enabled_ints = 0xffffffffbreak;

若当前运行,在U模式下时:

  • enabled_ints = 0xffffffff,处理接受所有中断。

最后,返回值为(pending_ints & enabled_ints),该值为非0时,表示在U模式下可处理的中断。

换言之,在U模式下,可处理的中断,必须满足:

  • mie中对应bit为1:表示打开xx模式yy中断开关
  • mip中对应bit为1:表示xx模式yy中断等待处理

在U模式下,仅检查上述2项条件,因为U模式本身不具备处理中断的能力,因此对于满足条件的这些中断,需要全部做放过处理。在后续,可通过检查mideleg进行委托到S处理,或者非委托陷入M模式处理。

3 处理中断

static __exception int raise_interrupt(RISCVCPUState *s)
{mask = get_pending_irq_mask(s); // 检测是否有中断或异常if (mask == 0)return 0;irq_num = ctz32(mask); // mask转为中断号或异常号raise_exception(s, irq_num | CAUSE_INTERRUPT); // 处理中断或异常return -1;
}

在调用get_pending_irq_mask函数,查询到mask为非0,下面进行中断的处理。

3.1 获取中断编号

然后,会调用ctz32函数,查询mask中,第几位为1。

static inline int ctz32(uint32_t a)
{int i;if (a == 0)return 32;for(i = 0; i < 32; i++) {if ((a >> i) & 1)return i;}return 32;
}

例如:
发生M模式时钟中断时,mask=0x80,那么irq_num=7,表示中断编号(Exception Code)为7。
那么,irq_num | CAUSE_INTERRUPT,结果为0x80000007。

3.2 检查委托

然后,会调用raise_exception函数,如下:

static void raise_exception(RISCVCPUState *s, uint32_t cause)
{raise_exception2(s, cause, 0);
}
static void raise_exception2(RISCVCPUState *s, uint32_t cause,target_ulong tval)
{BOOL deleg;target_ulong causel;// part1 : check delegif (s->priv <= PRV_S) {/* delegate the exception to the supervisor priviledge */if (cause & CAUSE_INTERRUPT)deleg = (s->mideleg >> (cause & (MAX_XLEN - 1))) & 1;elsedeleg = (s->medeleg >> cause) & 1;} else {deleg = 0;}...
}

在raise_exception2函数中,首先判断当前模式,如果<=S,即U和S模式,那么才进行委托判断,也就是说:

  • 只有在U和S模式下,发生中断时,才能委托到S模式处理;
  • 在M模式下,发生中断时,不能委托,只能在M模式处理。

这里当前为S模式,因此会进入分支。
然后,再判断cause的最高位:

  • 为1,表示中断。
  • 为0,表示异常。

其实无论是中断,还是异常,都是从cause中取出Exception Code,并判断mideleg中第Exception Code位的值deleg:
如果deleg为0,表示不委托,会在M模式下处理此中断;
如果deleg为1,表示委托,此中断会被委托到S模式处理。

这里M模式时钟中断,对应deleg为0,即mideleg.MTIP=0。
因此,此中断需要在M模式下处理

3.3 进入中断

检查委托,得到deleg值。
然后会将cause扩展为64位,以便写入寄存器中,如下:

static void raise_exception2(RISCVCPUState *s, uint32_t cause,target_ulong tval)
{...// part2 : enter interrupt// 将cause扩展为64位// 即0x80000007 => 0x8000000000000007causel = cause & 0x7fffffff;if (cause & CAUSE_INTERRUPT)causel |= (target_ulong)1 << (s->cur_xlen - 1);// 委托if (deleg) {s->scause = causel;s->sepc = s->pc;s->stval = tval;s->mstatus = (s->mstatus & ~MSTATUS_SPIE) |(((s->mstatus >> s->priv) & 1) << MSTATUS_SPIE_SHIFT);s->mstatus = (s->mstatus & ~MSTATUS_SPP) |(s->priv << MSTATUS_SPP_SHIFT);s->mstatus &= ~MSTATUS_SIE;set_priv(s, PRV_S);s->pc = s->stvec;} // 不委托else {s->mcause = causel;s->mepc = s->pc;s->mtval = tval;s->mstatus = (s->mstatus & ~MSTATUS_MPIE) |(((s->mstatus >> s->priv) & 1) << MSTATUS_MPIE_SHIFT);s->mstatus = (s->mstatus & ~MSTATUS_MPP) |(s->priv << MSTATUS_MPP_SHIFT);s->mstatus &= ~MSTATUS_MIE;set_priv(s, PRV_M);s->pc = s->mtvec;}
}

当deleg为0时,表示不委托,在M模式处理中断。
进入中断服务程序之前,需要完成以下操作:

  • 更新mcause
  • 更新mepc
  • 更新mtval
  • 更新mstatus
  • 切换到M模式
  • pc = mtvec,跳转到M模式异常处理入口地址

当deleg为1时,表示委托,在S模式处理中断。
进入中断服务程序之前,需要完成以下操作:

  • 更新scause
  • 更新sepc
  • 更新stval
  • 更新mstatus
  • 切换到S模式
  • pc = stvec,跳转到S模式异常处理入口地址

更新这些寄存器,主要是做现场保存,比如进入中断处理前的PC,模式等,以便在退出中断处理后,可以恢复到中断前的状态(具体参考RISCV规范文档)。

这里有一个问题,mtvec或stvec,到底什么时候配置的,以及指向何处?
接下来,我们来解释这个问题。

3.3.1 配置mtvec

在Bootloader初始化过程中,会执行riscv-pk\machine\mentry.S中,如下代码:

  # write mtvec and make sure it sticksla t0, trap_vector			// t0 = &trap_vectorcsrw mtvec, t0				// mtvec = t0

也就是,把trap_vector地址,写入mtvec寄存器(配置M模式,异常处理入口地址)。
mentry.S中trap_vector地址处,代码如下:
在这里插入图片描述
当为了处理中断或异常,而进入M模式时,PC会跳转到M模式异常向量表trap_vector,开始执行第一条指令csrrw sp, mscratch, sp,直到处理完毕后(当然中间可能会有一些跳转),执行最后一条指令mret,返回之前的模式。硬件在响应mret指令时,会自动将PC跳转到发生异常前的位置。

第一条与最后一条指令之间,这段代码,我们可以理解为:M模式下的异常服务程序
在Bootloader初始化时,只有先配置了mtvec,后续M模式下的异常,才能正常响应。

3.3.2 配置stvec

在进入OS阶段,Linux初始化过程中,会执行arch/riscv/kernel/head.S中,如下代码:

relocate:/* Relocate return address */li a1, PAGE_OFFSET		// a1 = PAGE_OFFSETla a0, _start			// a0 = _startsub a1, a1, a0			// a1 = a1 - a0add ra, ra, a1			// ra = ra + a1/* Point stvec to virtual address of intruction after satp write */la a0, 1f				// a0 = 1fadd a0, a0, a1			// a0 = a0 + a1csrw stvec, a0			// stvec = a0 (stvec = 1f + PAGE_OFFSET - _start)

也就是,把S模式异常处理入口地址(1f + PAGE_OFFSET - _start),写入stvec寄存器,(可参考《一篇分析RISC-V Linux汇编启动过程》,或者《内核代码分析(linux系统riscv架构)》)。

该入口地址,其实位于arch/riscv/kernel/entry.S中trap_entry地址处,代码如下:
在这里插入图片描述
直到处理完毕后(当然中间可能会有一些跳转),执行最后一条指令sret,返回之前的模式。硬件在响应sret指令时,会自动将PC跳转到发生异常前的位置。

第一条与最后一条指令之间,这段代码,我们可以理解为:S模式下的异常服务程序
在Linux初始化时,只有先配置了stvec,后续S模式下的异常,才能正常响应。

3.4 执行中断服务程序

回到TinyEMU源码上来,看看如何M模式时钟中断。
在raise_exception2函数中,进入M模式,并跳转到mtvec指向的M模式异常处理入口地址,会执行riscv-pk\machine\mentry.S中,以下关键代码:

  # Yes.  Simply clear MTIE and raise STIP.li a0, MIP_MTIP					// a0 = MIP_MTIPcsrc mie, a0						// mie &= ~a0\li a0, MIP_STIP					// a0 = MIP_STIPcsrs mip, a0						// mip |= a0...mret
  • mie.MTIP=0,关闭M模式时钟中断
  • mip.STIP=1,S模式时钟中断处于等待响应状态(中断注入)

然后,便通过mret退出,结束处理。
可以看出:

  • 中断服务程序,并没有特别处理此时钟中断,仅仅是切到M模式下,向S模式注入了一个时钟中断。
  • 类似于,实现了将M模式时钟中断,“委托”到S模式处理的效果。注入的STIP中断,与正常中断处理流程完全一致(下一轮,重新再走一遍“查询中断”=>“处理中断”,这些各个步骤)。

3.5 退出中断

由于退出中断时,固件/OS,往往会调用mret或sret指令,来恢复中断前的状态和模式。
我们看看TinyEMU,是如何响应mret和sret指令的。

3.5.1 处理mret指令

当TinyEMU执行mret指令时,会调用riscv_cpu.c中handle_mret函数,如下所示:

static void handle_mret(RISCVCPUState *s)
{int mpp, mpie;mpp = (s->mstatus >> MSTATUS_MPP_SHIFT) & 3;/* set the IE state to previous IE state */mpie = (s->mstatus >> MSTATUS_MPIE_SHIFT) & 1;s->mstatus = (s->mstatus & ~(1 << mpp)) |(mpie << mpp);/* set MPIE to 1 */s->mstatus |= MSTATUS_MPIE;/* set MPP to U */s->mstatus &= ~MSTATUS_MPP;set_priv(s, mpp);s->pc = s->mepc;
}

退出中断服务程序后,需要完成以下操作:

  • 恢复mstatus
  • M模式,切换到中断前的模式
  • pc = mepc,跳转中断前的程序PC地址

这些操作,都是做现场恢复(具体参考RISCV规范文档)。

3.5.2 处理sret指令

当TinyEMU执行sret指令时,会调用riscv_cpu.c中handle_sret函数,如下所示:

static void handle_sret(RISCVCPUState *s)
{int spp, spie;spp = (s->mstatus >> MSTATUS_SPP_SHIFT) & 1;/* set the IE state to previous IE state */spie = (s->mstatus >> MSTATUS_SPIE_SHIFT) & 1;s->mstatus = (s->mstatus & ~(1 << spp)) |(spie << spp);/* set SPIE to 1 */s->mstatus |= MSTATUS_SPIE;/* set SPP to U */s->mstatus &= ~MSTATUS_SPP;set_priv(s, spp);s->pc = s->sepc;
}

退出中断服务程序后,需要完成以下操作:

  • 恢复mstatus
  • S模式,切换到中断前的模式
  • pc = sepc,跳转中断前的程序PC地址

这些操作,都是做现场恢复(具体参考RISCV规范文档)。

4 总结

中断查询,其流程图,如下所示:
在这里插入图片描述
中断处理,其流程图,如下所示:
在这里插入图片描述

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

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

相关文章

软件设计师-基础知识科目-数据库技术基础识9

九、数据库技术基础识&#xff1a; 数据库设计阶段&#xff1a; 需求分析阶段 -> 确定系统边界。逻辑设计阶段 -> 关系规范化分。 数据库三级模式和两层映射&#xff1a; 三级模式&#xff1a; 外模式、概念模式&#xff08;也称模式&#xff09;、内模式&#xff08…

新员工入职培训时长缩短36%!智能陪练产品再升级

诸多预测认为&#xff0c;2024 年将成为国内大模型产业应用爆发的元年。中关村科金作为领先的对话式 AI 技术解决方案提供商&#xff0c;自主研发的智能陪练产品&#xff0c;以学、练、考、培一体化的方式&#xff0c;为企业提供全方位的综合培训服务。 借助大模型技术方面的突…

视频号小店新商机逐渐爆发,高门槛仍挡不住商家前进的脚步!

大家好&#xff0c;我是电商花花。 不知道大家有没有发现一件很有意思的事情&#xff0c;就是现在有越来越多的商家涌入抖音小店&#xff0c;部分商家还是想在视频号小店里博一丝机会。 我们都知道视频号小店是除了抖音小店之外&#xff0c;最火热的项目了&#xff0c;部分商…

19(20)-1(3)-CSS3 平面 2D 变换+CSS3 过渡

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍一、CSS3 平面 2D 变换&#x1f48e;1 坐标轴&#x1f48e;2 transform 语法…

KubeSphere 社区双周报|2024.03.29-04.11

KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书、新增的讲师证书以及两周内提交过 commit 的贡献者&#xff0c;并对近期重要的 PR 进行解析&#xff0c;同时还包含了线上/线下活动和布道推广等一系列社区动态。 本次双周报涵盖时间为&#xff1a;2024.03.29-04.11…

C/C++ 入门(5)内存管理

个人主页&#xff1a;仍有未知等待探索-CSDN博客 专题分栏&#xff1a;C 欢迎指教&#xff01; 目录 一、内存分布 二、C中动态内存管理 new delete 三、C语言的动态内存管理 四、operator new 和operator delete函数 operator new operator delete 五、new和delete的…

细胞世界:4.细胞分化(划区域)与细胞衰老(设施磨损)

(1)细胞凋亡 1. 概念&#xff1a;细胞凋亡可以比作城市的规划者主动拆除某些建筑来更新城市或防止危险建筑对市民的潜在伤害。这是一个有序的过程&#xff0c;由城市&#xff08;细胞内部&#xff09;的特定规划&#xff08;基因&#xff09;所决定。 2. 特征&#xff1a;细…

简述Kafka的高可靠性

什么叫可靠性&#xff1f; 大家都知道&#xff0c;系统架构有三高&#xff1a;「高性能、高并发和高可用」&#xff0c;三者的重要性不言而喻。 对于任意系统&#xff0c;想要同时满足三高都是一件非常困难的事情&#xff0c;大型业务系统或者传统中间件都会搭建复杂的架构来…

2024 年 AI代码助手AI Coding Assistant智能工具

AI代码助手&#xff08;AI Coding Assistant&#xff09;是一种利用人工智能帮助开发人员更快、更准确地编写代码的软件工具。 它可以通过根据提示生成代码或在你实时编写代码时建议自动完成代码来实现此目的。 以下是AI代码助手可以做的一些事情&#xff1a; 与你使用的流行代…

PointNet++函数square_distance(src, dst):计算两组点之间的欧式距离(代码详解)

文章目录 一、计算两组点之间的欧式距离二、举例三、中间结果输出 一、计算两组点之间的欧式距离 def square_distance(src, dst):"""Calculate Euclid distance between each two points.src^T * dst xn * xm yn * ym zn * zm&#xff1b;sum(src^2, dim-1…

STM32的位操作(相当于51单片机的sbit)

经过一段时间的学习&#xff0c;今天发现STM32的单个端口都有一个32位的地址&#xff0c;这样就可以把这个地址给找出来&#xff0c;进行单个位的操作了&#xff0c;这也没有什么好说的&#xff0c;直接复制粘贴就好了&#xff0c;用到的时候过来复制直接使用就行了。虽然看着挺…

macU盘在电脑上读不出来 u盘mac读不出来怎么办 macu盘不能写入 Tuxera NTFS for Mac免费下载

对于Mac用户来说&#xff0c;使用U盘是很常见的操作&#xff0c;但有时候可能会遇到Mac电脑无法读取U盘的情况&#xff0c;这时候就需要使用一些特定的工具软件来帮助我们解决问题。本文就来告诉大家macU盘在电脑上读不出来是怎么回事&#xff0c;u盘mac读不出来怎么办。 一、m…

解决redis乱码问题

目录 1.问题 2.查看redis序列化机制 3.设置redis的序列化器 1.问题 在使用redis最为缓存时&#xff0c;发现key乱码问题 这是由于redis的序列化机制导致的 2.查看redis序列化机制 3.设置redis的序列化器 Configuration Data public class RedisConfig {/*** redis序列化*…

蓝桥杯 2019 省A 糖果 动态规划/二进制

#include <bits/stdc.h> // 包含标准库中的所有头文件 using namespace std;int main() {int n,m,k; // 定义变量n&#xff08;糖果包数&#xff09;、m&#xff08;口味数&#xff09;、k&#xff08;每包糖果的个数&#xff09;cin>>n>>m>>k; // 输入…

探索工业AI智能摄像机的卓越性能!

​ 在当今快速发展的工业智能化领域&#xff0c;上海晶珩的工业AI智能摄像机系列以其卓越的性能和多功能性在国内外备受关注&#xff08;文末有国外工程师的评测链接&#xff09;。搭载Raspberry Pi CM4支持的ED-AIC2000和ED-AIC2100系列旨在广泛应用&#xff0c;涵盖从简单的条…

React之基础项目搭建

前言 React的生态系统非常庞大&#xff0c;拥有大量的第三方库和工具&#xff0c;如React Native&#xff08;用于构建原生移动应用&#xff09;、Next.js&#xff08;用于构建服务器渲染应用&#xff09;、Create React App&#xff08;用于快速搭建React应用的脚手架&#x…

工业数据采集平台:从起源到崛起的辉煌历程

关键词&#xff1a;工业数据采集平台, 工业数据采集, 工业数据采集分析,智能化 在当今数字化的时代&#xff0c;工业领域也在经历着深刻的变革。而工业数据采集平台的发展历程&#xff0c;正是这场变革中的重要篇章。 回首过去&#xff0c;工业数据采集曾是一个繁琐而复杂的过…

整数划分(计数类dp)-java

整数划分我们主要通过两种思路来对这道题就行解决。 文章目录 前言 一、整数划分 二、模拟完全背包 三.代码如下 1.代码如下 2.测试样例 3.代码运行结果 四、计数类dp 4.1算法思路 4.2代码如下 总结 前言 整数划分我们主要通过两种思路来对这道题就行解决。 提示&#xff1a;以…

15. 【Android教程】按钮 Button/ImageButton

在前面两章我们讲了 TextView&#xff0c;它是一个纯输出的控件&#xff1b;而 EditText 在 TextView 基础之上加入了简单的输入功能&#xff1b;今天要讲的 Button 是一个和用户互动感很强的控件&#xff0c;从今往后不再是单纯的文本展示&#xff0c;我们可以通过 TextView、…

智能汽车领域检测实验室数智化转型

随着汽车制造行业自主研发能力的提高&#xff0c;当前的汽车企业实验室不仅需要进行种类繁多的试验项目&#xff0c;同时还需要将试验过程中的试验结果、试验过程、报告文档等一系列重要数据进行统一管理。为了提升实验数据的安全性及应用性&#xff0c;青软青之通过软硬件结合…