LV.12 D18 中断处理 学习笔记
一、ARM的异常处理机制及工程代码结构
1.1异常概念
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。
1.2异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制.
1.3ARM异常源
概念:导致异常产生的事件称为异常源
ARM异常源
FIQ 快速中断请求引脚有效
IRQ 外部中断请求引脚有效
Reset 复位电平有效
Software Interrupt 执行swi指令
Data Abort 数据终止
Prefetch Abort 指令预取终止
Undefined Instruction 遇到不能处理的指令
1.4异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切 换成对应的异常模式
1.5ARM异常响应
ARM产生异常后的动作(自动完成)
1.拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>
2.修改CPSR的值
2.1.修改中断禁止位禁止相应的中断
2.2.修改模式位进入相应的异常模式
2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_<mode>
4.设置PC为相应的异常向量(异常向量表对应的地址)
1.6异常向量表
异常向量表
> 异常向量表的本质是内存中的一段代码
> 表中为每个异常源分配了四个字节的存储空间
> 遇到异常后处理器自动将PC修改为对应的地址
> 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳
转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址但可以通过配置协处理器来修改其地址
1.7异常返回
ARM异常返回的动作(自己编写)
1.将SPSR_<mode>的值复制给CPSR使处理器恢复之前的状态
2.将LR_<mode>的值复制给PC使程序跳转回被打断的地址继续执行
1.8 IRQ异常举例
注:整个过程CPSR保存的永远是当前程序运行状态,SPSR只是异常时对原来的CPSR进行备份
二、工程模板代码结构分析
common:老师写好的库函数文件,里面实现了很多功能,比如把所有的寄存器封装,比如手搓了一个printf。
interface.c: 我们自己要实现的c源文件
start: 启动文件,任何芯片一上电执行的第一个程序一定是汇编程序,要初始化栈,初始化芯片,将异常向量表基地址位置改变,打开FIQ、IRQ然后跳转到C程序。
makefile: 编译规则
map.lds: 链接脚本,这个工程模板内有很多的C文件S文件H文件,他们编译链接后只生成一个.bin文件写入开发板,哪个文件放入哪个位置,我们写好的文件在内存中的位置都由它决定
三、中断处理框架搭建
LR保存的是被打断的下一条指令的地址(指的是汇编指令,一条c语言可能会编译成很多条汇编指令)
在遇到IRQ时会跳转到以_start:为基地址偏移0x18。
然后我们在 b main 后面来写这个中断服务程序,因为b main之前的代码都是启动代码,一开始就会执行。而 irq_handler是异常处理程序,芯片刚启动时我们不希望它执行,我们希望它遇到异常的时候再执行。
但是我们不能直接在这里写,因为IRQ模式下有很多寄存器都是和USER模式共用的如果在这里写,可定会用到一些寄存器,这样就会覆盖掉寄存器中本来的内容,返回主程序就不能正确返回这个状态了
所以我们需要先压栈保护现场
这个是时候就可以写了吗,还是不能,我们来复习一下LR寄存器
R14(LR,Link Register)
链接寄存器,一般有以下两种用途:
> 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址程序需要返回时将LR的值复制到PC即可实现。
> 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回。
原理
当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址。其原理是当执行跳转指令或产生异常时,处理器内部会将PC寄存器中的值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4。
BL
当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是BL指令下一条指令的地址。
该时刻PC=N+8 LR=N+4
IRQ中断
当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址。
该时刻PC=N+12 LR=N+8
因为产生IRQ异常后自动保存到LR寄存器中的返回地址是被IRQ打断的指令下一条在下一条指令,所以需要我们人为的修复一下。
那么为什么不直接让他返回一个正确的呢,因为ARM是精简指令集,要想直接返回正确的必须要再加一个电路,这样会增加硬件成本,所以不如软件修复一条指令就解决了。
由于这是一个非叶子函数,在这段程序中可能还会有跳转,所以我们干脆把LR也压栈保护一下。
异常处理程序既可以用汇编写,也可以通过混合编程用C语言写,但前面修改LR和压栈两条指令,只能用汇编写。
四、中断处理程序编程
interface.c
#include "exynos_4412.h"//异常处理程序
void do_irq(void)
{printf("Key2 pressed\n");
}void Delay(unsigned int Time)
{while(Time--);
}int main()
{/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*//*将GPX1_1设置成中断功能*/GPX1.CON = GPX1.CON | (0xF << 4);/*设置GPX1_1中断触发方式:下降沿触发*/EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);/*使能GPX1_1的中断功能*/EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1)); /*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*//*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/ICDDCR = ICDDCR | 1;/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);/*选择CPU0来处理57号中断*/ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/CPU0.ICCICR = CPU0.ICCICR | 1;GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);while(1){/*点亮LED2*/GPX2.DAT = GPX2.DAT | (1 << 7);/*延时*/Delay(1000000);/*熄灭LED2*/GPX2.DAT = GPX2.DAT & (~(1 << 7));/*延时*/Delay(1000000);}return 0;
}
start.S
.text
.global _start
_start:/** Vector table*/ b resetb .b .b .b .b .//从异常向量表再跳转到IRQ的异常处理程序b irq_handlerb .reset:/** Set vector address in CP15 VBAR register*/ ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBAR/** Set the cpu to SVC32 mode, Disable FIQ/IRQ*/ mrs r0, cpsrbic r0, r0, #0x1forr r0, r0, #0xd3msr cpsr ,r0/** Defines access permissions for each coprocessor*/ mov r0, #0xfffffffmcr p15, 0, r0, c1, c0, 2 /** Invalidate L1 I/D */mov r0, #0 @Set up for MCRmcr p15, 0, r0, c8, c7, 0 @Invalidate TLBsmcr p15, 0, r0, c7, c5, 0 @Invalidate icache/** Set the FPEXC EN bit to enable the FPU*/ mov r3, #0x40000000fmxr FPEXC, r3/** Disable MMU stuff and caches*/mrc p15, 0, r0, c1, c0, 0bic r0, r0, #0x00002000 @Clear bits 13 (--V-)bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)orr r0, r0, #0x00001000 @Set bit 12 (---I) Icacheorr r0, r0, #0x00000002 @Set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @Set bit 11 (Z---) BTBmcr p15, 0, r0, c1, c0, 0/** Initialize stacks */
init_stack: /*svc mode stack*/msr cpsr, #0xd3ldr sp, _stack_svc_end/*undef mode stack*/msr cpsr, #0xdbldr sp, _stack_und_end/*abort mode stack*/ msr cpsr,#0xd7ldr sp,_stack_abt_end/*irq mode stack*/ msr cpsr,#0xd2ldr sp, _stack_irq_end/*fiq mode stack*/msr cpsr,#0xd1ldr sp, _stack_fiq_end/*user mode stack, enable FIQ/IRQ*/msr cpsr,#0x10ldr sp, _stack_usr_end/*Call main*/b main//IRQ的异常处理程序
irq_handler://因为产生IRQ异常后自动保存到LR中的返回地址是被IRQ打断指令的//下一条再下一条指令的地址,所以我们需要人为的去修复一下sub lr, lr, #4//因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组//所以在处理异常之前需要先将之前USER模式下寄存器的值压栈保护stmfd sp!,{r0-r12}//处理异常bl do_irq//异常返回//1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值//2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前的状态//3.将栈中LR寄存器中的值出栈给PC,实现程序的返回ldmfd sp!,{r0-r12,pc}^_stack_svc_end: .word stack_svc + 512
_stack_und_end: .word stack_und + 512
_stack_abt_end: .word stack_abt + 512
_stack_irq_end: .word stack_irq + 512
_stack_fiq_end:.word stack_fiq + 512
_stack_usr_end: .word stack_usr + 512.data
stack_svc: .space 512
stack_und:.space 512
stack_abt: .space 512
stack_irq: .space 512
stack_fiq: .space 512
stack_usr: .space 512
只按了一次按键,就会一直打印Key2 pressed。
中断挂起寄存器, EXT_INT41_PEND[1]对应GPX1_1引脚,会自动置1,置1就会把这个中断挂。当你处理完中断,返回到main函数,此时EXT_INT41_PEND[1]依旧是1,不会自动清零,所以还会给中断控制器发送中断信号,中断控制器还会将中断信号转发给CPU,还会触发中断。所以我们需要在CPU处理完中断后,把挂起位清零。
中断挂起寄存器比较特殊,写1才会清零,写0则保持不变。
我们在异常处理程序中对中断挂起寄存器的第一位进行修改,则按一次按键,只会产生一次中断。
但是有个新问题所有的IRQ异常都会跳到这里,那么我们需要区分一下,但是CPU不知道是谁发来的,所以需要询问中断控制器。
ICCIAR寄存器,后面[31:10]位与本次实验无关,我们只看[9:0]位,中断控制器把几号中断转给CPU,就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值,来写不同的中断处理程序。
#include "exynos_4412.h"//异常处理程序
void do_irq(void)
{unsigned int IrqNum = 0;/*从中断控制器中获取当前中断的中断号*/IrqNum = CPU0.ICCIAR & 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 57:printf("Key2 pressed\n");/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND = (1 << 1);break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}int main()
{/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*//*将GPX1_1设置成中断功能*/GPX1.CON = GPX1.CON | (0xF << 4);/*设置GPX1_1中断触发方式:下降沿触发*/EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);/*使能GPX1_1的中断功能*/EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1)); /*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*//*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/ICDDCR = ICDDCR | 1;/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);/*选择CPU0来处理57号中断*/ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/CPU0.ICCICR = CPU0.ICCICR | 1;GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);while(1){/*点亮LED2*/GPX2.DAT = GPX2.DAT | (1 << 7);/*延时*/Delay(1000000);/*熄灭LED2*/GPX2.DAT = GPX2.DAT & (~(1 << 7));/*延时*/Delay(1000000);}return 0;
}
此时,我们又发现按键只有第一次有效,之后再按按键就没有反应。
因为中断控制器不知道CPU0已经处理完中断处理程序了,所以并没有将新的中断信号发送给CPU0。
ICCEOIR寄存器,本次实验只看[9:0]位,CPU处理完中断后,会把中断号写入该寄存器。
将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断
#include "exynos_4412.h"//异常处理程序
void do_irq(void)
{unsigned int IrqNum = 0;/*从中断控制器中获取当前中断的中断号*/IrqNum = CPU0.ICCIAR & 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 57:printf("Key2 pressed\n");/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND = (1 << 1);/*将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断*/CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 57;break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}int main()
{/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*//*将GPX1_1设置成中断功能*/GPX1.CON = GPX1.CON | (0xF << 4);/*设置GPX1_1中断触发方式:下降沿触发*/EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);/*使能GPX1_1的中断功能*/EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1)); /*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*//*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/ICDDCR = ICDDCR | 1;/*在中断控制器中使能57号中断,使中断控制器在接收到57号中断后,能将其进一步转发到CPU接口*/ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);/*选择CPU0来处理57号中断*/ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x1 << 8);/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/CPU0.ICCICR = CPU0.ICCICR | 1;GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);while(1){/*点亮LED2*/GPX2.DAT = GPX2.DAT | (1 << 7);/*延时*/Delay(1000000);/*熄灭LED2*/GPX2.DAT = GPX2.DAT & (~(1 << 7));/*延时*/Delay(1000000);}return 0;
}
这次代码我们需要实现按一次按键就产生一次中断,但个人原因,没有实现,也没找出问题,后续再解决。
五、中断编程补充
中断和轮询:轮询是CPU主动去查看硬件有没有异常产生,而中断是硬件主动通知CPU。中断的效率更高一些,用的较多。
真正开发时有操作系统,我们其实只需要写中断程序,然后打开对应中断就可以
FIQ为什么比IRQ快:
1)FIQ的优先级比IRQ高
2)FIQ可以打断IRQ
3)FIQ在异常向量表的最末,别的中断处理程序需要跳转,而FIQ可以直接往后写
4)FIQ有四组直接独有的寄存器,如果只需要r8-r12的话,他不需要压栈保护现场。但是如果用到了r0-r7还是要压栈保护现场的。
作业
1.使用中断的方式检测Key3按键的状态,实现按一次按键,LED2点亮,再次按下,LED2熄灭
EINT[10]的中断号是58。
本次实验依旧采用下降沿触发方式,GPX1_2对应EXT_INT41_CON[2]。
EXT_INT41_MASK[1]对应GPX1_1的开和关。0x0打开中断,0x1关闭中断。我们把它打开。
我们把ICDDCR寄存器写1 ,监控所有的外部中断,并将挂起的中断转发到CPU的接口
ICCDCR寄存器相当于GIC(中断控制器)的总开关。
ICDISER_CPU寄存器的作用:寄存器接收到中断信号,通过配置该寄存器对应的位,控制该中断信号发送或不发送给CPU。
下面这个寄存器是ICDIPTR_CPU,它的作用是为每一个中断选择处理他的CPU。
哪一位置1,中断信号就发给哪个CPU处理。但4412是一个四核的CPU,所以高四位是没有用的。
一共需要40个寄存器来管理这160个中断归属于哪一个CPU处理
想把58号中断交给CPU0处理,则把偏移地址为0x838的寄存器的[23:16]写为00000001即可。
ICCICR_CPUn寄存器是中断控制器和CPU之间的接口,他就像一个开关,用哪个CPU就要打开哪个。
EXT_INT41_PEND[2]对应GPX1_2引脚,会自动置1,置1就会把这个中断挂。当你处理完中断,返回到main函数,此时EXT_INT41_PEND[2]依旧是1,不会自动清零,所以还会给中断控制器发送中断信号,中断控制器还会将中断信号转发给CPU,还会触发中断。所以我们需要在CPU处理完中断后,把挂起位清零。
中断挂起寄存器比较特殊,写1才会清零,写0则保持不变。
ICCIAR寄存器,后面[31:10]位与本次实验无关,我们只看[9:0]位,中断控制器把几号中断转给CPU,就会往这个寄存器的[9:0]位写几。所以我们可以让CPU在处理中断之前先读取这个寄存器的值,来写不同的中断处理程序。
ICCEOIR寄存器,本次实验只看[9:0]位,CPU处理完中断后,会把中断号写入该寄存器。
将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断
#include "exynos_4412.h"unsigned int flag = 1;//异常处理程序
void do_irq(void)
{unsigned int IrqNum;/*从中断控制器中获取当前中断的中断号*/IrqNum = CPU0.ICCIAR & 0x3FF;switch(IrqNum){case 0://0号中断的处理程序break;case 1://1号中断的处理程序break;/** ......*/case 58:printf("Key3 pressed\n");if(flag == 1){GPX2.DAT = GPX2.DAT | (1 << 7);flag = 0;}else{GPX2.DAT = GPX2.DAT & (~(1 << 7));flag = 1;}/*清除GPIO控制器中的中断挂起位*/EXT_INT41_PEND = (1 << 2);/*将当前中断的中断号写回到中断控制器,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其他中断*/CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | 58;break;/** ......*/case 159://159号中断的处理程序break;default:break;}}void Delay(unsigned int Time)
{while(Time--);
}int main()
{/*外设层次 —— 让外部的硬件控制器产生一个中断信号并发送给中断控制器*//*将GPX1_2设置成中断功能*/GPX1.CON = GPX1.CON | (0xF << 8);/*设置GPX1_2中断触发方式:下降沿触发*/EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 8)) | (0x2 << 8);/*使能GPX1_2的中断功能*/EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 2)); /*中断控制器层次 —— 让中断控制器接收外设发来的中断信号并对其进行管理然后再转发给一个合适的CPU去处理*//*全局使能中断控制器,使其能够接收外部设备产生的中断信号并转发给CPU接口*/ICDDCR = ICDDCR | 1;/*在中断控制器中使能58号中断,使中断控制器在接收到58号中断后,能将其进一步转发到CPU接口*/ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 26);/*选择CPU0来处理58号中断*/ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 16)) | (0x1 << 16);/*将中断控制器和CPU0之间的接口使能,使得中断控制器转发的信号能够到达CPU0*/CPU0.ICCICR = CPU0.ICCICR | 1;GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);//LED2while(1){/*点亮LED2*/GPX2.DAT = GPX2.DAT | (1 << 7);/*延时*/Delay(1000000);/*熄灭LED2*/GPX2.DAT = GPX2.DAT & (~(1 << 7));/*延时*/Delay(1000000);}return 0;
}
相关文章:

LV.12 D18 中断处理 学习笔记
一、ARM的异常处理机制及工程代码结构 1.1异常概念 处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序。 1.2异常处理机制 不同的处…...

蓝桥杯每日一题2023.11.19
题目描述 “蓝桥杯”练习系统 (lanqiao.cn) 题目分析 首先想到的方法为dfs去寻找每一个数,但发现会有超时 #include<bits/stdc.h> using namespace std; const int N 2e5 10; int n, cnt, a[N]; void dfs(int dep, int sum, int start) {if(dep 4){if(s…...
<b><strong>,<i><em>标签的区别
1. b标签和strong标签 b标签:仅仅是UI层面的加粗样式,并不具备HTML语义 strong标签:不仅是在UI层面的加粗样式,具备HTML语义,表示强调 2. i标签和em标签 i 标签:仅仅是UI层面的斜体样式,并不具备…...
c++中的特殊类设计
文章目录 1.请设计一个类,不能被拷贝2. 请设计一个类,只能在堆上创建对象3. 请设计一个类,只能在栈上创建对象4. 请设计一个类,不能被继承5. 请设计一个类,只能创建一个对象(单例模式) 1.请设计一个类,不能…...

开源更安全? yum源配置/rpm 什么是SSH?
文章目录 1.开放源码有利于系统安全2.yum源配置,这一篇就够了!(包括本地,网络,本地共享yum源)3.rpm包是什么4.SSH是什么意思?有什么功能? 1.开放源码有利于系统安全 开放源码有利于系统安全 2.yum源配置…...

庖丁解牛:NIO核心概念与机制详解 04 _ 分散和聚集
文章目录 Pre概述分散/聚集 I/O分散/聚集的应用聚集写入Code Pre 庖丁解牛:NIO核心概念与机制详解 01 庖丁解牛:NIO核心概念与机制详解 02 _ 缓冲区的细节实现 庖丁解牛:NIO核心概念与机制详解 03 _ 缓冲区分配、包装和分片 概述 分散/聚…...

Java读写Jar
Java提供了读写jar的类库Java.util.jar,Java获取解析jar包的工具类如下: import java.io.File; import java.io.IOException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import …...
【四元数简述】
w cos(theta/2) x ax * sin(theta/2) y ay * sin(theta/2) z az * sin(theta/2) 向量(x,y,z)是旋转轴 a 是任意正数 theta是旋转角度。 上面就是一个四元数表示旋转。 如何使用 空间中向量(1,2,3)扩展为(0,1,2,3&#…...
ClickHouse SQL 查询优化
1 单表查询 1.1 Prewhere替代where Prewhere和where语句的作用相同,用来过滤数据。不同之处在于prewhere只支持 *MergeTree 族系列引擎的表,首先会读取指定的列数据,来判断数据过滤,等待数据过滤之后再读取select 声明的列字段来补…...

「Verilog学习笔记」数据选择器实现逻辑电路
专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点,刷题网站用的是牛客网 分析 将变量A、B接入4选1数据选择器选择输入端S0 S1。将变量C分配在数据输入端。从表中可以看出输出L与变量C的关系。 当AB00时选通D0而此时L0,所以数据端D0接0…...

【Go入门】Web工作方式
【Go入门】 Web工作方式 我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢? 对于普通的上网过程,系统其实是这样做的&…...

综述:目标检测二十年(机翻版)(未完
原文地址 20年来的目标检测:一项调查 摘要关键词一 介绍二 目标检测二十年A.一个目标检测的路线图1)里程碑:传统探测器Viola Jones探测器HOG检测器基于可变形零件的模型(DPM) 2)里程碑:基于CNN的两阶段探测器RCNNSPPN…...

quinn源码解析:QUIC数据包是如何发送的
quinn源码解析:QUIC数据包是如何发送的 简介QUIC协议中的概念endpoint(端点)connection(连接)Stream(流)Frame (帧) 发包过程解析SendStream::write_allConnectionDriverEndpointDriver 简介 q…...

scss的高级用法——循环
周末愉快呀!一起来学一点简单但非常有用的css小知识。 最近在一个项目中看到以下css class写法: 了解过tailwind css或者unocss的都知道,从命名就可以看出有以下样式: font-size: 30pxmargin-left: 5px;margin-top: 10px; 于是…...
Linux安装Chrome浏览器 -linux安装choeme
Linux 操作系统一般自带的浏览器是 FireFox,不过有些用户可能更喜欢 Google 出品的 Chrome 浏览器。本教程将介绍如何在 Linux 系统上安装 Chrome 浏览器,以及可能会遇到的一些问题解决方案。 下载 Chrome 安装包 需要下载 Chrome 的安装包。可以在 Go…...

六大排序(插入排序、希尔排序、冒泡排序、选择排序、堆排序、快速排序)未完
文章目录 排序一、 排序的概念1.排序:2.稳定性:3.内部排序:4.外部排序: 二、插入排序1.直接插入排序2.希尔排序 三、选择排序1.直接选择排序方法一方法二直接插入排序和直接排序的区别 2.堆排序 四、交换排序1.冒泡排序2.快速排序…...

JVM垃圾回收相关概念
目录 一、System.gc()的理解 二、内存溢出与内存泄露 (一)OOM (二)内存泄露 三、StopTheWorld 四、垃圾回收的并行与并发 五、安全点与安全区域 (一)安全点 (二)安全区域 …...

C++各种字符转换
C各种字符转换 一.如何将char数组转化为string类型二. string转char数组:参考 一.如何将char数组转化为string类型 在C中,可以使用string的构造函数或者赋值操作符来将char数组转换为string类型。 方法1:使用string的构造函数 const char* c…...
MSSQL-逻辑级常用命令
--SQL Server 查询表的记录数 --one: 使用系统表. SELECT object_name (i.id) TableName, rows as RowCnt FROM sysindexes i INNER JOIN sysObjects o ON (o.id i.id AND o.xType U ) WHERE indid < 2 ORDER BY rows desc ————————————…...
【如何学习Python自动化测试】—— 时间等待
3 、 时间等待 在做自动化测试时,难免会碰到一些问题,比如你在脚本中操作某个对象时, 页面还没有加载出来,你的操作语句已经被执行,从而导致脚本执行失败,针对这样的问题 webdriver 提供了等待操作…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

selenium学习实战【Python爬虫】
selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...

微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...

(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...