基于aarch64分析kernel源码 三:启动代码分析

一、内核启动入口点

/** Kernel startup entry point.* ---------------------------** The requirements are:*   MMU = off, D-cache = off, I-cache = on or off,*   x0 = physical address to the FDT blob.* 这部分注释说明了内核启动入口点的要求和约束条件。* 要求包括:MMU(内存管理单元)关闭,数据缓存(D-cache)关闭,指令缓存(I-cache)可以开启或关闭,* 以及需要将物理地址传递给 FDT(平台设备树)blob 的寄存器 x0** Note that the callee-saved registers are used for storing variables* that are useful before the MMU is enabled. The allocations are described* in the entry routines.* 请注意,被调用方保存的寄存器用于存储启用 MMU 之前有用的变量。分配在条目例程中描述。*//* 标识内核启动入口点 */__HEAD/** DO NOT MODIFY. Image header expected by Linux boot-loaders.* 请勿修改。Linux 引导加载进程所需的映像标头。*/efi_signature_nop			// special NOP to identity as PE/COFF executable 特殊 NOP 标识为 PE/COFF 可执行文档b	primary_entry			// branch to kernel start, magic 跳转到内核启动代码/* 这部分代码是内核启动入口点后面的一条 .head 段,包含了用于 Linux 引导加载器期望的镜像头信息。其中包括偏移量、大小、标志等信息 */.quad	0				// Image load offset from start of RAM, little-endian 镜像加载偏移量le64sym	_kernel_size_le			// Effective size of kernel image, little-endian 内核镜像的有效大小le64sym	_kernel_flags_le		// Informative flags, little-endian 信息标志.quad	0				// reserved.quad	0				// reserved.quad	0				// reserved.ascii	ARM64_IMAGE_MAGIC		// Magic number 幻数.long	.Lpe_header_offset		// Offset to the PE header. PE头的偏移量__EFI_PE_HEADER/* 这行代码标识接下来的代码是 EFI(扩展固件接口)PE(可执行文件)头部信息 */.section ".idmap.text","a"

二、primary_entry

	/* 这行代码标识接下来的代码是 EFI(扩展固件接口)PE(可执行文件)头部信息 */.section ".idmap.text","a"/** The following callee saved general purpose registers are used on the* primary lowlevel boot path:* 以下被调用者保存的通用寄存器用于主低级引导路径**  寄存器		 上下文					  目标*  Register   Scope                      Purpose*  x19        primary_entry() .. start_kernel()        whether we entered with the MMU on*  x20        primary_entry() .. __primary_switch()    CPU boot mode *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0*  x22        create_idmap() .. start_kernel()         ID map VA of the DT blob*  x23        primary_entry() .. start_kernel()        physical misalignment/KASLR offset*  x24        __primary_switch()                       linear map KASLR seed*  x25        primary_entry() .. start_kernel()        supported VA size*  x28        create_idmap()                           callee preserved temp register*/
SYM_CODE_START(primary_entry)bl	record_mmu_statebl	preserve_boot_argsbl	create_idmap/** If we entered with the MMU and caches on, clean the ID mapped part* of the primary boot code to the PoC so we can safely execute it with* the MMU off.* 如果我们在 MMU 和缓存打开的情况下输入,请清理主启动代码的 ID 映射部分到 PoC,* 以便我们可以在关闭 MMU 的情况下安全地执行它。*/cbz	x19, 0fadrp	x0, __idmap_text_startadr_l	x1, __idmap_text_endadr_l	x2, dcache_clean_pocblr	x2
0:	mov	x0, x19bl	init_kernel_el			// w0=cpu_boot_modemov	x20, x0/** The following calls CPU setup code, see arch/arm64/mm/proc.S for* details.* 下面调用 CPU 设置代码,请参阅 arch/arm64/mm/proc.S 了解详细信息。* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.* 在返回时,CPU将准备好MMU被打开,TCR将被设置。*/
#if VA_BITS > 48mrs_s	x0, SYS_ID_AA64MMFR2_EL1tst	x0, #0xf << ID_AA64MMFR2_EL1_VARange_SHIFTmov	x0, #VA_BITSmov	x25, #VA_BITS_MINcsel	x25, x25, x0, eqmov	x0, x25
#endifbl	__cpu_setup			// initialise processor 初始化处理器b	__primary_switch
SYM_CODE_END(primary_entry)

这段代码是主要的入口点函数 primary_entry,在启动过程中执行一系列操作将控制权切换到内核

以下是对每行代码的解释:

SYM_CODE_START(primary_entry)

这行代码表示这是一个本地符号,标识 primary_entry 函数的开始位置

bl	record_mmu_state
bl	preserve_boot_args
bl	create_idmap

这三行代码调用了三个不同的函数。首先调用 record_mmu_state 函数来记录 MMU 的状态,然后调用 preserve_boot_args 函数来保留引导参数,最后调用 create_idmap 函数来创建 ID 映射(identity mapping)。

cbz	x19, 0f
adrp	x0, __idmap_text_start
adr_l	x1, __idmap_text_end
adr_l	x2, dcache_clean_poc
blr	x2
0:	mov	x0, x19
bl	init_kernel_el			// w0=cpu_boot_mode
mov	x20, x0

这几行代码根据 x19 寄存器的值进行条件分支。如果 x19 为零(等于零),则跳转到标号 0 处。在标号 0 处,会将 __idmap_text_start__idmap_text_end 地址计算给寄存器 x0x1,并将 dcache_clean_poc 地址计算给寄存器 x2,然后通过 blr 指令将控制权转移给 dcache_clean_poc 函数进行 ID 映射部分的清理操作。如果 x19 不为零,则继续执行下一行代码,将 x19 的值传递给 init_kernel_el 函数,并将函数返回值存储在 x20 寄存器中。

#if VA_BITS > 48
mrs_s	x0, SYS_ID_AA64MMFR2_EL1
tst	x0, #0xf << ID_AA64MMFR2_EL1_VARange_SHIFT
mov	x0, #VA_BITS
mov	x25, #VA_BITS_MIN
csel	x25, x25, x0, eq
mov	x0, x25
#endif
bl	__cpu_setup			// initialise processor
b	__primary_switch

这部分代码是一个条件编译块,在条件编译宏 VA_BITS 大于 48 时才会被编译。它首先读取 SYS_ID_AA64MMFR2_EL1 寄存器的值到 x0 中,然后根据位偏移掩码进行测试,并根据结果设置 x0 的值。接下来,调用 __cpu_setup 函数来初始化处理器。最后,通过无条件分支指令 b 转移到 __primary_switch 位置。

SYM_CODE_END(primary_entry)

这行代码表示 primary_entry 函数的结束位置

通过这段代码,可以看出 primary_entry 函数在启动过程中调用了几个辅助函数,其中包括记录 MMU 状态保留引导参数创建 ID 映射然后根据条件分支和处理器初始化,将控制权切换到内核的 __primary_switch 位置,最终完成内核的初始化和启动

三、record_mmu_state

SYM_CODE_START_LOCAL(record_mmu_state)mrs	x19, CurrentELcmp	x19, #CurrentEL_EL2mrs	x19, sctlr_el1b.ne	0fmrs	x19, sctlr_el2
0:
CPU_LE( tbnz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)
CPU_BE( tbz	x19, #SCTLR_ELx_EE_SHIFT, 1f	)tst	x19, #SCTLR_ELx_C		// Z := (C == 0)and	x19, x19, #SCTLR_ELx_M		// isolate M bitcsel	x19, xzr, x19, eq		// clear x19 if Zret/** Set the correct endianness early so all memory accesses issued* before init_kernel_el() occur in the correct byte order. Note that* this means the MMU must be disabled, or the active ID map will end* up getting interpreted with the wrong byte order.* 尽早设置正确的字节序,以便在 init_kernel_el() 之前发出的所有内存访问都以正确的字节顺序发生。* 请注意,这意味着必须禁用 MMU,否则活动 ID 映射最终将以错误的字节顺序进行解释。*/
1:	eor	x19, x19, #SCTLR_ELx_EEbic	x19, x19, #SCTLR_ELx_Mb.ne	2fpre_disable_mmu_workaroundmsr	sctlr_el2, x19b	3f
2:	pre_disable_mmu_workaroundmsr	sctlr_el1, x19
3:	isbmov	x19, xzrret
SYM_CODE_END(record_mmu_state)

这段代码是一个本地符号 record_mmu_state,该函数的主要目的是记录 MMU(内存管理单元)的状态。下面是对每行代码的解释:

SYM_CODE_START_LOCAL(record_mmu_state)

这行代码表示这是一个本地符号,用于定义 record_mmu_state 函数的开始位置

mrs x19, CurrentEL
cmp x19, #CurrentEL_EL2

这两行代码将当前异常级别的值读取到寄存器 x19 中,并与宏 CurrentEL_EL2 进行比较,以检查是否处于 EL2 异常级别

mrs x19, sctlr_el1
b.ne 0f
mrs x19, sctlr_el2
0:

这几行代码根据上述比较结果,如果不在 EL2 异常级别,则将 sctlr_el1 寄存器的值读取到寄存器 x19 中;否则,跳转到标号 0 处并将 sctlr_el2 寄存器的值读取到寄存器 x19 中。

CPU_LE( tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f )
CPU_BE( tbz x19, #SCTLR_ELx_EE_SHIFT, 1f )

这两行代码根据 CPU 的大小端模式(little-endianbig-endian),分别进行条件分支。根据 SCTLR_ELx_EE 寄存器的值,如果满足条件,则跳转到标号 1 处。

tst x19, #SCTLR_ELx_C          // Z := (C == 0)
and x19, x19, #SCTLR_ELx_M     // isolate M bit
csel x19, xzr, x19, eq         // clear x19 if Z
ret

这几行代码进行位操作条件选择,并最终返回结果。它们根据 SCTLR_ELx_C 寄存器的值执行相应的操作,并将结果存储在寄存器 x19 中,然后通过 ret 指令返回函数。

1: eor x19, x19, #SCTLR_ELx_EE
bic x19, x19, #SCTLR_ELx_M
b.ne 2f
pre_disable_mmu_workaround
msr sctlr_el2, x19
b 3f
2: pre_disable_mmu_workaround
msr sctlr_el1, x19
3: isb
mov x19, xzr
ret

这部分代码根据条件分支进行不同的操作根据前面的条件比较结果,它们对寄存器 x19 进行位操作和函数调用,并最终返回结果。具体操作包括将 SCTLR_ELx_EE 寄存器的值与 SCTLR_ELx_M 寄存器的值进行异或清除操作,然后根据条件选择将结果写入 sctlr_el2sctlr_el1 寄存器。最后通过 isb 指令刷新指令流水线,并将寄存器 x19 设置为零,并通过 ret 指令返回函数。

SYM_CODE_END(record_mmu_state)

这行代码表示 record_mmu_state 函数的结束位置

通过这段代码,可以看出该函数根据当前的异常级别和 CPU 的大小端模式获取相应的寄存器值,并进行一系列位操作条件分支记录 MMU 状态。具体的操作和处理根据不同的条件和配置进行调整,以满足特定的需求和约束。

四、preserve_boot_args

/** Preserve the arguments passed by the bootloader in x0 .. x3*/
SYM_CODE_START_LOCAL(preserve_boot_args)mov	x21, x0				// x21=FDTadr_l	x0, boot_args			// record the contents ofstp	x21, x1, [x0]			// x0 .. x3 at kernel entrystp	x2, x3, [x0, #16]cbnz	x19, 0f				// skip cache invalidation if MMU is ondmb	sy				// needed before dc ivac with// MMU offadd	x1, x0, #0x20			// 4 x 8 bytesb	dcache_inval_poc		// tail call
0:	str_l   x19, mmu_enabled_at_boot, x0ret
SYM_CODE_END(preserve_boot_args)

这段代码是一个本地符号 preserve_boot_args,其主要目的是保留引导加载程序传递给内核的参数。下面是对每行代码的解释:

/** Preserve the arguments passed by the bootloader in x0 .. x3* 在 x0 .. x3 中保留引导加载进程传递的参数*/

此注释指出了该函数的目标,即保留由引导加载程序传递给内核的参数(保存在 x0x3 寄存器中)。

SYM_CODE_START_LOCAL(preserve_boot_args)

这行代码表示这是一个本地符号,用于定义 preserve_boot_args 函数的开始位置

mov x21, x0              // x21=FDT

x0 的值(引导加载程序传递的第一个参数)复制到寄存器 x21 中,以便进一步处理。

adr_l x0, boot_args       // record the contents of
stp x21, x1, [x0]        // x0 .. x3 at kernel entry
stp x2, x3, [x0, #16]

这几行代码使用 adr_l 指令将 boot_args 地址加载到寄存器 x0 中,并使用 stp 指令将 x21x1x2x3 的值按顺序保存到 boot_args 地址所指向的内存中。

cbnz x19, 0f             // skip cache invalidation if MMU is on
dmb sy                  // needed before dc ivac with// MMU off

根据 x19 的值进行条件分支。如果 x19 不为零,则跳转到标号 0 处。在标号 0 处,执行 dmb sy 指令,该指令用于在 MMU 关闭时,在执行 dc ivac 操作之前保证数据的内存一致性

add x1, x0, #0x20       // 4 x 8 bytes
b dcache_inval_poc      // tail call

计算出新的地址(x0 + 0x20)并保存到寄存器 x1 中,然后使用尾调用方式跳转到 dcache_inval_poc 函数,该函数用于清除缓存中的数据以确保数据的一致性

0: str_l x19, mmu_enabled_at_boot, x0
ret

在标号 0 处,将 x19 的值保存到内存中的 mmu_enabled_at_boot 地址处,并通过 ret 指令返回函数。

SYM_CODE_END(preserve_boot_args)

这行代码表示 preserve_boot_args 函数的结束位置

通过这段代码,可以看出该函数的主要作用是保留引导加载程序传递给内核的参数,并记录这些参数的值。此外,根据 MMU 的状态,还包括对缓存进行清理和一致性操作,以确保数据的正确性和可靠性

五、create_idmap

SYM_FUNC_START_LOCAL(create_idmap)mov	x28, lr/** The ID map carries a 1:1 mapping of the physical address range* covered by the loaded image, which could be anywhere in DRAM. This* means that the required size of the VA (== PA) space is decided at* boot time, and could be more than the configured size of the VA* space for ordinary kernel and user space mappings.* ID 映射带有加载映像所覆盖的物理地址范围的 1:1 映射,该映射可能位于 DRAM 中的任何位置。* 这意味着所需的 VA (== PA) 空间大小是在引导时确定的,并且可能大于普通内核和用户空间映射的 VA 空间的配置大小。** There are three cases to consider here:* 这里有三种情况需要考虑:* - 39 <= VA_BITS < 48, and the ID map needs up to 48 VA bits to cover*   the placement of the image. In this case, we configure one extra*   level of translation on the fly for the ID map only. (This case*   also covers 42-bit VA/52-bit PA on 64k pages).* - 39 = VA BITS 48, ID映射需要多达48个VA BITS来覆盖图像的位置。* 在本例中,我们仅为ID映射动态地配置了一个额外的转换级别。(本例还包括64k页上的42位va52位PA)。** - VA_BITS == 48, and the ID map needs more than 48 VA bits. This can*   only happen when using 64k pages, in which case we need to extend*   the root level table rather than add a level. Note that we can*   treat this case as 'always extended' as long as we take care not*   to program an unsupported T0SZ value into the TCR register.* - VA BITS == 48, ID映射需要大于48个VA位。这只有在使用64k页时才会发生,* 在这种情况下,我们需要扩展根级表,而不是添加一个级别。* 请注意,只要我们注意不将不支持的T0SZ值编程到TCR寄存器中,我们就可以将这种情况视为“始终扩展”。** - Combinations that would require two additional levels of*   translation are not supported, e.g., VA_BITS==36 on 16k pages, or*   VA_BITS==39/4k pages with 5-level paging, where the input address*   requires more than 47 or 48 bits, respectively.* - 不支持需要两个额外转换级别的组合,* 例如,16k 页上的 VA_BITS==36,或 5 级分页的 VA_BITS==39/4k 页面,其中输入地址分别需要超过 47 位或 48 位。*/
#if (VA_BITS < 48)
#define IDMAP_PGD_ORDER	(VA_BITS - PGDIR_SHIFT)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)/** If VA_BITS < 48, we have to configure an additional table level.* First, we have to verify our assumption that the current value of* VA_BITS was chosen such that all translation levels are fully* utilised, and that lowering T0SZ will always result in an additional* translation level to be configured.* 如果VA_BITS < 48,我们必须配置一个额外的表级别。 * 首先,我们必须验证我们的假设,即选择VA_BITS的当前值,以便充分利用所有翻译级别,* 并且降低 T0SZ 将始终导致要配置额外的转换级别。*/
#if VA_BITS != EXTRA_SHIFT
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endif
#else
#define IDMAP_PGD_ORDER	(PHYS_MASK_SHIFT - PGDIR_SHIFT)
#define EXTRA_SHIFT/** If VA_BITS == 48, we don't have to configure an additional* translation level, but the top-level table has more entries.* 如果VA BITS == 48,我们不需要配置额外的转换级别,但是顶级表有更多的条目。*/
#endifadrp	x0, init_idmap_pg_diradrp	x3, _textadrp	x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZEmov	x7, SWAPPER_RX_MMUFLAGSmap_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT/* Remap the kernel page tables r/w in the ID map *//* 在ID映射中重新映射内核页表 */adrp	x1, _textadrp	x2, init_pg_diradrp	x3, init_pg_endbic	x4, x2, #SWAPPER_BLOCK_SIZE - 1mov	x5, SWAPPER_RW_MMUFLAGSmov	x6, #SWAPPER_BLOCK_SHIFTbl	remap_region/* Remap the FDT after the kernel image *//* 在内核映像之后重新映射FDT */adrp	x1, _textadrp	x22, _end + SWAPPER_BLOCK_SIZEbic	x2, x22, #SWAPPER_BLOCK_SIZE - 1bfi	x22, x21, #0, #SWAPPER_BLOCK_SHIFT		// remapped FDT address 重映射FDT地址add	x3, x2, #MAX_FDT_SIZE + SWAPPER_BLOCK_SIZEbic	x4, x21, #SWAPPER_BLOCK_SIZE - 1mov	x5, SWAPPER_RW_MMUFLAGSmov	x6, #SWAPPER_BLOCK_SHIFTbl	remap_region/** Since the page tables have been populated with non-cacheable* accesses (MMU disabled), invalidate those tables again to* remove any speculatively loaded cache lines.* 由于页表已填充了不可缓存的访问(已禁用 MMU),因此请再次使这些表失效以删除任何推测加载的缓存行*/cbnz	x19, 0f				// skip cache invalidation if MMU is on 如果MMU打开,则跳过缓存无效dmb	syadrp	x0, init_idmap_pg_diradrp	x1, init_idmap_pg_endbl	dcache_inval_poc
0:	ret	x28
SYM_FUNC_END(create_idmap)

这段代码是用于在Linux内核中创建ID映射表的函数create_idmap。以下对每行代码进行解释:

SYM_FUNC_START_LOCAL(create_idmap)mov	x28, lr

这两行代码定义了一个本地符号保存链接寄存器lr(返回地址)到寄存器x28

#if (VA_BITS < 48)

这行代码是一个条件编译指令,判断虚拟地址位数VA_BITS是否小于48。

#define IDMAP_PGD_ORDER	(VA_BITS - PGDIR_SHIFT)
#define EXTRA_SHIFT	(PGDIR_SHIFT + PAGE_SHIFT - 3)

这两行代码根据条件编译的结果定义了宏,计算ID映射表页目录级别额外位移

adrp	x0, init_idmap_pg_dir
adrp	x3, _text
adrp	x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
mov	x7, SWAPPER_RX_MMUFLAGSmap_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT

这几行代码使用adrpmov指令加载一些变量地址,并调用了map_memory函数,将这些地址按照一定的映射关系进行映射。

adrp	x1, _text
adrp	x2, init_pg_dir
adrp	x3, init_pg_end
bic	x4, x2, #SWAPPER_BLOCK_SIZE - 1
mov	x5, SWAPPER_RW_MMUFLAGS
mov	x6, #SWAPPER_BLOCK_SHIFT
bl	remap_region

这几行代码同样使用adrpmov指令加载一些变量地址,并调用了remap_region函数重新映射内核页表

adrp	x1, _text
adrp	x22, _end + SWAPPER_BLOCK_SIZE
bic	x2, x22, #SWAPPER_BLOCK_SIZE - 1
bfi	x22, x21, #0, #SWAPPER_BLOCK_SHIFT		// remapped FDT address
add	x3, x2, #MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
bic	x4, x21, #SWAPPER_BLOCK_SIZE - 1
mov	x5, SWAPPER_RW_MMUFLAGS
mov	x6, #SWAPPER_BLOCK_SHIFT
bl	remap_region

这几行代码同样使用adrpmov指令加载一些变量地址,并调用了remap_region函数重新映射文件描述符表(FDT)

cbnz	x19, 0f				// skip cache invalidation if MMU is on
dmb	syadrp	x0, init_idmap_pg_dir
adrp	x1, init_idmap_pg_end
bl	dcache_inval_poc
0:	ret	x28
SYM_FUNC_END(create_idmap)

这几行代码用于在 MMU 开启时执行缓存失效操作,在 MMU 关闭时直接跳过。然后,通过bl指令调用了dcache_inval_poc函数,进一步执行缓存失效操作。最后,通过ret指令返回之前保存的链接寄存器lr中的值。

综上所述,这段代码的主要功能是创建 ID 映射表,并对一些关键内存区域进行重新映射缓存失效操作。

六、init_kernel_el

/** Starting from EL2 or EL1, configure the CPU to execute at the highest* reachable EL supported by the kernel in a chosen default state. If dropping* from EL2 to EL1, configure EL2 before configuring EL1.* 从EL2或EL1开始,将CPU配置为在所选的默认状态下以内核支持的最高可达EL执行。* 如果从EL2降为EL1,请先配置EL2,再配置EL1。** Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if* SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.* 由于我们不能总是依赖 ERET 同步写入系统(例如,如果 SCTLR_ELx.EOS 是明确的),我们将 ISB 放在 ERET 之前。** Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in x0 if* booted in EL1 or EL2 respectively, with the top 32 bits containing* potential context flags. These flags are *not* stored in __boot_cpu_mode.* 如果分别在 EL1 或 EL2 中引导,则在 x0 中返回 BOOT_CPU_MODE_EL1 或 BOOT_CPU_MODE_EL2,* 前 32 位包含潜在的上下文标志。这些标志不存储在__boot_cpu_mode中。** x0: whether we are being called from the primary boot path with the MMU on* x0: 在MMU打开的情况下,我们是否从主引导路径被调用*/
SYM_FUNC_START(init_kernel_el)mrs	x1, CurrentELcmp	x1, #CurrentEL_EL2b.eq	init_el2SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)mov_q	x0, INIT_SCTLR_EL1_MMU_OFFpre_disable_mmu_workaroundmsr	sctlr_el1, x0isbmov_q	x0, INIT_PSTATE_EL1msr	spsr_el1, x0msr	elr_el1, lrmov	w0, #BOOT_CPU_MODE_EL1eretSYM_INNER_LABEL(init_el2, SYM_L_LOCAL)msr	elr_el2, lr// clean all HYP code to the PoC if we booted at EL2 with the MMU oncbz	x0, 0fadrp	x0, __hyp_idmap_text_startadr_l	x1, __hyp_text_endadr_l	x2, dcache_clean_pocblr	x2
0:mov_q	x0, HCR_HOST_NVHE_FLAGSmsr	hcr_el2, x0isbinit_el2_state/* Hypervisor stub */adr_l	x0, __hyp_stub_vectorsmsr	vbar_el2, x0isbmov_q	x1, INIT_SCTLR_EL1_MMU_OFF/** Fruity CPUs seem to have HCR_EL2.E2H set to RES1,* making it impossible to start in nVHE mode. Is that* compliant with the architecture? Absolutely not!*/mrs	x0, hcr_el2and	x0, x0, #HCR_E2Hcbz	x0, 1f/* Set a sane SCTLR_EL1, the VHE way */pre_disable_mmu_workaroundmsr_s	SYS_SCTLR_EL12, x1mov	x2, #BOOT_CPU_FLAG_E2Hb	2f1:pre_disable_mmu_workaroundmsr	sctlr_el1, x1mov	x2, xzr
2:mov	w0, #BOOT_CPU_MODE_EL2orr	x0, x0, x2eret
SYM_FUNC_END(init_kernel_el)

这段代码是用于初始化内核执行级别(EL)的函数init_kernel_el。以下对每行代码进行解释:

SYM_FUNC_START(init_kernel_el)mrs	x1, CurrentELcmp	x1, #CurrentEL_EL2b.eq	init_el2

这几行代码获取当前的执行级别(EL)到寄存器x1,并与常量CurrentEL_EL2比较。如果相等,则跳转到标签init_el2处理 EL2 的初始化。

SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)mov_q	x0, INIT_SCTLR_EL1_MMU_OFFpre_disable_mmu_workaroundmsr	sctlr_el1, x0isbmov_q	x0, INIT_PSTATE_EL1msr	spsr_el1, x0msr	elr_el1, lrmov	w0, #BOOT_CPU_MODE_EL1eret

这几行代码定义了一个内部标签init_el1,将寄存器 x0 设置为一个控制 EL1 配置的值,禁用 MMU 前的工作,使用msr指令将x0的值写入sctlr_el1系统寄存器,使用isb指令同步指令流水线,将寄存器x0设置为一个控制程序状态的值,使用msr指令将x0的值写入spsr_el1系统寄存器,使用msr指令将链接寄存器(返回地址)的值写入elr_el1系统寄存器,将寄存器w0设置为常量BOOT_CPU_MODE_EL1,使用eret指令从异常状态退出,并将处理器切换到 EL1 级别。

SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)msr	elr_el2, lr// clean all HYP code to the PoC if we booted at EL2 with the MMU oncbz	x0, 0fadrp	x0, __hyp_idmap_text_startadr_l	x1, __hyp_text_endadr_l	x2, dcache_clean_pocblr	x2
0:mov_q	x0, HCR_HOST_NVHE_FLAGSmsr	hcr_el2, x0isbinit_el2_state/* Hypervisor stub */adr_l	x0, __hyp_stub_vectorsmsr	vbar_el2, x0isbmov_q	x1, INIT_SCTLR_EL1_MMU_OFF/** Fruity CPUs seem to have HCR_EL2.E2H set to RES1,* making it impossible to start in nVHE mode. Is that* compliant with the architecture? Absolutely not!*/mrs	x0, hcr_el2and	x0, x0, #HCR_E2Hcbz	x0, 1f/* Set a sane SCTLR_EL1, the VHE way */pre_disable_mmu_workaroundmsr_s	SYS_SCTLR_EL12, x1mov	x2, #BOOT_CPU_FLAG_E2Hb	2f1:pre_disable_mmu_workaroundmsr	sctlr_el1, x1mov	x2, xzr
2:mov	w0, #BOOT_CPU_MODE_EL2orr	x0, x0, x2eret
SYM_FUNC_END(init_kernel_el)

这段代码定义了一个内部标签init_el2,使用msr指令将链接寄存器(返回地址)的值写入elr_el2系统寄存器。

然后,根据传入的参数判断是否为 EL2 级别,如果是,则执行一些清理操作并配置 EL2 的状态,将寄存器x0设置为常量HCR_HOST_NVHE_FLAGS的值,并使用msr指令将x0的值写入hcr_el2系统寄存器,使用isb指令同步指令流水线。接着,通过调用init_el2_state函数初始化 EL2 的状态,设置了 Hypervisor stub 的异常向量表,将寄存器x1设置为一个控制 EL1 配置的值。

接下来,代码检查了处理器的配置,如果发现处理器不支持在 VHE 模式下启动(具体通过HCR_EL2.E2H寄存器判断),则使用非 VHE 方式设置合理的EL1配置。

最后,使用pre_disable_mmu_workaround函数禁用 MMU 前的工作,将寄存器w0设置为常量BOOT_CPU_MODE_EL2,并使用orr指令与寄存器x2进行或操作,将标志位信息添加到返回值中。最后,使用eret指令从异常状态退出,并将处理器切换到 EL2 级别。

综上所述,这段代码的主要功能是根据当前的执行级别(EL)和参数设置内核执行的EL级别,并进行相应的初始化操作。

七、__cpu_setup

/**	__cpu_setup**	Initialise the processor for turning the MMU on.* 初始化处理器以启动MMU。** Input:*	x0 - actual number of VA bits (ignored unless VA_BITS > 48)* Output:*	Return in x0 the value of the SCTLR_EL1 register.*/.pushsection ".idmap.text", "a"
SYM_FUNC_START(__cpu_setup)tlbi	vmalle1				// Invalidate local TLBdsb	nshmov	x1, #3 << 20msr	cpacr_el1, x1			// Enable FP/ASIMDmov	x1, #1 << 12			// Reset mdscr_el1 and disablemsr	mdscr_el1, x1			// access to the DCC from EL0isb					// Unmask debug exceptions now,enable_dbg				// since this is per-cpureset_pmuserenr_el0 x1			// Disable PMU access from EL0reset_amuserenr_el0 x1			// Disable AMU access from EL0/** Default values for VMSA control registers. These will be adjusted* below depending on detected CPU features.* VMSA控制寄存器的默认值。这些将根据检测到的CPU特性在下面进行调整。*/mair	.req	x17tcr	.req	x16mov_q	mair, MAIR_EL1_SETmov_q	tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS | TCR_MTE_FLAGStcr_clear_errata_bits tcr, x9, x5#ifdef CONFIG_ARM64_VA_BITS_52sub		x9, xzr, x0add		x9, x9, #64tcr_set_t1sz	tcr, x9
#elseidmap_get_t0sz	x9
#endiftcr_set_t0sz	tcr, x9/** Set the IPS bits in TCR_EL1.* 设置TCR EL1的IPS位。*/tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6
#ifdef CONFIG_ARM64_HW_AFDBM/** Enable hardware update of the Access Flags bit.* Hardware dirty bit management is enabled later,* via capabilities.* 启用Access Flags位的硬件更新。硬件脏位管理稍后通过功能启用。*/mrs	x9, ID_AA64MMFR1_EL1and	x9, x9, #0xfcbz	x9, 1forr	tcr, tcr, #TCR_HA		// hardware Access flag update 硬件访问标志更新
1:
#endif	/* CONFIG_ARM64_HW_AFDBM */msr	mair_el1, mairmsr	tcr_el1, tcr/** Prepare SCTLR*/mov_q	x0, INIT_SCTLR_EL1_MMU_ONret					// return to head.S.unreq	mair.unreq	tcr
SYM_FUNC_END(__cpu_setup)

这段代码是一个名为__cpu_setup的函数,用于初始化处理器以开启MMU功能。以下对每行代码进行解释:

.pushsection ".idmap.text", "a"
SYM_FUNC_START(__cpu_setup)

这两行代码将函数放置在.idmap.text节中,并开始__cpu_setup函数。

tlbi	vmalle1				// Invalidate local TLB
dsb	nsh

这两行代码执行TLBI指令来使本地TLB无效,并使用DSB指令确保操作执行顺序正确

mov	x1, #3 << 20
msr	cpacr_el1, x1			// Enable FP/ASIMD

这两行代码将寄存器x1设置为控制协处理器访问权限的值,并使用msr指令将其写入cpacr_el1系统寄存器,以启用浮点和SIMD指令功能。

mov	x1, #1 << 12			// Reset mdscr_el1 and disable
msr	mdscr_el1, x1			// access to the DCC from EL0
isb					// Unmask debug exceptions now,
enable_dbg				// since this is per-cpu

这些代码将寄存器x1设置为控制调试器及其相关功能的mdscr_el1寄存器的值,并使用msr指令将其写入mdscr_el1系统寄存器以重置相关配置。然后,使用isb指令同步指令流水线,通过enable_dbg函数启用调试异常。

reset_pmuserenr_el0 x1			// Disable PMU access from EL0
reset_amuserenr_el0 x1			// Disable AMU access from EL0

这两行代码使用自定义的宏函数禁用来自EL0级别的性能计数器监视器的访问。

mair	.req	x17
tcr	.req	x16
mov_q	mair, MAIR_EL1_SET
mov_q	tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS | TCR_MTE_FLAGS

这几行代码定义了一些寄存器常量,其中包括mairtcr以及一系列用于构建tcr_el1寄存器值的掩码常量

tcr_clear_errata_bits tcr, x9, x5

这行代码使用一个自定义的宏函数清除TCR_EL1寄存器中已知的硬件错误的位

#ifdef CONFIG_ARM64_VA_BITS_52
sub		x9, xzr, x0
add		x9, x9, #64
tcr_set_t1sz	tcr, x9
#else
idmap_get_t0sz	x9
#endif
tcr_set_t0sz	tcr, x9

这些代码根据配置选项判断是否使用52位的虚拟地址空间。如果是,则将寄存器x9设置为一个控制TCR_EL1寄存器中T1SZ字段的值;否则,调用自定义的函数获取t0sz的值,并将其写入tcr_el1寄存器的相应字段(T0SZ)。

tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6
#ifdef CONFIG_ARM64_HW_AFDBM
mrs	x9, ID_AA64MMFR1_EL1
and	x9, x9, #0xf
cbz	x9, 1f
orr	tcr, tcr, #TCR_HA		// hardware Access flag update
1:
#endif	/* CONFIG_ARM64_HW_AFDBM */
msr	mair_el1, mair
msr	tcr_el1, tcr

这些代码使用自定义的宏函数计算物理地址大小,并将计算结果写入tcr_el1寄存器的相应字段(IPS)。
如果启用了ARM64硬件更新访问标志位(AFDBM)功能,这部分代码会读取ID_AA64MMFR1_EL1系统寄存器的值,然后根据配置选项进行处理,并将得到的掩码值与tcr寄存器进行逻辑或操作,以使硬件能够更新Access Flags位。最后,使用msr指令将mairtcr的值分别写入mair_el1tcr_el1系统寄存器。

mov_q	x0, INIT_SCTLR_EL1_MMU_ON
ret					// return to head.S.unreq	mair
.unreq	tcr
SYM_FUNC_END(__cpu_setup)

这些代码将寄存器x0设置为一个常量值INIT_SCTLR_EL1_MMU_ON,该值将作为返回值存储在x0寄存器中,并通过ret指令返回到汇编语言代码的起点。

总结而言,这段代码是用于初始化处理器开启MMU功能的函数。它包括**TLB的使能**、协处理器权限的设置调试器相关功能的配置性能计数器和监视器的禁用VMSA控制寄存器的设置虚拟地址空间大小的计算以及最终的SCTLR_EL1寄存器的初始化

八、__primary_switch

SYM_FUNC_START_LOCAL(__primary_switch)adrp	x1, reserved_pg_diradrp	x2, init_idmap_pg_dirbl	__enable_mmu
#ifdef CONFIG_RELOCATABLEadrp	x23, KERNEL_STARTand	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASEmov	x0, x22adrp	x1, init_pg_endmov	sp, x1mov	x29, xzrbl	__pi_kaslr_early_initand	x24, x0, #SZ_2M - 1		// capture memstart offset seed 捕获memstart偏移种子bic	x0, x0, #SZ_2M - 1orr	x23, x23, x0			// record kernel offset 记录内核偏移量
#endif
#endifbl	clear_page_tablesbl	create_kernel_mappingadrp	x1, init_pg_dirload_ttbr1 x1, x1, x2
#ifdef CONFIG_RELOCATABLEbl	__relocate_kernel
#endifldr	x8, =__primary_switchedadrp	x0, KERNEL_START		// __pa(KERNEL_START)br	x8
SYM_FUNC_END(__primary_switch)

这段代码是一个名为__primary_switch的本地函数(SYM_FUNC_START_LOCAL表示本地函数),以下对每行代码进行解释:

SYM_FUNC_START_LOCAL(__primary_switch)

这行代码开始了__primary_switch函数。

adrp	x1, reserved_pg_dir
adrp	x2, init_idmap_pg_dir
bl	__enable_mmu

这几行代码使用adrp指令将reserved_pg_dirinit_idmap_pg_dir地址分别加载到寄存器x1x2中,然后调用__enable_mmu函数启用MMU功能

#ifdef CONFIG_RELOCATABLEadrp	x23, KERNEL_STARTand	x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASEmov	x0, x22adrp	x1, init_pg_endmov	sp, x1mov	x29, xzrbl	__pi_kaslr_early_initand	x24, x0, #SZ_2M - 1		// capture memstart offset seedbic	x0, x0, #SZ_2M - 1orr	x23, x23, x0			// record kernel offset
#endif
#endif

这部分代码是根据配置选项进行条件编译的。如果启用可重定位内核CONFIG_RELOCATABLE),则会执行相应的操作。首先,使用adrp指令将KERNEL_START的地址加载到寄存器x23中,并使用and指令和掩码MIN_KIMG_ALIGN - 1对其进行位与运算。如果启用了随机基址CONFIG_RANDOMIZE_BASE),则将x22的值移动到x0寄存器,并将init_pg_end的地址加载到x1寄存器中。然后,将栈指针sp设置为x1寄存器的值,将x29寄存器(帧指针)清零,并调用__pi_kaslr_early_init函数执行早期地址空间布局随机化(KASLR)的初始化操作。接下来,使用and指令和掩码SZ_2M - 1x0进行位与运算,并将结果保存在x24寄存器中,以记录起始内存偏移种子。再次使用bic指令将x0与掩码SZ_2M - 1进行位清除操作。最后,使用orr指令将x0x23进行位或运算,将内核偏移量记录在x23寄存器中。

bl	clear_page_tables
bl	create_kernel_mapping

这两行代码分别调用clear_page_tablescreate_kernel_mapping函数。clear_page_tables函数用于清除页表,而create_kernel_mapping函数用于创建内核映射

adrp	x1, init_pg_dir
load_ttbr1 x1, x1, x2

这几行代码使用adrp指令将init_pg_dir的地址加载到寄存器x1中,然后调用load_ttbr1宏函数,将x1x1x2作为参数,用于加载TTBR1Translation Table Base Register 1)寄存器。

#ifdef CONFIG_RELOCATABLEbl	__relocate_kernel
#endif

这部分代码也是根据配置选项进行条件编译的。如果启用了可重定位内核(CONFIG_RELOCATABLE),则会调用__relocate_kernel函数,该函数用于重新定位内核

ldr	x8, =__primary_switched
adrp	x0, KERNEL_START		// __pa(KERNEL_START)
br	x8

这几行代码使用ldr指令__primary_switched的地址加载到寄存器x8,然后使用adrp指令KERNEL_START的地址加载到寄存器x0(此处的__pa(KERNEL_START)表示获取KERNEL_START符号的物理地址)。最后,使用br指令跳转到x8寄存器的值所指示的地址。

总结而言,这段代码是一个处理器切换函数__primary_switch,其中包括启用MMU功能初始化内核地址空间布局随机化清除页表创建内核映射加载TTBR1寄存器以及可选重新定位内核等操作。最后,通过跳转到__primary_switched函数来完成处理器切换。

1、__primary_switched

/** The following fragment of code is executed with the MMU enabled.* 下面的代码片段在启用MMU的情况下执行。**   x0 = __pa(KERNEL_START)*/
SYM_FUNC_START_LOCAL(__primary_switched)adr_l	x4, init_taskinit_cpu_task x4, x5, x6adr_l	x8, vectors			// load VBAR_EL1 with virtualmsr	vbar_el1, x8			// vector table addressisbstp	x29, x30, [sp, #-16]!mov	x29, spstr_l	x21, __fdt_pointer, x5		// Save FDT pointerldr_l	x4, kimage_vaddr		// Save the offset betweensub	x4, x4, x0			// the kernel virtual andstr_l	x4, kimage_voffset, x5		// physical mappingsmov	x0, x20bl	set_cpu_boot_mode_flag// Clear BSSadr_l	x0, __bss_startmov	x1, xzradr_l	x2, __bss_stopsub	x2, x2, x0bl	__pi_memsetdsb	ishst				// Make zero page visible to PTW#if VA_BITS > 48adr_l	x8, vabits_actual		// Set this early so KASAN early initstr	x25, [x8]			// ... observes the correct valuedc	civac, x8			// Make visible to booting secondaries
#endif#ifdef CONFIG_RANDOMIZE_BASEadrp	x5, memstart_offset_seed	// Save KASLR linear map seedstrh	w24, [x5, :lo12:memstart_offset_seed]
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)bl	kasan_early_init
#endifmov	x0, x21				// pass FDT address in x0bl	early_fdt_map			// Try mapping the FDT earlymov	x0, x20				// pass the full boot statusbl	init_feature_override		// Parse cpu feature overrides
#ifdef CONFIG_UNWIND_PATCH_PAC_INTO_SCSbl	scs_patch_vmlinux
#endifmov	x0, x20bl	finalise_el2			// Prefer VHE if possibleldp	x29, x30, [sp], #16bl	start_kernelASM_BUG()
SYM_FUNC_END(__primary_switched)

这段代码是在启用MMU的情况下执行的。以下对每行代码进行解释:

SYM_FUNC_START_LOCAL(__primary_switched)

这行代码开始了名为__primary_switched的本地函数。

adr_l	x4, init_task
init_cpu_task x4, x5, x6

这两行代码使用adr_l指令将init_task的地址加载到寄存器x4中,然后调用init_cpu_task宏将x4x5x6作为参数初始化CPU任务

adr_l	x8, vectors
msr	vbar_el1, x8
isb

这几行代码使用adr_l指令vectors的地址加载到寄存器x8,然后使用msr指令将x8的值写入VBAR_EL1寄存器,即异常向量表地址寄存器。最后,使用isb指令刷新指令流水线

stp	x29, x30, [sp, #-16]!
mov	x29, sp

这几行代码将帧指针寄存器x29链接寄存器x30保存在栈上,然后将栈指针寄存器sp的值赋给x29,建立新的帧。

str_l	x21, __fdt_pointer, x5

这行代码使用str_l指令将寄存器x21中的值存储到__fdt_pointer变量的地址中。

ldr_l	x4, kimage_vaddr
sub	x4, x4, x0
str_l	x4, kimage_voffset, x5

这几行代码使用ldr_l指令kimage_vaddr的值加载到寄存器x4,然后使用sub指令计算内核虚拟和物理地址之间的偏移量,并将结果存储在kimage_voffset变量中

mov	x0, x20
bl	set_cpu_boot_mode_flag

这两行代码将寄存器x20的值移动到x0寄存器中,并调用set_cpu_boot_mode_flag函数设置CPU引导模式标志。

adr_l	x0, __bss_start
mov	x1, xzr
adr_l	x2, __bss_stop
sub	x2, x2, x0
bl	__pi_memset
dsb	ishst

这几行代码使用adr_l指令将__bss_start__bss_stop的地址加载到寄存器x0x2中,然后使用sub指令计算BSS段的大小,并将结果存储在x2中。接着,调用__pi_memset函数将BSS段清零,并使用dsb指令确保对BSS段的修改对Page Table Walkers(PTW)可见。

#if VA_BITS > 48adr_l	x8, vabits_actualstr	x25, [x8]dc	civac, x8
#endif

这部分代码是根据虚拟地址位数进行条件编译的。如果虚拟地址位数超过48位,则使用adr_l指令将vabits_actual的地址加载到寄存器x8中,然后将寄存器x25的值存储到x8所指示的地址中,并使用dc指令刷新数据缓存

#ifdef CONFIG_RANDOMIZE_BASEadrp	x5, memstart_offset_seedstrh	w24, [x5, :lo12:memstart_offset_seed]
#endif

这部分代码是根据配置选项进行条件编译的。如果启用了随机基址CONFIG_RANDOMIZE_BASE),则会使用adrp指令将memstart_offset_seed的地址加载到寄存器x5中,然后使用strh指令将w24的低12位存储到x5所指示的地址中。

#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)bl	kasan_early_init
#endif

这部分代码也是根据配置选项进行条件编译的。如果启用了KASAN(内核地址检测和调试工具)的通用模式CONFIG_KASAN_GENERIC)或软件标记模式CONFIG_KASAN_SW_TAGS),则会调用kasan_early_init函数执行早期的**KASAN初始化操作**。

mov	x0, x21
bl	early_fdt_map

这两行代码将寄存器x21的值移动到x0寄存器中,并调用early_fdt_map函数尝试在早期映射FDT(设备树)。

mov	x0, x20
bl	init_feature_override

这两行代码将寄存器x20的值移动到x0寄存器中,并调用init_feature_override函数解析CPU特性的覆盖设置

#ifdef CONFIG_UNWIND_PATCH_PAC_INTO_SCSbl	scs_patch_vmlinux
#endif

这部分代码也是根据配置选项进行条件编译的。如果启用了将PAC修补应用到SCS(System Control Space)的功能(CONFIG_UNWIND_PATCH_PAC_INTO_SCS),则会调用scs_patch_vmlinux函数进行相应的操作。

mov	x0, x20
bl	finalise_el2

这两行代码将寄存器x20的值移动到x0寄存器中,并调用finalise_el2函数,用于在可能的情况下首选VHE(Virtualization Host Extensions)。

ldp	x29, x30, [sp], #16
bl	start_kernel

这两行代码从栈上恢复帧指针寄存器x29和链接寄存器x30的值,然后调用start_kernel函数启动内核

ASM_BUG()
SYM_FUNC_END(__primary_switched)

这两行代码使用ASM_BUG()宏来标记一个汇编错误,并结束__primary_switched函数的定义。

总结而言,这段代码是在启用MMU的情况下执行的处理器切换函数__primary_switched,其中包括初始化CPU任务、设置异常向量表地址、保存FDT指针、计算内核虚拟和物理地址偏移、清零BSS段、初始化KASAN、映射FDT、解析CPU特性覆盖设置等操作。最后,恢复寄存器并调用start_kernel函数启动内核。

九、总结

1、通过 __HEAD 将启动代码链接到文件开始位置。

2、调用 primary_entry ,进行初始化相关操作。

3、调用 __primary_switch - > __primary_switched 调用 start_kernel 函数。

4、start_kernel 函数进入 C 语言环境初始化内核。

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

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

相关文章

Vue2基础五、工程化开发

零、文章目录 Vue2基础五、工程化开发 1、工程化开发和脚手架 &#xff08;1&#xff09;开发 Vue 的两种方式 核心包传统开发模式&#xff1a;基于 html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xf…

【Python】数据分析+数据挖掘——探索Pandas中的索引与数据组织

前言 在数据科学和数据分析领域&#xff0c;Pandas是一个备受喜爱的Python库。它提供了丰富的数据结构和灵活的工具&#xff0c;帮助我们高效地处理和分析数据。其中&#xff0c;索引在Pandas中扮演着关键角色&#xff0c;它是一种强大的数据组织和访问机制&#xff0c;使我们…

【Unity2D】角色动画的切换

动画状态转换 第一种方法是设置一个中间状态&#xff0c;从中间状态向其余各种状态切换&#xff0c;且各状态向其他状态需要设置参数 实现动作转移时右键点击Make Transition即可 实现动画转移需要设置条件 点击一种动画到另一种动画的线 &#xff0c;然后点击加号添加Condi…

玩转LaTeX(三)【数学公式(基础)、​矩阵、多行公式】

数学公式基础 导言区&#xff08;引包&#xff09; \usepackage{amsmath} %带星号的eqution 正文区 \begin{document}%数学公式初步 \section{简介} \LaTeX{}将排版内容分为文本模式和数学模式。文本模式用于普通文本排版&#xff0c;数学模式用于数学公式排版。 …

【字节三面】41. 缺失的第一个正数

41. 缺失的第一个正数 解题思路 在原数组上进行操作 如果数字是2 将其放在索引为1的位置上数字x 放在索引为x - 1的位置上对于长度为n的数组 其中没有出现的最小正整数只能在[1,n 1]引入如果1 - n 这些数都出现了 那么答案就是n 1如果都没有出现完全 那么答案就在[1,n]中没…

【LeetCode】206.反转链表

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; …

【LeetCode 热题 100】矩阵 专题(大多原地算法,需要一定思维)

解题思路 在 代码注释中&#xff01; 文章目录 73. 矩阵置零54. 螺旋矩阵48. 旋转图像240. 搜索二维矩阵 II 73. 矩阵置零 class Solution { public:void setZeroes(vector<vector<int>>& matrix) {// 难点&#xff1a;原地算法// 直接复用 matrix 第一行 和 …

oracle,获取每日24*60,所有分钟数

前言&#xff1a; 为规范用户的时间录入&#xff0c;因此我们采用下拉的方式&#xff0c;让用户选择需要的时间&#xff0c;因此我们需要将一天24小时的时间拆分为类似00:00,00:01...23:00,23:01,23:59。因此我们需要生成24*601440行的下拉复选值。具体效果如下图所示。 思路 1…

libuv库学习笔记-processes

Processes libuv提供了相当多的子进程管理函数&#xff0c;并且是跨平台的&#xff0c;还允许使用stream&#xff0c;或者说pipe完成进程间通信。 在UNIX中有一个共识&#xff0c;就是进程只做一件事&#xff0c;并把它做好。因此&#xff0c;进程通常通过创建子进程来完成不…

微信小程序 background-image直接设置本地图片路径,编辑器正常显示,真机运行不显示解决方法

项目场景 微信小程序&#xff0c;设置background-image直接设置本地图片路径。 问题描述 编辑器正常显示&#xff0c;真机运行不显示 原因分析 background-image只能用网络url或者base64图片编码。 解决方案 1、将本地图片转为网络url后设置到background-image上 例如&…

nestjs+typeorm+mysql基本使用学习

初始化项目 安装依赖 npm i -g nest/cli 新建项目 nest new project-name 命令行创建 创建Controller&#xff1a;nest g co test 创建Module&#xff1a;nest g mo test 创建Service&#xff1a;nest g service test 请求创建 123123 接口文档swagger 安装依赖 npm…

【Golang 接口自动化03】 解析接口返回XML

目录 解析接口返回数据 定义结构体 解析函数&#xff1a; 测试 优化 资料获取方法 上一篇我们学习了怎么发送各种数据类型的http请求&#xff0c;这一篇我们来介绍怎么来解析接口返回的XML的数据。 解析接口返回数据 定义结构体 假设我们现在有一个接口返回的数据resp如…

❤ yarn 和npm 的使用

❤ yarn 和npm 的使用 yarn 版本1的使用 yarn 简介 Yarn是facebook发布的一款取代npm的包管理工具。 yarn特点&#xff1a; 1&#xff0c;速度超快。 Yarn 缓存了每个下载过的包&#xff0c;所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率&#xff0c;因…

C# 反射

反射的概念&#xff1a;C#通过类型&#xff08;Type&#xff09;来创建对象&#xff0c;调用对象中的方法&#xff0c;属性等信息&#xff1b;B超就是利用了反射原理将超声波打在人的肚子上&#xff0c;然后通过反射波进行体内器官的成员&#xff1b; 反射提供的类&#xff1a;…

【代理模式】了解篇:静态代理 动态代理~

目录 1、什么是代理模式&#xff1f; 2、静态代理 3、动态代理 3.1 JDK动态代理类 3.2 CGLIB动态代理类 4、JDK动态代理和CGLIB动态代理的区别&#xff1f; 1、什么是代理模式&#xff1f; 定义&#xff1a; 代理模式就是为其他对象提供一种代理以控制这个对象的访问。在某…

强引用和弱引用

什么是弱引用和强引用 强引用&#xff1a; JavaScript 中强引用&#xff1a;对象的引用在 JavaScript 中是强引用&#xff0c;也就是将一个引用对象通过变量或常量保存时&#xff0c;那么这个变量或常量就是强引用&#xff0c;这个对象就不会被回收。 弱引用&#xff1a; JavaS…

P1123 取数游戏(dfs,嘎嘎清晰)

1分析&#xff1a;这一题每个数是否选择会影响后面的选择情况&#xff0c;所以需要用一个数组来保存 所以状态为当前选到那个数&#xff0c;之前选的数的和以及之前每个数是否选了 之后直接搜索即可。尽管复杂度较高&#xff0c;但因为存在大量的不合法情况所以可以通过 时间复…

华为nat64配置

1.前期环境准备 环境拓扑 拓扑分为两个区域,左边为trust区域,使用IPv4地址互访,右边为untrust区域,使用IPv6地址互访 2.接口地址配置 pc1地址配置 pc2地址配置 FW接口配置 (1)首先进入防火墙配置界面 注:防火墙初始账号密码为user:admin,pwd:Admin@123,进入之后…

web攻击面试|网络渗透面试(三)

Web攻击大纲 常见Web攻击类型&#xff1a; SQL注入攻击&#xff1a;介绍SQL注入攻击的概念、原理和常见的攻击方式&#xff0c;如基于错误消息的注入、基于布尔盲注的注入等。解释攻击者如何利用SQL注入漏洞获取敏感信息或者对数据库进行恶意操作&#xff0c;并提供防御措施&a…

8.docker仓库

文章目录 Docker仓库本地私有仓库Docker HarborDocker harbor部署访问页面创建用户下载私有仓库镜像harbor同步 Docker仓库 本地私有仓库 ##先下载 registry 镜像docker pull registry##修改配置文件&#xff0c;在 daemon.json 文件中添加私有镜像仓库地址vim /etc/dock…