怎样编写裸片启动程序-ARMv8的Boot Code和ROM程序

news2024/11/29 11:48:45

ROM程序就是固化在芯片的ROM里面,把应用程序从存储器里加载/搬移到RAM中并使处理器开始执行应用程序的一段程序

1 Boot Code和ROM程序

从多普通单核MCU(如STM32)的使用者的角度来看,只需要把编译好的hex文件烧写到片上Flash中,再复位一下就能让程序在芯片上跑起来。

但此类开发者往往忘记了一个重要的问题——Flash里的程序是怎么被加载到MCU的RAM中的,或者说,被烧写到Flash中的程序是怎么跑起来的?程序明明是在RAM上执行,怎么把程序烧写到FLASH中就行了呢?

这个问题,实际上就是在问:裸片是如何Boot(启动)起来的?是的,你可以从本文的标题中获得这个问题的答案——Flash中的程序是由ROM程序加载到芯片的RAM中的。此外,在执行此前存储在Flash中的那个程序前,芯片可能还执行了一段被称为Boot Code的程序,用以设定芯片的初始状态。

2 芯片从Flash启动的流程

上边又抛出两个问题,ROM程序是啥?ROM程序执行的流程是怎样的?这一节就来讨论这两个问题。
请添加图片描述

如上图,MCU内含CORE(处理核)、ROM、RAM和FLASH这几个组件。芯片上电以后,CORE会从ROM固定地址读取程序,用以加载芯片使用者烧写到FLASH中的内容。这段存储在ROM中,芯片上电后首先执行的程序,就是我们说的ROM程序。

请添加图片描述

FLASH的开头位置通常会放一些FLASH文件的信息,比如程序有多大,搬移到什么地方去,搬移完成后从哪里开始执行。这些问题都是我们在编写ROM程序时需要考虑的,这部分放到第三节再详细讨论。

ROM程序首先会读取FLASH的文件信息,确认搬移长度和目的地址,这个地址一般是RAM的起始地址。然后根据读取到的信息把FLASH文件搬移到RAM上。搬移完成后,芯片或者MCU的RAM中就有了我们烧写到FLASH里的应用程序。这时只要把PC修改到程序的起始地址,就可以让我们的应用程序在芯片上跑起来了。

所以芯片从Flash启动的流程大致分步

  1. 芯片上电,CORE读取并执行ROM程序
  2. 读取FLASH文件信息
  3. 根据文件信息把FLASH文件中的应用程序搬移到RAM上
  4. 跳转PC到RAM上应用程序的起始地址
  5. 开始执行应用程序的第一条指令

3 如何设计ROM程序

3.1 考虑片上资源使用方式
这里提到的片上资源主要指RAM,它是我们搬移的目的地和执行程序的大本营。对于多核SoC而言,RAM需要被划分成几个区域供给不同的处理器作RAM使用。同时还应当保留一些空间用作共享内存(用来做数据交换和核间通信)。比如像下边这样。
请添加图片描述
值得注意的是,咱在RAM末尾预留了一部分空间用作堆栈,需要指定堆栈空间也是ROM程序和一般应用程序的不同之处。毕竟此时整片RAM都由我们规划,堆栈尽可能放到末尾能够方便我们划分其他空间的用途。

对于单核处理器而言,物理RAM的起始地址就可以是处理器的RAM空间起始地址,整片RAM的划分情况可以如下图所示,只有RAM和堆栈两个区域。为了方便叙述和读者理解,咱后面的设计都基于单核SoC这个前提。
请添加图片描述
比如假设某个MCU(比如STM32)有64K的RAM,就可以拿4K做堆栈,其余的60K全部用来放应用程序。

有了大致的划分方案,就要开始做计算了哈。


假设RAM起始地址为0x80000000
64K = 601024 = 65536 = 0x10000*
60K = 601024 = 61440 = 0x0F000*

所以,RAM的地址范围是0x80000000-0x80010000;
其中,0x80000000-0x8000EFFF用来存放程序;
0x8000F000-0x8000FFFF用来当堆栈;


做完计算就可以得到下面这个设计结果啦。它指导着我们明确三件事:

  1. 应保证应用程序编译生成的的大小不超过60KB
  2. 上电复位后,应把堆栈指针寄存器(SP)初始值写为指向堆栈的最高地址0x8000FFFC(四字节对齐)
  3. ROM程序搬移FLASH文件的的目地址和搬移完成后的跳转地址都是0x80000000

请添加图片描述

3.2 设计存储FLASH文件信息数据结构
要把一段代码从FLASH搬到RAM,至少得知道这段代码在FLASH的什么位置吧,所以代码存放的起始地址和结束地址就得记录在前一节提到的文件信息段里。

知道从哪儿搬出来,还得知道搬到那个地址去。不过因为这个地址通常是固定的(比如本文介绍的这个启动程序固定搬到RAM的起始地址0x80000000去),所以就不用记录在FLASH文件信息段里啦。

搬移完成后需要一个跳转地址,用于设置PC,所以这个地址也得记录在信息段里边。

综上所述,我们的FLASH文件信息数据结构可以设计成下面这样。


typedef struct flash_info_seg{
	unsigned int program_file_offset;	//应用程序代码起始位置在flash里的偏移地址
	unsigned int ram_start_addr;		//搬移到ram时对应的起始地址
	unsigned int ram_end_addr;			//搬移到ram时对应的结束地址
	unsigned int pc_branch_addr;		//搬移完成后pc的跳转地址	
}flash_info_seg_def;


假如你这样设计FLASH文件信息数据结构的话,下图里【FLASH文件信息】这个区域就应该放着一个flash_info_seg_def。相应的,由于flash_info_seg_def的大小为4*4Bytes = 16Bytes,所以【FLASH文件内容】这个区域的偏移地址B也就等于A + sizeof(flash_info_seg_def),即A +16
请添加图片描述
所以第一个参数program_file_offset应赋值为B
ram_start_addrpc_branch_addr都应该赋值为RAM的起始地址0x80000000

第三个参数ram_end_addr的值跟你的应用程序的实际长度有关,假设你编译得到的二进制文件长度为0x100, 那么ram_end_addr就应该赋值为ram_start_addr + 0x100

3.3 设计启动方式选择组件
这部分可能大伙感到很陌生,但是至少知道开发板上通常有那么几个用于选择启动方式的跳帽或者拨码开关。芯片上电以后我们需要首先用几句汇编读一下这些引脚的状态,选择启动方式。

比如假设只有两种启动方式debug_boot和smcflash_boot,引脚的状态存储在0x90000000这个地址上,可以用下面的汇编程序实现启动模式的选择。

/* choose boot method option */
option_jump:
	/* read boot mode pins status */
	LDR r0, #0x90000000
	LDR r1, [r0]
	MOV r2, #0x1
	AND r2, r2, r0
	CMP	r2, #0
	BEQ	debug_boot
	B	smcflash_boot

首先把0x90000000这个地址上的值读到通用寄存器r1中,

	LDR r0, #0x90000000
	LDR r1, [r0]
	MOV r2, #0x1

然后把读出来的值和0x1做与运算,结果存到r2

	MOV r2, #0x1
	AND r2, r2, r0

如果值是0,就选择debug启动模式,否则选择smcflash启动模式

	CMP	r2, #0
	BEQ	debug_boot
	B	smcflash_boot

3.4 考虑核间通信的需求
本文讨论的是单核处理器的启动程序,但设计方法也适用于多核处理器的启动。多核处理器启动时可能会遇到FLASH不能同时被多核读取的问题,也就要考虑多核的启动顺序。

关于这个,可以参考我这篇文章哈:在多核异构SoC平台上进行软件开发。

4 什么是Boot Code

可能你已经发现,前面咱们一直在讨论如何启动,但没有涉及硬件初始化的内容。所以这里说的Boot Code,特指芯片启动时堆芯片进行初始化的代码。

的这部分代码要做的工作包括以下这些:

  • 异常初始化(Initializing exceptions)
  • 寄存器初始化(Initializing registers)
  • 配置MMU和Cache(Configuring the MMU and caches)
  • 使能NEON和浮点加速器(Enabling NEON and Floating Point)
  • 切换异常处理级别(Changing Exception levels)

ARMv8架构的处理器支持AArch32和AArch64两种运行模式,本文只介绍AArch32模式,AArch64模式的Boot Code可以参考ARM官方裸片启动程序示例手册《Bare-metal Boot Code for ARMv8-A Processors》

5 如何编写Boot Code

5.1 异常初始化
异常初始化需要设置向量表并启用异步异常。

在AArch32模式下启动处理器时,SCTLR的值。v设置复位向量的位置:

  • 当SCTLR.V为0,处理器从地址0x00000000开始执行。
  • 当SCTLR.V为1,处理器从地址0xFFFF0000开始执行。可以使用硬件输入VINITHI来设置SCTLR.V的复位值。

对于复位以外的异常,处理器查找向量表,通过对向量基址寄存器进行编程,可以将向量表放置在定制的位置。最多有四个向量表。相应的向量基址寄存器有:

  • 向量基址寄存器(VBAR)(安全)
  • 监控向量基址寄存器(MVBAR)
  • 向量基址寄存器(HVBAR)
  • VBAR(非安全)
.balign 0x20
vector_table_base_address:
B reset_handler
B undefined_handler
B svc_handler
B prefetch_handler
B data_handler
NOP
B IRQ_handler
// You can place the FIQ handler code here.

在使用向量表之前,必须初始化四个向量表,并对向量表基址寄存器进行编程。向量表的基址必须32字节对齐。

然后用下面的代码在复位后初始化VBAR和MVBAR

LDR R1, =secure_vector_table_base_address
MCR P15, 0, R1, C12, C0, 0 // Initialize VBAR (Secure).
LDR R1, =monitor_vector_table_base_address
MCR P15, 0, R1, C12, C0, 1 // Initialize MVBAR.

异步异常包括异步中止、IRQ和FIQ。它们可以由CPSR的{A,I,F}寄存器位屏蔽。因此,如果要使用异步中止(asynchronous aborts),IRQ和FIQ,CPSR.{A,I,F}必须清零。

// Enable asynchronous aborts, interrupts, and fast interrupts.
CPSIE aif

要使能中断,还必须初始化外部中断控制器,以便将中断传送给处理器,但本文不讨论这一个。


5.2 寄存器初始化
寄存器初始化包括初始化以下寄存器:

  • 通用寄存器
  • 堆栈指针寄存器
  • 系统控制寄存器

5.2.1 初始化系通用寄存器
通对于用寄存器,没有特殊需求的话,全写0吧


5.2.2 初始化堆栈指针寄存器
堆栈指针寄存器(r13)在一些指令中隐式使用,例如push
和pop。在使用它之前,必须用一个合适的值初始化它。

在MPCore系统中,不同的堆栈指针(sp)必须指向不同的内存地址,以避免覆盖堆栈区域。如果需要在不同模式下使用sp,那就必须初始化所有sp。

下面代码是为一种模式初始化SP。SP指向的堆栈位于stack_top,堆栈大小为CPU_STACK_SIZE字节。

// Initialize the stack pointer.
LDR R13, =stack_top
ADD R13, R13, #4 
MRC P15, 0, R0, C0, C0, 5 // Read MPIDR.
AND R0, R0, #0xFF // R0 == core number.
MOV R2, #CPU_STACK_SIZE
MUL R1, R0, R2 // Create separate stack spaces 
SUB R13, R13, R1 // for each processor.

5.2.3 初始化系统控制寄存器

对于一些系统控制寄存器,例如保存的程序状态寄存器(SPSR)和异常链接寄存器hyp模式(ELR_hyp),该架构没有为它们定义复位值。因此,在使用寄存器之前,必须对其进行初始化。
下面是在监控(Monitor)模式下初始化SPSR和ELR_hyp

// Initialize SPSR in all modes.
MOV R0, #0
MSR SPSR, R0
MSR SPSR_svc, R0
MSR SPSR_und, R0
MSR SPSR_hyp, R0
MSR SPSR_abt, R0
MSR SPSR_irq, R0
MSR SPSR_fiq, R0
// Initialize ELR_hyp.
MOV R0, #0
MSR ELR_hyp, R0

理论上,所有没有架构上定义的复位值的系统寄存器都必须手动初始化。

我们前面说可以把所有通用寄存器写成0,但一些寄存器可以具有实现定义的复位值,这部分内容可以参考参见ARM架构参考手册 《ARM® Architecture Reference Manual ARMv8, for
ARMv8-A architecture profile
》里面的通用寄存器(General system control registers)章节。

5.3 配置MMU和Cache
MMU和Cache配置涉及以下操作:
• 清除(Cleaning)和关闭(Invalidating)Cache
• 配置 MMU
• 使能 MMU 和 Caches


5.3.1 清理缓存并使其失效
缓存RAM中的内容在重置后无效,因此必须执行无效操作来初始化处理器中的所有缓存。

在某些ARMv7-A处理器中,如Cortex-A9处理器,必须使用软件来使所有Cache ram无效。在ARMv8-A处理器和大多数ARMv7-A处理器中,就不必这样做,因为硬件会在复位后自动使所有Cache ram失效。但是,在某些情况下,您必须使用软件来清理数据缓存并使其无效,例如核心断电过程。

下面是多次使用DCCISW指令来清理L1数据缓存并使其无效。其他级别缓存或其他缓存操作也可以参考这个代码。

// Disable L1 Caches.
MRC P15, 0, R1, C1, C0, 0 // Read SCTLR.
BIC R1, R1, #(0x1 << 2) // Disable D Cache.
MCR P15, 0, R1, C1, C0, 0 // Write SCTLR.
// Invalidate Data cache to create general-purpose code. Calculate the
// cache size first and loop through each set + way.
MOV R0, #0x0 // R0 = 0x0 for L1 dcache 0x2 for L2 dcache.
MCR P15, 2, R0, C0, C0, 0 // CSSELR Cache Size Selection Register.
MRC P15, 1, R4, C0, C0, 0 // CCSIDR read Cache Size.
AND R1, R4, #0x7
ADD R1, R1, #0x4 // R1 = Cache Line Size.
LDR R3, =0x7FFF
AND R2, R3, R4, LSR #13 // R2 = Cache Set Number – 1.
LDR R3, =0x3FF
AND R3, R3, R4, LSR #3 // R3 = Cache Associativity Number – 1.
CLZ R4, R3 // R4 = way position in CISW instruction.
MOV R5, #0 // R5 = way loop counter.
way_loop:
MOV R6, #0 // R6 = set loop counter.
set_loop:
ORR R7, R0, R5, LSL R4 // Set way.

ORR R7, R7, R6, LSL R1 // Set set.
MCR P15, 0, R7, C7, C6, 2 // DCCISW R7.
ADD R6, R6, #1 // Increment set counter.
CMP R6, R2 // Last set reached yet?
BLE set_loop // If not, iterate set_loop,
ADD R5, R5, #1 // else, next way.
CMP R5, R3 // Last way reached yet?
BLE way_loop // if not, iterate way_loop.

5.3.2 配置MMU
ARMv8-A处理器使用VMSAv8-32在AArch32中执行以下操作:

  • 将物理地址转换成虚拟地址。
  • 确定内存属性并检查访问权限。

地址转换由转换表定义,并由内存管理单元(MMU)管理。启用MMU之前,必须设置映射表(Translation table)和映射表遍历规则。

每个特权级(PL)都有专用的映射表和控制寄存器。在使用之前,必须设置所有映射表和控制寄存器。

有关ARMv8-A架构配置文件的详细信息,可以参考ARM架构参考手册ARMv8中关于VMSAv8-32的部分。

AArch32支持两种转换表格式:

  • VMSAv8-32短描述符格式。
  • VMSAv8-32长描述符格式。

在ARMv8-A中,安全状态下的软件执行权限等级由异常级别(el)定义。有关PLs和ELs之间的关系,可以参考《ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile》中的 Execution privilege, Exception levels, and AArch32 Privilege levels章节,这里就不赘述了。


5.3.3 使能MMU和Cache

在启用MMU和缓存之前,必须对它们进行初始化。在使能所有ARMv8-A处理器的MMU和缓存之前,必须设置SMPEN位,以支持硬件一致性。

下面是设置SMPEN位以及启用MMU和缓存。

// SMP is implemented in the CPUECTLR register.
MRRC P15, 1, R0, R1, C15 // Read CPUECTLR.
ORR R0, R0, #(0x1 << 6) // Set SMPEN.
MCRR P15, 1, R0, R1, C15 // Write CPUECTLR.
// Enable caches and the MMU.
MRC P15, 0, R1, C1, C0, 0 // Read SCTLR.
ORR R1, R1, #(0x1 << 2) // The C bit (data cache).
ORR R1, R1, #(0x1 << 12) // The I bit (instruction cache).
ORR R1, R1, #0x1 // The M bit (MMU).
MCR P15, 0, R1, C1, C0, 0 // Write SCTLR.
DSB
ISB

5.4 使能NEON和浮点加速器
在AArch32模式下,默认禁用NEON技术和FP,因此必须手动使能。

// Enable access to NEON/FP by enabling access to Coprocessors 10 and 11.
// Enable Full Access in both privileged and non-privileged modes.
MOV R0, #(0xF << 20) // Enable CP10 & CP11 function
MCR P15, 0, R0, C1, C0, 2 // Write the Coprocessor Access Control 
ISB // Register (CPACR).
// Switch on the FP and NEON hardware.
MOV R1, #(0x1 << 30)
VMSR FPEXC, R1

这部分可以参考《ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile》中的Enabling Advanced SIMD and floating-point support章节。

5.5 切换模式
AArch32有九种处理器模式,分别是:

  • USR
  • SYS
  • FIQ
  • IRQ
  • SVC
  • ABT
  • UND
  • HYP
  • MNT

在AArch32模式下启动时,处理器在复位后进入SVC模式。

通常,处理器接受或返回异常以改变到其他模式。也可以直接改变CPSR.m实现模式切换,如下。

.equ Mode_USR, 0x10
.equ Mode_FIQ, 0x11
.equ Mode_IRQ, 0x12
.equ Mode_SVC, 0x13
.equ Mode_MNT, 0x16
.equ Mode_ABT, 0x17
.equ Mode_HYP, 0x1A
.equ Mode_UND, 0x1B
.equ Mode_SYS, 0x1F
// When a processor is in Monitor, System, FIQ, IRQ, Supervisor, Abort
// or Undefined mode, use the CPS instruction to change modes.
CPS #Mode_FIQ

USR模式不能直接改写寄存器切换模式。所以当处于USR模式时,可以通过如下命令切换到SVC模式。

// When processors are in User mode, use SVC to change from User mode 
// to SVC mode. Make sure that VBAR is initialized before executing SVC.
SVC #0

6 参考

  • Bare-metal Boot Code for ARMv8-A Processors Version 1.0
  • ARM® Architecture Reference Manual ARMv8, for ARMv8-A architecture profile (ARM DDI 0487)
  • ARM® Cortex™-A Series Programmer’s Guide for ARMv7-A (ARM DEN 0013)
  • ARM® Cortex®-A Series Programmer’s Guide for ARMv8-A (ARM DEN0024)

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

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

相关文章

JavaScript(WebAPI) (前端)

文章目录前言一、WebAPI二、DOM1.选中元素2.事件3.获取/修改元素内容4.获取/修改元素属性5.获取/修改表单元素属性6.操作复选框7.获取/修改样式属性①行内样式②通过css class 指定的样式8.新增节点9.删除节点总结前言 虽然学了一些js的语法,但是仍然无法写出页面的动态效果~~…

【JavaScript高级进阶】构造函数和原型,学会prototype

目录 前言 1.构造函数和原型 1.1使用prototype解决内存浪费的问题 1.2constructor构造函数构造器构造函数 2.原型链 2.1js中成员查找规则 2.2原型对象this指向 2.3扩展内置对象 3.call作用 4.继承 4.1利用原型对象继承 写在最后 前言 哈喽哈喽大家好&#xff0c;因为…

【Day1】零基础学java--》记事本运行java程序,通熟语言让你彻底明白为什么配置java环境变量

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#xff0c;从今天开始我将协同大家一起从零基础学习Java&#xff0c;期待与君为伴&#xff0c;走向海的彼岸。&#x1f495;&#x1f495;&#x1f495; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&a…

C语言之数组练习题

第1关&#xff1a;数组插入元素 300 任务要求参考答案评论106 任务描述相关知识 数组数组元素的表示方法编程要求测试说明任务描述 本关需要你将一个数插入到一组已经排好序的数组并输出。 相关知识 数组在程序设计中&#xff0c;为了处理方便&#xff0c; 把具有相同类型…

【C++】多态 — 多态的原理 (下篇)

文章目录&#x1f4d6; 前言1. 虚函数表1.1 虚函数表的引入&#xff1a;1.2 基类的虚表&#xff1a;1.3 派生类虚表&#xff1a;2. 多态的原理2.1 多态虚函数的调用和普通函数的调用&#xff1a;2.1 - 1 到底什么是多态&#xff08;重点&#xff09;2.1 - 2 父类的指针实现多态…

Allegro基本规则设置指导书之Analysis Modes

Allegro基本规则设置指导书之Analysis Modes 下面介绍基本规则设置指导书之Analysis Modes 点击set-up-constrains-Modes 调出Analysis Modes,这个是所有DRC的总开关 下面介绍常用的一些开关设置 Design Options (Soldermask) 从上往下 阻焊到阻焊的间距 阻焊到pad和走线…

EasyCVR及智能分析网关在校园视频融合及明厨亮灶项目中的应用方案设计

随着校园智能化需求的不断增长&#xff0c;越来越多的校园逐渐开始升级校园监控视频平台&#xff0c;将原先传统的视频监控系统&#xff0c;逐渐升级转变为灵活性强、视频能力丰富、具备AI检测能力、并能支持视频汇聚与统一管理的智能化校园综合管理平台。 在某学校的视频监控…

MapReduce概述

MapReduce概述 MapReduce是一个分布式运算程序的编程框架&#xff0c;是用户开发“基于Hadoop的数据分析应用”的核心框架。 MapReduce核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的分布式运算程序&#xff0c;并发运行在一个Hadoop集群上。 MapReduce…

【优化算法】鹈鹕优化算法(POA)(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑…

ARMv7/ARMv8/ARMv9架构你不知道的那些事

快速链接: . &#x1f449;&#x1f449;&#x1f449; 个人博客笔记导读目录(全部) &#x1f448;&#x1f448;&#x1f448; 付费专栏-付费课程 【购买须知】: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; 以下仅代表个人观点&…

七大排序算法——快速排序

AcWing算法专题——快速排序 文章目录AcWing算法专题——快速排序前言一、快速排序的核心二、算法图示三、算法代码四、算法详解1.递归停止的条件2.下标移动的实现3.下标初始值的设定4.边界情况分析4.1区间划分4.2 do while循环条件五、思考题总结前言 现在我们开始进入算法模块…

【第一章 虚拟机】

第一章 虚拟机 1. 虚拟机VM ①虚拟机就是一台虚拟的计算机&#xff0c;它是一款软件&#xff0c;用来执行一系列虚拟计算机指令。 ②虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机&#xff08;比如VMware&#xff09;,它们完全是对物理计算机的仿真&#xff0c;提供了一个可运…

瑞芯微rk3568移植openbmc(三)

2022.11.04 更新 1、关于h264 novnc openbmc中使用的ipkvm其server端调用的是libvncserver库&#xff0c;而其web client端调用的则是novnc的库&#xff0c;既上篇研究修改了libvncserver后&#xff0c;再次继续研究了一下novnc。 Github搜索一圈以后&#xff0c;发现https://…

Java设计模式之单例模式详细讲解

设计模式与单例模式 1、什么是单例模式 ​ 单例模式是指保证某个类在整个软件系统中只有一个对象实例&#xff0c;并且该类仅提供一个返回其对象实例的方法&#xff08;通常为静态方法&#xff09; 2、单例模式的种类 ​ 经典的单例模式实现方式一般有五种 2.1 饿汉式 //…

【SpringBoot】SpringBoot整合SpringSecurity+thymeleaf实现认证授权(配置对象版)

一.概述 1.框架概述 Spring Security 是 Spring 家族中的一个安全管理框架&#xff0c;Spring Security 的两大核心功能就是认证&#xff08;authentication&#xff09;和授权&#xff08;authorization&#xff09;。 认证 &#xff1a;你是什么人。授权 &#xff1a;你能…

RHCE学习 --- 第一次作业

RHCE学习 — 第一次作业 首先我们先设置网卡开机自动启动 vi /etc/sysconfig/network-scripts/ifcfg-ens160 最下面修改ONBOOTyes 然后安装好chrony&#xff0c;配置/etc/chrony.conf文件&#xff0c;添加题目要求的时间服务器 然后设置一个例行性工作&#xff0c;每天早上九…

美国FBA海运专线究竟是什么?美国fba海运专线都有那些?

美国FBA海运专线究竟是什么?美国FBA特别航运线&#xff0c;顾名思义就是海运发送的特别货运线&#xff0c;那么它的收费标准是什么呢?一、美国FBA海运专线究竟是什么 美国FBA特别航运线&#xff0c;顾名思义就是海运发送的特别货运线&#xff0c;那么它的收费标准是什么呢? …

猿创征文|『编程与创作』10款颜值颇高的宝藏工具

&#x1f31f;个人主页&#xff1a;Mymel_晗&#xff0c;一名喜欢鼓捣 Java 的在校学生。 &#x1f31f;撸代码本来是一件枯燥的事情&#xff0c;而一款高颜值工具加持可能会让你事半功倍&#xff0c;今天就给大家推荐一下我在大学学习中发现的几款颜值工具~ 从写代码&#xff…

录音m4a怎么转换成mp3

有小伙伴问手机录音文件电脑上播放不了怎么办&#xff1f;这是很多小伙伴在用手机录完音后遇到最多的问题&#xff0c;尤其是那些第一次遇到这个问题的人&#xff0c;根本不知道这是什么原因导致的&#xff0c;还总以为自己的录音文件出了问题&#xff0c;回去检查发现手机上还…

03-Nginx性能调优与零拷贝

目录 Nginx 性能调优 零拷贝&#xff08;Zero Copy&#xff09; 零拷贝基础 A、 实现细节 B、 总结 零拷贝方式 A、 实现细节 B、 总结 A、 实现细节 B、 总结 A、 实现细节 B、 总结 Nginx 性能调优 在 Nginx 性能调优中&#xff0c;有两个非常重要的理论点&#xff08;面试点…