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(;;) {
		// 获取PC
		s->pc = GET_PC(); 

		// check pending interrupts
		raise_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 = 0xfffffddd
        if (s->mstatus & MSTATUS_SIE) // s->mstatus.sie = 1
            enabled_ints |= s->mideleg; // enabled_ints = 0xffffffff
        break;
    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 = 0xfffffddd
        if (s->mstatus & MSTATUS_SIE) // s->mstatus.sie = 1
            enabled_ints |= s->mideleg; // enabled_ints = 0xffffffff
        break;

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 = 0xffffffff
        break;

若当前运行,在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 deleg
    if (s->priv <= PRV_S) {
        /* delegate the exception to the supervisor priviledge */
        if (cause & CAUSE_INTERRUPT)
            deleg = (s->mideleg >> (cause & (MAX_XLEN - 1))) & 1;
        else
            deleg = (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 => 0x8000000000000007
    causel = 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 sticks
  la t0, trap_vector			// t0 = &trap_vector
  csrw 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_OFFSET
	la a0, _start			// a0 = _start
	sub a1, a1, a0			// a1 = a1 - a0
	add ra, ra, a1			// ra = ra + a1

	/* Point stvec to virtual address of intruction after satp write */
	la a0, 1f				// a0 = 1f
	add a0, a0, a1			// a0 = a0 + a1
	csrw 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_MTIP
  csrc mie, a0						// mie &= ~a0\
  li a0, MIP_STIP					// a0 = MIP_STIP
  csrs 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.mfbz.cn/a/537807.html

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

相关文章

【教程】7代核显直通HDMI成功输出 PVE下玩AIO最有性价比的机器

大家好&#xff0c;我是村雨Mura&#xff0c;好久没写教程了&#xff0c;本期是7代核显直通&#xff0c;重点在于HDMI输出画面 本教程理论上适用于4代以后intel带核显CPU&#xff0c;如果你有直通成功经验欢迎评论区分享 前面有点啰嗦&#xff0c;想直接看教程&#xff0c;直…

【每日练习】二叉树

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;二叉树 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 文章目录 一、100. 相同的树1. 题目简介2.…

Go语言中channel和互斥锁的应用场景

面对一个并发问题,我们的解决方案是使用channel还是互斥锁来实现并不总是很清晰。因为Go提倡使用通信来共享内存,所以一个常见的错误就是总是强制使用channel,不管实际情况如何。但是我们应该把这两种选择作为互补手段。 首先,简单回顾一下Go语言中的channel:channel是一种交…

同步检查继电器DT-13/200额定电压100V柜内安装板前接线JOSEF约瑟

系列型号 DT-13/200同步检查继电器; DT-13/160同步检查继电器; DT-13/130同步检查继电器; DT-13/120同步检查继电器; DT-13/90同步检查继电器; DT-13/254同步检查继电器; 同步检查继电器DT-13/200 用途 DT-13型同步检查继电器用于两端供电线路的自动重合闸线路中&…

2024 年第十四届 Mathorcup 数学应用挑战赛题目C 题 物流网络分拣中心货量预测及人员排班完整思路以及源代码分享,仅供学习

电商物流网络在订单履约中由多个环节组成&#xff0c;图1是一个简化的物流网络示意图。其中&#xff0c;分拣中心作为网络的中间环节&#xff0c;需要将包裹按照不同流向进行分拣并发往下一个场地&#xff0c;最终使包赛到达消费者手中。分拣中心管理效率的提升&#xff0c;对整…

物联网SaaS平台

在信息化、智能化浪潮席卷全球的今天&#xff0c;物联网SaaS平台作为推动工业数字化转型的重要工具&#xff0c;正日益受到广泛关注。那么&#xff0c;物联网SaaS平台究竟是什么&#xff1f;HiWoo Cloud作为物联网SaaS平台又有哪些独特优势&#xff1f;更重要的是&#xff0c;它…

使用unicloud-map 无法展示poi的天坑

天坑&#xff01;天坑&#xff01;天坑 使用unicloud-map的天坑 202404121722&#xff0c;昨天晚上发现uni-admin中导入了unicloud-map管理端之后在chrome浏览器由于地图定位失败&#xff0c;一直没有办法新增poi,不过后面发现safari浏览器是可以定位出来的&#xff0c;所以今…

上网行为管理软件怎么选择?哪个软件好?| 三款热门行为审计软件分享

上网行为监控系统是一种用于监控和管理互联网使用行为的系统。 这种系统主要用于企业和学校等机构&#xff0c;以控制和管理员工或学生在工作时间或学习时间内对互联网的使用。 而现在的企业越来越信息化&#xff0c;随之而来的信息危机也丛生不断&#xff0c;企业管理软件也…

VXWorks6.9 + Workbench3.3 Simulation 代码调试

VxWorks系列传送门 本章是基于前一篇《VXWorks6.9 Workbench3.3 开发环境部署》来进行讲解的&#xff0c;在上一篇我们创建了一个Hello World 的项目&#xff0c;并将编译后的可执行文件放到了VxWorks - FTP共享文件目录下&#xff0c;顺利的在VxWin 系统中跑起来。 本篇着重讲…

【学习】Spring IoCDI

&#x1f3a5; 个人主页&#xff1a;Dikz12&#x1f4d5;格言&#xff1a;吾愚多不敏&#xff0c;而愿加学欢迎大家&#x1f44d;点赞✍评论⭐收藏 目录 Spring 是什么&#xff1f; 什么是 IoC容器&#xff1f; 传统开发模式 loC开发模式 IoC的优势 IoC 的使用 Bean的…

TMS320F280049 EPWM模块--ET子模块(7)

下图是ET子模块在EPWM中的位置。可以看到ET子模块相对较独立。接收多种信号&#xff0c;处理后传递给PIE和ADC。 下图是ET的内部框图&#xff0c;可以更具体的看到输入和输出信号。 ET内部也可以软件force产生事件信号。ET输出时可以做分频&#xff0c;也就是接收n次输入后才输…

外贸开发信必知技巧:高回复率不再是梦

外贸行业在Zoho的客户群体中占比较高。因为我们的国际化背景、丰富的产品组合、多语言多币种跨时区、高性价比等特点&#xff0c;成为外贸企业开展业务的选择。在和外贸客户沟通中&#xff0c;发现无论是外贸大拿还是新手小白&#xff0c;大家遇到一个共同的问题——发出去的开…

5252DG 外场通信测试仪范围:9kHz~6.3GHz/9GHz/20GHz

5252DG 外场通信测试仪 率范围&#xff1a;9kHz~6.3GHz/9GHz/20GHz 简述 5252DG外场通信测试仪是集合高性能信号分析模块、多制式解析算法软件于一体的手持式测试仪表&#xff0c;是为满足运营商对移动通信的测试而推出的全新平台。其拥有更高测试频率、更大解析带宽、更快扫…

单例19c RMAN数据迁移方案

一、环境说明 源库 目标库 IP 192.168.37.200 192.168.37.202 系统版本 RedHat 7.9 RedHat 7.9 数据库版本 19.3.0.0.0 19.3.0.0.0 SID beg beg hostname beg rman 数据量 1353M 说明:源库已经创建数据库实例&#xff0c;并且存在用户kk和他创建的表空间…

内存地产风云录:malloc、free、calloc、realloc演绎动态内存世界的楼盘开发与交易大戏

欢迎来到白刘的领域 Miracle_86.-CSDN博客 系列专栏 C语言知识 先赞后看&#xff0c;已成习惯 创作不易&#xff0c;多多支持&#xff01; 在这个波澜壮阔的内存地产世界中&#xff0c;malloc、free、calloc和realloc四位主角&#xff0c;共同演绎着一场场精彩绝伦的楼盘开…

绿联 安装SeaTable在线协同表格

绿联 安装SeaTable在线协同表格 SeaTable 是一款以在线协同表格为基础的新型企业数字化平台。它支持“文件”、“图片”、“单选项”、“协作人”、“计算公式”等丰富的数据类型&#xff0c;帮助你用表格的形式来方便的组织和管理各类信息。在表格基础上&#xff0c;它支持自…

MySQL相关问题快问快答

我写这篇文章的目的只有一个&#xff1a;通过这些问题来帮助我去将我脑子里的MySQL脑图给巩固熟悉&#xff0c;通过回答这些问题&#xff0c;让我对脑子里的MySQL知识有更深的印象&#xff0c;当什么时候我的MySQL脑图不熟的时候&#xff0c;我就可以拿这篇文章来去巩固一下&am…

构建第一个ArkTS之基本语法概述

在初步了解了ArkTS语言之后&#xff0c;我们以一个具体的示例来说明ArkTS的基本组成。如下图所示&#xff0c;当开发者点击按钮时&#xff0c;文本内容从“Hello World”变为“Hello ArkUI”。 图1 示例效果图 本示例中&#xff0c;ArkTS的基本组成如下所示。 图2 ArkTS的基本…

[大模型]Qwen1.5-4B-Chat WebDemo 部署

Qwen1.5-4B-Chat WebDemo 部署 Qwen1.5 介绍 Qwen1.5 是 Qwen2 的测试版&#xff0c;Qwen1.5 是基于 transformer 的 decoder-only 语言模型&#xff0c;已在大量数据上进行了预训练。与之前发布的 Qwen 相比&#xff0c;Qwen1.5 的改进包括 6 种模型大小&#xff0c;包括 0.…

回溯算法2s总结

8.回溯算法 回溯算法理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。回溯是递归的副产品&#xff0c;只要有递归就会有回溯。 回溯的本质是穷举&#xff0c;穷举所有可能&#xff0c;然后选出我们想要的答案 回溯法解决的问题 回溯法&#xff0c;一…
最新文章