TinyEMU源码分析之中断处理

news2024/11/24 17:57:08

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.coloradmin.cn/o/1588860.html

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

相关文章

【图像处理】-小议YUV色彩空间-YUV和RGB格式的来由,相互关系以及转换方式,并对编程实现的YUV转为RGB程序进行介绍

小议YUV色彩空间 摘要: 在视频图像处理等相关相关领域&#xff0c;YUV是一个经常出现的格式。本文主要以图解的资料形式详细描述YUV和RGB格式的来由&#xff0c;相互关系以及转换方式&#xff0c;并对编程实现的YUV转为RGB程序进行介绍。 1 引言 自然界的颜色千变万化&#xff…

AI算力报告:算力大时代,AI算力产业链全景梳理

今天分享的是AI算力专题系列深度研究报告&#xff1a;《算力大时代&#xff0c;AI算力产业链全景梳理》。 &#xff08;报告出品方&#xff1a;中信建投证券&#xff09; 报告共计&#xff1a;98页 核心观点 生成式 AI取得突破&#xff0c;我们对生成式 A 带来的算力需求做…

AI预测体彩排3第1弹【2024年4月12日预测--第1套算法开始计算第1次测试】

前面经过多个模型几十次对福彩3D的预测&#xff0c;积累了一定的经验&#xff0c;摸索了一些稳定的规律&#xff0c;有很多彩友让我也出一下排列3的预测结果&#xff0c;我认为目前时机已成熟&#xff0c;且由于福彩3D和体彩排列3的玩法完全一样&#xff0c;我认为3D的规律和模…

6.基础乐理-升降号、黑键的音名

首先需要先了解音乐中的两个符号&#xff0c;升号和降号&#xff0c;升号的符号像#&#xff0c;降号的符号像b&#xff0c;如下图&#xff1a; 升号表示升高到相邻的音&#xff0c;降号表示降低到相邻的音&#xff0c;现在首先要知道音是有高低的&#xff0c;在钢琴键盘上从左到…

ZGC的介绍

背景 在jdk17中已经将ZGC从实验性产品升级到正式产品功能&#xff0c;达到亚毫秒级停顿&#xff0c;毫不留情地将parallel和G1拉开了数量级的差别&#xff0c;无论是平均停顿还是最大停顿时间都能毫不费劲地控制在10ms内。 《深入理解Java虚拟机》在书中这样定义&#xff1a;Z…

String类(2)

❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; hellohello~&#xff0c;大家好&#x1f495;&#x1f495;&#xff0c;这里是E绵绵呀✋✋ &#xff0c;如果觉得这篇文章还不错的话还请点赞❤️❤️收藏&#x1f49e; &#x1f49e; 关注&#x1f4a5;&…

【日常记录】【JS】使用Number.prototype.toLocaleString 对 数字做 格式化

文章目录 1、引言2、语法参数3、常见案例4、参考链接 1、引言 在目前的项目中&#xff0c;经常需要给数字做格式化处理&#xff0c;特别是财务方面&#xff0c;比如分割成千分位&#xff0c;保留小数&#xff0c;增加符号等 &#xff0c;这些都需要我们手写一些函数来处理。 但…

Vue.js组件精讲 第2章 基础:Vue.js组件的三个API:prop、event、slot

如果您已经对 Vue.js 组件的基础用法了如指掌&#xff0c;可以跳过本小节&#xff0c;不过当做复习稍读一下也无妨。 组件的构成 一个再复杂的组件&#xff0c;都是由三部分组成的&#xff1a;prop、event、slot&#xff0c;它们构成了 Vue.js 组件的 API。如果你开发的是一个…

TechTool Pro for Mac v19.0.3中文激活版 硬件监测和系统维护工具

TechTool Pro for Mac是一款专为Mac用户设计的强大系统维护和故障排除工具。它凭借全面的功能、高效的性能以及友好的操作界面&#xff0c;赢得了广大用户的信赖和好评。 软件下载&#xff1a;TechTool Pro for Mac v19.0.3中文激活版 作为一款专业的磁盘和系统维护工具&#x…

大数据入门之如何利用Phoenix访问Hbase

在大数据的世界里&#xff0c;HBase和Phoenix可谓是一对黄金搭档。HBase以其高效的列式存储和强大的数据扩展能力&#xff0c;成为大数据存储领域的佼佼者&#xff1b;而Phoenix则以其SQL化的操作方式&#xff0c;简化了对HBase的访问过程。今天&#xff0c;就让我们一起看看如…

哲学家带你实现单链表

最近本哲♂学家学习了链表这一新的数据结构&#xff0c;接下来由我带领大家实现链表&#xff1a; 一 、头文件 注&#xff1a;本写法是无头的单链表&#xff0c;所以传参为二级指针。 我们事先写好所要完成的函数&#xff0c;在 .c文件中进一步去完成。 typedef int SLTData…

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

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

云原生数据库海山(He3DB)PostgreSQL版核心设计理念

本期深入解析云原生数据库海山PostgreSQL版&#xff08;以下简称“He3DB”&#xff09;的设计理念&#xff0c;探讨在设计云原生数据库过程中遇到的工程挑战&#xff0c;并展示He3DB如何有效地解决这些问题。 He3DB是移动云受到 Amazon Aurora 论文启发而独立自主设计的云原生数…

【学习】Spring IoCDI

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

策略为王股票软件源代码-----如何修改为自己软件04

上面是如何打开-------类---------函数 1. 数据结构 1) 股票数据结构的定义在头文件Src\StkLib\Include\Stock.h中,主要的几个结构定义为: KDATA K线数据结构 DRDATA 除权数据结构 REPORT 交易所在交易时间内不断发送的报价信息 MINUTE 分钟成交…

最大连续1的个数 III

题目链接 最大连续1的个数 III 题目描述 注意点 nums[i] 不是 0 就是 10 < k < nums.length 解答思路 创建一个滑动窗口&#xff0c;保证窗口内翻转0的个数始终不大于k&#xff0c;不断移动窗口的右边界&#xff0c;有以下三种情况&#xff1a; 当右边界的值为1&…

Java基础第十课——类与对象(1)

前面二白的九讲属于Java基础方面的内容&#xff0c;总体来说偏基础和简单&#xff0c;能完成的操作也有限&#xff0c;有兴趣的同学可以写一写相关的管理系统&#xff0c;后面二白也会上传一些自己敲的小系统&#xff0c;下面就要开始Java面对对象的知识内容了&#xff0c;从这…

【年度典型案例】扫码就能领补贴?通知社保在线速办?当心是钓鱼骗局!

随着我们生活的数字化程度越来越高&#xff0c;完成各种业务和服务变得前所未有的便捷。只需轻轻一点手机屏幕&#xff0c;我们办事儿变得飞快又方便。然而&#xff0c;正当我们享受这种数字化带来的便捷时&#xff0c;一些不法分子也在暗中伺机而动&#xff0c;利用各种手段制…

c# refc# substring c# 反射c# split c# websocket c# datatable使用

在C#编程中&#xff0c;ref关键字、Substring方法、反射&#xff08;Reflection&#xff09;、Split方法、WebSocket通信以及DataTable的使用都是常见的技术和方法。下面我将逐一为您详解这些内容。 1. C# ref关键字 ref关键字在C#中用于按引用传递参数。这意味着当您将变量作…

PC-lint 学习之配置方法

1. 下载PC-lint 9.0后&#xff0c;点击pclint9setup.exe进行安装&#xff08;我只安装了C/C语言&#xff0c;其他语言可安装时选择&#xff09; 2.安装完成后&#xff0c;打开keil5&#xff0c;选择配置 3. 配置选项 &#xff08;1&#xff09;Lint Executable&#xff1a;在第…