嵌入式Linux裸机开发(五)中断管理

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • STM32 中断系统
  • IMX6U中断控制
    • 8个中断
    • GIC中断控制器
      • GIC介绍
      • 中断ID
      • GIC逻辑分块
      • GIC协处理器
    • 中断使能
    • 中断优先级
  • 重点代码分析
    • 官方SDK函数
    • start.S文件
    • 自行编写中断驱动文件


前言

最近在学习中发现,学Linux嵌入式不仅是对Linux的学习熟悉,而且还是对Cortex-A内核的学习掌握
没怎么看懂,内容太多了,我觉得暂时先搞清楚一些原理概念以及简单的外部函数接口就行,内部可能在后续的学习中进行钻研


STM32 中断系统

STM32 的中断系统主要有以下几个关键点:
①、 中断向量表。
②、 NVIC(内嵌向量中断控制器)。
③、 中断使能。
④、 中断服务函数。

中断向量表是一个表,这个表里面存放的是中断向量。中断向量表是一系列中断服务程序入口地址组成的表。
由半导体厂商定好的,当某个中断被触发以后就会自动跳转到中断向量表中对应的中断服务程序(函数)入口地址处。
中断向量表在整个程序的最前面,如 STM32F103 :

__Vectors       DCD     __initial_sp               ; Top of StackDCD     Reset_Handler              ; Reset HandlerDCD     NMI_Handler                ; NMI HandlerDCD     HardFault_Handler          ; Hard Fault HandlerDCD     MemManage_Handler          ; MPU Fault HandlerDCD     BusFault_Handler           ; Bus Fault HandlerDCD     UsageFault_Handler         ; Usage Fault HandlerDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SVC_Handler                ; SVCall HandlerDCD     DebugMon_Handler           ; Debug Monitor HandlerDCD     0                          ; ReservedDCD     PendSV_Handler             ; PendSV HandlerDCD     SysTick_Handler            ; SysTick Handler; External InterruptsDCD     WWDG_IRQHandler            ; Window WatchdogDCD     PVD_IRQHandler             ; PVD through EXTI Line detectDCD     TAMPER_IRQHandler          ; TamperDCD     RTC_IRQHandler             ; RTCDCD     FLASH_IRQHandler           ; FlashDCD     RCC_IRQHandler             ; RCCDCD     EXTI0_IRQHandler           ; EXTI Line 0DCD     EXTI1_IRQHandler           ; EXTI Line 1DCD     EXTI2_IRQHandler           ; EXTI Line 2DCD     EXTI3_IRQHandler           ; EXTI Line 3DCD     EXTI4_IRQHandler           ; EXTI Line 4DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7DCD     ADC1_2_IRQHandler          ; ADC1_2DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TXDCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1DCD     CAN1_SCE_IRQHandler        ; CAN1 SCEDCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5DCD     TIM1_BRK_IRQHandler        ; TIM1 BreakDCD     TIM1_UP_IRQHandler         ; TIM1 UpdateDCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and CommutationDCD     TIM1_CC_IRQHandler         ; TIM1 Capture CompareDCD     TIM2_IRQHandler            ; TIM2DCD     TIM3_IRQHandler            ; TIM3DCD     0                          ; ReservedDCD     I2C1_EV_IRQHandler         ; I2C1 EventDCD     I2C1_ER_IRQHandler         ; I2C1 ErrorDCD     0                          ; ReservedDCD     0                          ; ReservedDCD     SPI1_IRQHandler            ; SPI1DCD     0                          ; ReservedDCD     USART1_IRQHandler          ; USART1DCD     USART2_IRQHandler          ; USART2DCD     0                          ; ReservedDCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10DCD     RTC_Alarm_IRQHandler        ; RTC Alarm through EXTI LineDCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
__Vectors_End

中断向量表都是链接到代码的最前面,比如一般 ARM 处理器都是从地址 0X00000000 开始执行指令的,那么中断向量表就是从 0X00000000 开始存放的。

“__initial_sp”就是第一条中断向量,存放的是栈顶指针
复位中断复位函数 Reset_Handler 的入口地址

ARM 处理器都是从地址 0X00000000 开始运行,但是我们是下载到 0X8000000 开始的存储区域中,Cortex-M 架构引入了中断向量表偏移,中断向量表偏移配置在函数 SystemInit 中完成,通过向 SCB_VTOR 寄存器写入新的中断向量表首地址即可。

void SystemInit (void)
{RCC->CR |= (uint32_t)0x00000001;/* 省略其它代码 */#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;     //将中断向量表设置到 RAM 中#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;     //将中断向量表设置到 ROM 中#endif
}#define FLASH_BASE ((uint32_t)0x08000000)

对于Cortex-M来说:
中断使能:

NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);

中断服务函数:
要处理的工作就可以放到中断服务函数中去完成

/* 外部中断 2 服务程序 */
void EXTI2_IRQHandler(void)
{
/* 中断处理代码 */
}

IMX6U中断控制

Cortex-M 内核有个中断系统的管理机构—NVIC(Nested Vectored Interrupt Controller)
Cortex-A7 内核有个中断系统管理机构——GIC(general interrupt controller)

8个中断

Cortex-A内核有8个异常中断:
在这里插入图片描述
其中还包括一个未使用中断,实际只有七个中断。

对于 Cortex-M 内核来说,中断向量表列举出了一款芯片所有的中断向量,包括芯片外设的所有中断。

Cortex-A 内核 CPU 的所有外部中断都属于这个 IRQ 中断,当任意一个外部中断发生的时候都会触发 IRQ 中断。在 IRQ 中断服务函数里面就可以读取指定的寄存器来判断发生的具体是什么中断,进而根据具体的中断做出相应的处理。
在这里插入图片描述
①、复位中断(Rest), CPU 复位以后就会进入复位中断,我们可以在复位中断服务函数里面做一些初始化工作,比如初始化 SP 指针、 DDR 等等。
②、未定义指令中断(Undefined Instruction),如果指令不能识别的话就会产生此中断。
③、软中断(Software Interrupt,SWI),由 SWI 指令引起的中断, Linux 的系统调用会用 SWI指令来引起软中断,通过软中断来陷入到内核空间。
④、指令预取中止中断(Prefetch Abort),预取指令的出错的时候会产生此中断。
⑤、数据访问中止中断(Data Abort),访问数据出错的时候会产生此中断。
⑥、 IRQ 中断(IRQ Interrupt),外部中断,前面已经说了,芯片内部的外设中断都会引起此中断的发生。
⑦、 FIQ 中断(FIQ Interrupt),快速中断,如果需要快速处理中断的话就可以使用此中断。

常用复位中断和IRQ中断,实际需要编写的只有复位中断服务函数 Reset_Handler 和 IRQ 中断服务函数 IRQ_Handler,其它的中断本暂时没用到,所以都是死循环。

GIC中断控制器

GIC介绍

GIC目前有V1-V4,V1太老淘汰了,GIC V2 是给 ARMv7-A 架构使用的,比如 Cortex-A7、 Cortex-A9、 Cortex-A15 等,V3 和 V4 是给 ARMv8-A/R 架构使用的,也就是 64 位芯片使用的。ARM 会根据 GIC 版本的不同研发出不同的 IP 核,那些半导体厂商直接购买对应的 IP 核即可,ARM 针对 GIC V2 就开发出了 GIC400 这个中断控制器 IP 核。当 GIC 接收到外部中断信号以后就会报给 ARM 内核,但是ARM 内核只提供了四个信号给 GIC 来汇报中断情况: VFIQ、 VIRQ、 FIQ 和 IRQ。

VFIQ:虚拟快速 FIQ。VIRQ:虚拟外部 IRQ。FIQ:快速中断 IRQ。IRQ:外部中断 IRQ。
VFIQ 和 VIRQ 是针对虚拟化的,不管,只看IRQ
在这里插入图片描述
①、 SPI(Shared Peripheral Interrupt),共享中断,所有 Core 共享的中断,那些外部中断都属于 SPI 中断。比如按键中断、串口中断等等,这些中断所有的 Core 都可以处理,不限定特定 Core。
②、 PPI(Private Peripheral Interrupt),私有中断, GIC 是支持多核的,每个核肯定有自己独有的中断。这些独有的中断肯定是要指定的核心处理。
③、 SGI(Software-generated Interrupt),软件中断,由软件触发引起的中断,通过向寄存器GICD_SGIR 写入数据来触发,系统会使用 SGI 中断来完成多核之间的通信。

中断ID

每一个 CPU 最多支持 1020 个中断 ID,中断 ID 号为 ID0~ID1019。这 1020 个 ID 包含了 PPI、 SPI 和 SGI。

ID0~ID15:这 16 个 ID 分配给 SGI。
ID16~ID31:这 16 个 ID 分配给 PPI。
ID32~ID1019:这 988 个 ID 分配给 SPI,像 GPIO 中断、串口中断等这些外部中断

I.MX6U 的总共使用了 128 个SPI中断,以及 PPI 和 SGI 的 32 个 ID,一共160个

中断ID全部都在参考手册的第三章:
在这里插入图片描述
移植的官方SDK(Software Development Kit)中就有所有中断ID的枚举
在这里插入图片描述

GIC逻辑分块

GIC 架构分为了两个逻辑块: Distributor 和 CPU Interface,也就是分发器端和 CPU 接口端

Distributor(分发器端):中断事件应该发送到哪个 CPU 接口,将优先级最高的中断事件发送到 CPU 接口端
全局中断使能控制。控制每一个中断的使能或者关闭。设置每个中断的优先级。设置每个中断的目标处理器列表。设置每个外部中断的触发模式:电平触发或边沿触发。设置每个中断属于组 0 还是组 1。

CPU Interface(CPU 接口端):每个 CPU Core 都可以在 GIC 中找到一个与之对应的 CPU Interface
使能或者关闭发送到 CPU Core 的中断请求信号。应答中断。通知中断处理完成。设置优先级掩码,通过掩码来设置哪些中断不需要上报给 CPU Core。定义抢占策略。当多个中断到来的时候,选择优先级最高的中断通知给 CPU Core。

在我们移植的 core_ca7.h 文件里面有定义结构体 GIC_Type,分发器端和 CPU 接口端:
在这里插入图片描述

GIC协处理器

通过 c0 寄存器可以获取到处理器内核信息;通过 c1 寄存器可以使能或禁止 MMU、 I/D Cache 等;通过 c12 寄存器可以设置中断向量偏移;通过 c15 寄存器可以获取 GIC 基地址。(暂时没太理解,后续补充)

中断使能

使用 I.MX6U 上的外设中断就必须先打开 IRQ 中断
寄存器 CPSR 的 I=1 禁止 IRQ,当 I=0 使能 IRQ; F=1 禁止 FIQ, F=0 使能 FIQ。
还可以使用更简单的指令:
在这里插入图片描述
GIC 寄存器 GICD_ISENABLERn 和 GICD_ ICENABLERn 用来完成外部中断的使能和禁止,Cortex-A7 内核中断 ID 只使用了 512 个。一个 bit 控制一个中断 ID 的使能,那么就需要 512/32=16 个 GICD_ISENABLER 寄存器和16个GICD_ICENABLER来完成中断的使能与禁止。
GICD_ISENABLER0 的 bit[15:0]对应ID15 ~ 0 的 SGI 中断, GICD_ISENABLER0 的 bit[31:16]对应 ID31 ~ 16 的 PPI 中断。剩下的GICD_ISENABLER1~GICD_ISENABLER15 就是控制 SPI 中断的。

中断优先级

Cortex-A7 的中断优先级也可以分为抢占优先级和子优先级,两者同样是可以配置的。 GIC 控制器最多可以支持 256 个优先级,数字越小,优先级越高! Cortex-A7 选择了 32 个优先级。

在使用中断的时候需要初始化 GICC_PMR 寄存器:(设置优先级数)
在这里插入图片描述
Cortex-A7内核支持 32 个优先级。

抢占优先级和子优先级位数设置由寄存器 GICC_BPR决定:
在这里插入图片描述
寄存器 GICC_BPR 只有低 3 位有效,其值不同,抢占优先级和子优先级占用的位数也不同:
在这里插入图片描述
有32个抢占优先级,Cortex-A7 使用了 512 个中断 ID
如要设置ID40 中断的优先级为 5:

GICD_IPRIORITYR[40] = 5 << 3

重点代码分析

官方SDK函数

先了解官方SDK包的API函数:

GIC_Init     				//初始化 GIC。
GIC_EnableIRQ 				//使能指定的外设中断。
GIC_DisableIRQ 				//关闭指定的外设中断。
GIC_AcknowledgeIRQ 			//返回中断号。
GIC_DeactivateIRQ 			//无效化指定中断。
GIC_GetRunningPriority 		//获取当前正在运行的中断优先级。
GIC_SetPriorityGrouping 	//设置抢占优先级位数。
GIC_GetPriorityGrouping 	//获取抢占优先级位数。
GIC_SetPriority 			//设置指定中断的优先级。
GIC_GetPriority 			//获取指定中断的优先级。

这些函数都在文件末尾:
在这里插入图片描述

start.S文件

.global _start  				/* 全局标号 *//** 描述:	_start函数,首先是中断向量表的创建* 参考文档:ARM Cortex-A(armV7)编程手册V4.0.pdf P42,3 ARM Processor Modes and Registers(ARM处理器模型和寄存器)* 		 	ARM Cortex-A(armV7)编程手册V4.0.pdf P165 11.1.1 Exception priorities(异常)*/
_start:ldr pc, =Reset_Handler		/* 复位中断 					*/	ldr pc, =Undefined_Handler	/* 未定义中断 					*/ldr pc, =SVC_Handler		/* SVC(Supervisor)中断 		*/ldr pc, =PrefAbort_Handler	/* 预取终止中断 					*/ldr pc, =DataAbort_Handler	/* 数据终止中断 					*/ldr	pc, =NotUsed_Handler	/* 未使用中断					*/ldr pc, =IRQ_Handler		/* IRQ中断 					*/ldr pc, =FIQ_Handler		/* FIQ(快速中断)未定义中断 			*//* 复位中断 */	
Reset_Handler:cpsid i						/* 关闭全局中断 *//* 关闭I,DCache和MMU    关闭 I/D Cache、 MMU、对齐检测和分支预测* 采取读-改-写的方式。*/mrc     p15, 0, r0, c1, c0, 0     /* 读取CP15的C1寄存器到R0中       		        	*/bic     r0,  r0, #(0x1 << 12)     /* 清除C1寄存器的bit12位(I位),关闭I Cache            	*/bic     r0,  r0, #(0x1 <<  2)     /* 清除C1寄存器的bit2(C位),关闭D Cache    				*/bic     r0,  r0, #0x2             /* 清除C1寄存器的bit1(A位),关闭对齐						*/bic     r0,  r0, #(0x1 << 11)     /* 清除C1寄存器的bit11(Z位),关闭分支预测					*/bic     r0,  r0, #0x1             /* 清除C1寄存器的bit0(M位),关闭MMU				       	*/mcr     p15, 0, r0, c1, c0, 0     /* 将r0寄存器中的值写入到CP15的C1寄存器中	 				*/#if 0/* 汇编版本设置中断向量表偏移 */ldr r0, =0X87800000dsbisbmcr p15, 0, r0, c12, c0, 0dsbisb
#endif/* 设置各个模式下的栈指针,* 注意:IMX6UL的堆栈是向下增长的!* 堆栈指针地址一定要是4字节地址对齐的!!!* DDR范围:0X80000000~0X9FFFFFFF*//* 进入IRQ模式 */mrs r0, cpsrbic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/orr r0, r0, #0x12 	/* r0或上0x13,表示使用IRQ模式					*/msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/ldr sp, =0x80600000	/* 设置IRQ模式下的栈首地址为0X80600000,大小为2MB *//* 进入SYS模式 */mrs r0, cpsrbic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/orr r0, r0, #0x1f 	/* r0或上0x13,表示使用SYS模式					*/msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/ldr sp, =0x80400000	/* 设置SYS模式下的栈首地址为0X80400000,大小为2MB *//* 进入SVC模式 */mrs r0, cpsrbic r0, r0, #0x1f 	/* 将r0寄存器中的低5位清零,也就是cpsr的M0~M4 	*/orr r0, r0, #0x13 	/* r0或上0x13,表示使用SVC模式					*/msr cpsr, r0		/* 将r0 的数据写入到cpsr_c中 					*/ldr sp, =0X80200000	/* 设置SVC模式下的栈首地址为0X80200000,大小为2MB */cpsie i				/* 打开全局中断 */
#if 0/* 使能IRQ中断 */mrs r0, cpsr		/* 读取cpsr寄存器值到r0中 			*/bic r0, r0, #0x80	/* 将r0寄存器中bit7清零,也就是CPSR中的I位清零,表示允许IRQ中断 */msr cpsr, r0		/* 将r0重新写入到cpsr中 			*/
#endifb main				/* 跳转到main函数 			 	*//* 未定义中断 */
Undefined_Handler:ldr r0, =Undefined_Handlerbx r0/* SVC中断 */
SVC_Handler:ldr r0, =SVC_Handlerbx r0/* 预取终止中断 */
PrefAbort_Handler:ldr r0, =PrefAbort_Handler	bx r0/* 数据终止中断 */
DataAbort_Handler:ldr r0, =DataAbort_Handlerbx r0/* 未使用的中断 */
NotUsed_Handler:ldr r0, =NotUsed_Handlerbx r0/* IRQ中断!重点!!!!! */
IRQ_Handler:push {lr}					/* 保存lr地址 */push {r0-r3, r12}			/* 保存r0-r3,r12寄存器 */mrs r0, spsr				/* 读取spsr寄存器 */push {r0}					/* 保存spsr寄存器 */mrc p15, 4, r1, c15, c0, 0 /* 从CP15的C0寄存器内的值到R1寄存器中* 参考文档ARM Cortex-A(armV7)编程手册V4.0.pdf P49* Cortex-A7 Technical ReferenceManua.pdf P68 P138*/							add r1, r1, #0X2000			/* GIC基地址加0X2000,也就是GIC的CPU接口端基地址 */ldr r0, [r1, #0XC]			/* GIC的CPU接口端基地址加0X0C就是GICC_IAR寄存器,* GICC_IAR寄存器保存这当前发生中断的中断号,我们要根据* 这个中断号来绝对调用哪个中断服务函数*/push {r0, r1}				/* 保存r0,r1 */cps #0x13					/* 进入SVC模式,允许其他中断再次进去 */push {lr}					/* 保存SVC模式的lr寄存器 */ldr r2, =system_irqhandler	/* 加载C语言中断处理函数到r2寄存器中*/blx r2						/* 运行C语言中断处理函数,带有一个参数,保存在R0寄存器中 */pop {lr}					/* 执行完C语言中断服务函数,lr出栈 */cps #0x12					/* 进入IRQ模式 */pop {r0, r1}				str r0, [r1, #0X10]			/* 中断执行完成,写EOIR */pop {r0}						msr spsr_cxsf, r0			/* 恢复spsr */pop {r0-r3, r12}			/* r0-r3,r12出栈 */pop {lr}					/* lr出栈 */subs pc, lr, #4				/* 将lr-4赋给pc *//* FIQ中断 */
FIQ_Handler:ldr r0, =FIQ_Handler	bx r0									

重点在复位中断和IRQ中断(目前学的比较浅,还不太理解),但是关于下图:
在这里插入图片描述
之前学习过ARM的三级流水过程:(类似下图)
在这里插入图片描述
中断发生保存的是0x2008,所以要跳回到0x2004处执行这条指令,不然漏了这条指令。

自行编写中断驱动文件

irqNesting:中断嵌套计数器
irqTable:中断服务函数数组,大小为 I.MX6U 的中断源个数:160 个
int_init:中断初始化函数,初始化了 GIC,然后初始化了中断服务函数表,最终设置了中断向量表偏移
system_irqtable_init:中断服务函数表初始化函数,初始化 irqTable,给其赋初值
start.S 中调用的 system_irqhandler 函数:根据中断号在中断处理函数表 irqTable 中取出对应的中断处理函数并执行
default_irqhandler: 默认中断处理函数

#include "bsp_int.h"/* 中断嵌套计数器 */
static unsigned int irqNesting;/* 中断服务函数表 */
static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS];/** @description	: 中断初始化函数* @param		: 无* @return 		: 无*/
void int_init(void)
{GIC_Init(); 						/* 初始化GIC 							*/system_irqtable_init();				/* 初始化中断表 							*/__set_VBAR((uint32_t)0x87800000); 	/* 中断向量表偏移,偏移到起始地址   				*/
}/** @description	: 初始化中断服务函数表 * @param		: 无* @return 		: 无*/
void system_irqtable_init(void)
{unsigned int i = 0;irqNesting = 0;/* 先将所有的中断服务函数设置为默认值 */for(i = 0; i < NUMBER_OF_INT_VECTORS; i++){system_register_irqhandler((IRQn_Type)i,default_irqhandler, NULL);}
}/** @description			: 给指定的中断号注册中断服务函数 * @param - irq			: 要注册的中断号* @param - handler		: 要注册的中断处理函数* @param - usrParam	: 中断服务处理函数参数* @return 				: 无*/
void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) 
{irqTable[irq].irqHandler = handler;irqTable[irq].userParam = userParam;
}/** @description			: C语言中断服务函数,irq汇编中断服务函数会调用此函数,此函数通过在中断服务列表中查找指定中断号所对应的中断处理函数并执行。* @param - giccIar		: 中断号* @return 				: 无*/
void system_irqhandler(unsigned int giccIar) 
{uint32_t intNum = giccIar & 0x3FFUL;/* 检查中断号是否符合要求 */if ((intNum == 1023) || (intNum >= NUMBER_OF_INT_VECTORS)){return;}irqNesting++;	/* 中断嵌套计数器加一 *//* 根据传递进来的中断号,在irqTable中调用确定的中断服务函数*/irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam);irqNesting--;	/* 中断执行完成,中断嵌套寄存器减一 */}/** @description			: 默认中断服务函数* @param - giccIar		: 中断号* @param - usrParam	: 中断服务处理函数参数* @return 				: 无*/
void default_irqhandler(unsigned int giccIar, void *userParam) 
{while(1) {}
}

还是没怎么看懂,内容太多了,我觉得暂时先搞清楚一些原理概念以及简单的外部函数接口就行,内部可能在后续的学习中进行钻研

gpio内部有中断的设置:
在这里插入图片描述

中断函数写在这里:
在这里插入图片描述
主函数进行初始化:
在这里插入图片描述

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

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

相关文章

c语言终点站--文件操作

前言&#xff1a; 为什么要学习文件操作呢&#xff1f;想要知道这个问题&#xff0c;我们就需要先了解什么是数据的可持久化。 那么什么是数据的可持久化呢&#xff1f;数据的可持久化就是把内存中的数据对象永久的保存在电脑的磁盘文件中&#xff0c;将程序数据在持久状态和…

Three.js如何计算3DObject的2D包围框?

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在Three.js应用开发中&#xff0c;有时你可能需要为3D场景中的网格绘制2D的包围框&#xff0c;应该怎么做&#xff1f; 朴素的想法是把网格的3D包围框投影到屏幕空间&#xff0c;例如&#xff0c;下图中的绿色框 3D包围框…

LeetCode【84】柱状图中的最大矩形

题目&#xff1a; 思路&#xff1a; https://blog.csdn.net/qq_28468707/article/details/103682528 https://www.jianshu.com/p/2b9a36a548fa 清晰 代码&#xff1a; public int largestRectangleArea(int[] heights) {int[] heightadd new int[heights.length 1];for (i…

php+html+js+ajax实现文件上传

phphtmljsajax实现文件上传 目录 一、表单单文件上传 1、上传页面 2、接受文件上传php 二、表单多文件上传 1、上传页面 2、接受文件上传php 三、表单异步xhr文件上传 1、上传页面 2、接受文件上传php 四、表单异步ajax文件上传 1、上传页面 2、接受文件上传ph…

typora常用偏好设置

启用自动保存 关闭拼写检查 插入图片的设置 将图片保存在当前文件夹内 换行设置 关闭换行符的显示功能

ElementUI增删改的实现及表单验证

文章目录 一、准备二、添加功能2.1 新增添加按钮2.2 添加弹出框2.3 data中添加内容2.4 methods中添加相关方法 三、编辑功能3.1 表格中添加编辑和删除按钮3.2 methods中添加方法3.3 修改methods中clear方法3.4 修改methods中的handleSubmit方法 四、删除书籍功能4.1 往methods的…

【LeetCode刷题笔记】哈希查找

771. 宝石与石头 解题思路&#xff1a; 1. HashSet &#xff0c;把所有 宝石 加入 set , 然后遍历检查 每一块石头是否包含在set中 &#xff0c;若包含就是宝石。 2. 计数数组map, 把所有 宝石 进行 count 数组 计数 &#xff0c;, 然后遍历检查 每一块石头是否 count[stone] …

算法错题簿(持续更新)

自用算法错题簿&#xff0c;按算法与数据结构分类 python1、二维矩阵&#xff1a;记忆化搜索dp2、图论&#xff1a;DFS3、回溯&#xff1a;129612964、二叉树&#xff1a;贪心算法5、字符串&#xff1a;记忆化搜索6、01字符串反转&#xff1a;结论题7、二进制数&#xff1a;逆向…

高效节能双冷源空调架构在某新建数据中心项目中的应用

随着互联网、通信、金融等行业的发展&#xff0c;数据中心产业迈入高质量发展新阶段&#xff0c;在国家“双碳”战略目标和“东数西算”工程的有力指引下&#xff0c;数据中心加快向创新技术、强大算力、超高能效为特征的方向演进。数据中心已经成为支撑经济社会数字化转型必不…

Linux系统管理:虚拟机Centos Stream 9安装

目录 一、理论 1.Centos Stream 9 二、实验 1.虚拟机Centos Stream 9安装准备阶段 2.安装Centos Stream 9 3.进入系统 一、理论 1.Centos Stream 9 (1) 简介 CentOS Stream 是一种 Linux 操作系统。安装此操作系统的难题在于&#xff0c;在安装此系统之前&#xff0c…

想要用Chat GPT写申请文书?先看各大名校招生官对它的态度是什么?

新的申请季已经正式开始&#xff0c;一些热门项目的ED截止日期也不再遥远&#xff0c;因此很多准留学生们都已经开始了关于文书的创作。 而随着科技的不断发展&#xff0c;以ChatGPT为首的一众AI工具也作为一种辅助手段愈发融入了我们的生活。 那么不免就会有一些同学在准备申…

户外led显示屏中的裸眼3D效果是怎么做出来的?

近几年&#xff0c;裸眼3D成了一个热点词汇&#xff0c;但凡它出现的地方都会迅速成为网络热门话题和网红打卡点。裸眼3D大屏凭借其立体逼真的画面显示效果&#xff0c;带给人们新颖震撼的视觉体验&#xff0c;不仅成为户外广告的“新宠”&#xff0c;还成为了城市的新地标&…