[Linux kernel] [ARM64] boot 流程梳理
一、启动汇编代码部分
0. 链接文件找代码段入口 – _text
arch/arm64/kernel/vmlinux.lds.S
ENTRY(_text)
. = KIMAGE_VADDR;.head.text : {_text = .;HEAD_TEXT}.text : ALIGN(SEGMENT_ALIGN) { /* Real text segment */_stext = .; /* Text and read-only data */IRQENTRY_TEXTSOFTIRQENTRY_TEXTENTRY_TEXTTEXT_TEXTSCHED_TEXTLOCK_TEXTKPROBES_TEXTHYPERVISOR_TEXT*(.gnu.warning)}. = ALIGN(SEGMENT_ALIGN);_etext = .; /* End of text section */
include/asm-generic/vmlinux.lds.h
/* Section used for early init (in .S files) */
#define HEAD_TEXT KEEP(*(.head.text))
/** If padding is applied before .head.text, virt<->phys conversions will fail.*/
ASSERT(_text == KIMAGE_VADDR, "HEAD is misaligned") // 校验内核入口地址是否正确ASSERT(swapper_pg_dir - reserved_pg_dir == RESERVED_SWAPPER_OFFSET,"RESERVED_SWAPPER_OFFSET is wrong!")#ifdef CONFIG_UNMAP_KERNEL_AT_EL0
ASSERT(swapper_pg_dir - tramp_pg_dir == TRAMP_SWAPPER_OFFSET,"TRAMP_SWAPPER_OFFSET is wrong!")
#endif
1. Kernel startup entry point – __HEAD
include/linux/init.h
#define __HEAD .section ".head.text","ax"
arch/arm64/kernel/head.S
/** Kernel startup entry point.* ---------------------------** The requirements are:* MMU = off, D-cache = off, I-cache = on or off,* x0 = physical address to the FDT blob.** 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.*/__HEAD/** DO NOT MODIFY. Image header expected by Linux boot-loaders.*/efi_signature_nop // special NOP to identity as PE/COFF executableb primary_entry // branch to kernel start, magic.quad 0 // Image load offset from start of RAM, little-endianle64sym _kernel_size_le // Effective size of kernel image, little-endianle64sym _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.__EFI_PE_HEADER.section ".idmap.text","a"
这段代码是内核启动的入口点。它定义了一些要求和操作。首先,对于内核启动入口点,需要满足以下条件:MMU(内存管理单元)处于关闭状态。
D-cache(数据缓存)处于关闭状态。
I-cache(指令缓存)可以在启动时处于打开或关闭状态。
寄存器 x0 中存储了 FDT(平台设备树) blob 的物理地址。
注释中提到,被调用保存的寄存器用于存储在启用 MMU 之前有用的变量。这些分配在入口例程中进行描述。代码中的一些关键部分包括:efi_signature_nop:标识为 PE/COFF 可执行文件的特殊 NOP(无操作码)。
b primary_entry:跳转到内核的起始地址,开始内核启动的魔术操作。
.quad 指令:用于定义保留的、未使用的空间。
.ascii 指令:用于定义特定的魔数标识符。
.long 指令:指定到 PE 头的偏移量。
最后,代码进入了 .idmap.text 段,该段是一个特别的代码段,用于创建 ID 映射。
2. primary_entry.
/** 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*/
这段注释列出了在主要的低级引导路径中使用的一些被调用保存的通用寄存器。每个寄存器都有其作用和使用范围。x19:从 primary_entry() 到 start_kernel(),用于判断是否开启了 MMU(内存管理单元)。
x20:从 primary_entry() 到 __primary_switch(),用于指示 CPU 的引导模式。
x21:从 primary_entry() 到 start_kernel(),用于传递启动时在 x0 中传递的 FDT(平台设备树)指针。
x22:从 create_idmap() 到 start_kernel(),用于存储设备树 blob 的 ID 映射虚拟地址。
x23:从 primary_entry() 到 start_kernel(),用于存储物理对齐错误或 KASLR(内核地址空间布局随机化)偏移量。
x24:在 __primary_switch() 中使用,用于线性映射的 KASLR 种子。
x25:从 primary_entry() 到 start_kernel(),用于存储支持的虚拟地址大小。
x28:在 create_idmap() 中使用,作为被调用保存的临时寄存器。
这些寄存器在引导过程中具有特定的功能和用途,并且由于它们是被调用保存的寄存器,必须在函数调用之间进行保存和恢复,以保证数据的正确性和稳定性。
下边代码片段中的cbz/adrp/adr_l/blr四条指令(宏)的用法可查阅如下wiki:
ARM64 指令用法整理
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映射部分,以确保在关闭MMU时能够安全地执行它。这段注释描述了以下操作:如果进入此代码段时MMU和缓存已经打开,那么需要将主引导代码的ID映射部分清除,以确保后续在关闭MMU时不会出现映射错误。具体操作可以是将主引导代码所在的地址范围与物理内存进行映射的关系解除,以使得在关闭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.* On return, the CPU will be ready for the MMU to be turned on and* the TCR will have been set.*/
#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 processorb __primary_switch
SYM_CODE_END(primary_entry)
这段代码是内核启动的入口点。首先,它调用了一些函数:record_mmu_state:记录 MMU 状态的函数。
preserve_boot_args:保留引导参数的函数。
create_idmap:创建 ID 映射的函数。
接下来,如果在启动时 MMU 和缓存已经打开,则会清除主要启动代码区域的 ID 映射部分,以便可以在关闭 MMU 的情况下安全地执行它。然后,代码调用 init_kernel_el 函数进行内核初始化,将 cpu_boot_mode 作为参数传递给该函数。返回值存储在寄存器 x20 中。接下来是一些 CPU 设置代码的调用,详细信息可以在 arch/arm64/mm/proc.S 文件中找到。执行这些代码后,CPU 将准备好打开 MMU,并且 TCR 已经被设置好。最后,代码跳转到 __primary_switch 处,表示内核启动的转换。
下面是对给定代码的逐句详细讲解:
SYM_CODE_START(primary_entry)
这是一个符号宏,表示代码的起始点。bl record_mmu_statebl preserve_boot_argsbl create_idmap
这里调用了三个函数,分别是 record_mmu_state、preserve_boot_args 和 create_idmap。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
这段代码首先检查寄存器 x19 是否为零,如果是(MMU && cache on的情况下,清除主引导代码的ID映射部分,以确保在关闭MMU时能够安全地执行它。),则跳转到标签 0(即跳过一段代码)。如果 x19 不为零,则执行以下指令:使用 adrp 指令将 __idmap_text_start 的地址所在的页基址保存在 x0。
使用 adr_l 指令将 __idmap_text_end 的地址保存在 x1。
使用 adr_l 指令将 dcache_clean_poc 的地址保存在 x2。
使用 blr 指令调用寄存器 x2 中的函数,清洁 ID 映射的部分代码。
接着,执行标签 0 处的代码:
将寄存器 x19 的值移动到寄存器 x0 中。
调用 init_kernel_el 函数,将 x0 的值(即 cpu_boot_mode)作为参数传递,并将返回值移动到寄存器 x20 中。
#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
#endif
这是一个条件编译块,根据定义的宏 VA_BITS 的值来决定是否编译这段代码。如果 VA_BITS 大于 48,则执行以下指令:使用 mrs_s 指令将系统寄存器 SYS_ID_AA64MMFR2_EL1 的值放入寄存器 x0。
使用 tst 指令测试寄存器 x0 是否与 0xf << ID_AA64MMFR2_EL1_VARange_SHIFT 进行按位与操作的结果为零。
将寄存器 x0 的值设置为 VA_BITS(将虚拟地址位数设置为 VA_BITS)。
将寄存器 x25 设置为 VA_BITS_MIN(虚拟地址位数的最小值)。
根据上一步测试的结果,如果相等,则将寄存器 x0 的值复制到寄存器 x25 中。
将寄存器 x0 的值设置为寄存器 x25。bl __cpu_setup // initialise processorb __primary_switch
这里调用了 __cpu_setup 函数进行处理器初始化。然后,使用无条件分支指令 b 跳转到 __primary_switch,进入主要的切换处理。
3. __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 seedbic 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)
这段代码是内核启动的转换点。首先,代码使用 adrp 指令加载保留页目录表的地址到寄存器 x1 中,加载初始 ID 映射页目录表的地址到寄存器 x2 中。然后,调用 __enable_mmu 函数启用 MMU。接下来,根据配置进行一些处理:如果设置了可重定位选项 CONFIG_RELOCATABLE,则代码使用 adrp 指令加载内核起始地址到寄存器 x23 中,并将其与 MIN_KIMG_ALIGN - 1 进行与运算。如果启用了随机化基址 CONFIG_RANDOMIZE_BASE,则会执行一些随机化初始化的操作,并将结果存储在寄存器 x23 中。
如果没有设置可重定位选项,代码直接跳过这些处理。
接下来调用 clear_page_tables 函数清除页表,并调用 create_kernel_mapping 函数创建内核映射。然后,使用 adrp 指令加载初始页目录表的地址到寄存器 x1 中,并通过 load_ttbr1 指令将其加载到 TTBR1 寄存器中。如果配置中启用了可重定位选项 CONFIG_RELOCATABLE,代码还会调用 __relocate_kernel 函数进行内核重定位。最后,代码使用 ldr 指令加载 __primary_switched 标签的地址到寄存器 x8 中,使用 adrp 指令加载内核起始地址的物理地址 __pa(KERNEL_START) 到寄存器 x0 中,然后通过 br 指令跳转到 x8 中的地址。这段代码主要是进行 MMU 相关的初始化和设置,并在适当的时机进行内核重定位。
4. __primary_switched
/** The following fragment of code is executed with the MMU enabled.** 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的情况下执行。首先,代码将 init_task 的地址加载到寄存器 x4 中,并调用 init_cpu_task 函数,传递寄存器 x4、x5 和 x6 作为参数进行初始化。然后,代码使用 adr_l 指令加载 vectors 标签的地址到寄存器 x8 中,并将 vbar_el1 寄存器设置为寄存器 x8 中的向量表地址。接着执行 isb 指令以确保指令序列的正确执行顺序。接下来,代码保存寄存器 x29 和 x30 到栈中,并将当前栈指针保存到寄存器 x29 中。接着,代码将寄存器 x21 的值存储到 __fdt_pointer 中,用于保存 FDT(设备树)的指针。代码通过 ldr_l 指令加载 kimage_vaddr 标签的地址到寄存器 x4 中,并将其与寄存器 x0 中的 KERNEL_START 物理地址相减,得到内核虚拟地址和物理地址之间的偏移量。然后,代码将偏移量存储到 kimage_voffset 中。代码将寄存器 x20 的值存储到寄存器 x0 中,并调用 set_cpu_boot_mode_flag 函数,设置 CPU 启动模式标志。接下来,代码将 __bss_start 的地址加载到寄存器 x0 中,将寄存器 xzr(零寄存器)的值加载到寄存器 x1 中,将 __bss_stop 的地址加载到寄存器 x2 中,然后通过调用 __pi_memset 函数清零 BSS 段。然后,代码执行 dsb ishst 指令,确保零页面(zero page)对页表行为(PTW)可见。接下来,如果虚拟地址位数大于48位,代码将 vabits_actual 标签的地址加载到寄存器 x8 中,并将寄存器 x25 的值存储到 [x8] 中。随后,代码执行 dc civac, x8 指令,使更新后的值对启动的次要处理器可见。如果配置中启用了随机化基址 CONFIG_RANDOMIZE_BASE,代码将 memstart_offset_seed 的地址加载到寄存器 x5 中,并将寄存器 w24 的低位字保存到 [x5, :lo12:memstart_offset_seed] 中。接下来,如果配置中启用了 KASAN(内核地址边界检查器)或者 KASAN 软件标签,代码将调用 kasan_early_init 函数进行早期初始化。然后,代码将寄存器 x21 的值加载到寄存器 x0 中,并调用 early_fdt_map 函数尝试早期映射 FDT。之后,代码将寄存器 x20 的值加载到寄存器 x0 中,并调用 init_feature_override 函数,解析 CPU 功能覆盖。如果配置中启用了针对 SCS(系统控制寄存器)修补 VMLinux,代码将调用 scs_patch_vmlinux 函数。接下来,代码将寄存器 x20 的值加载到寄存器 x0 中,并调用 finalise_el2 函数,尽可能优先选择 VHE(虚拟化扩展模式)。最后,代码从栈中恢复寄存器 x29 和 x30 的值,并调用 start_kernel 函数启动内核。如果执行到这里,表示存在错误,代码将触发一个错误(ASM_BUG)。
3.1 load_ttbr1
arch/arm64/include/asm/assembler.h
/** load_ttbr1 - install @pgtbl as a TTBR1 page table* pgtbl preserved* tmp1/tmp2 clobbered, either may overlap with pgtbl*/.macro load_ttbr1, pgtbl, tmp1, tmp2phys_to_ttbr \tmp1, \pgtbloffset_ttbr1 \tmp1, \tmp2msr ttbr1_el1, \tmp1isb.endm
/** Offset ttbr1 to allow for 48-bit kernel VAs set with 52-bit PTRS_PER_PGD.* orr is used as it can cover the immediate value (and is idempotent).* In future this may be nop'ed out when dealing with 52-bit kernel VAs.* ttbr: Value of ttbr to set, modified.*/.macro offset_ttbr1, ttbr, tmp
#ifdef CONFIG_ARM64_VA_BITS_52mrs_s \tmp, SYS_ID_AA64MMFR2_EL1and \tmp, \tmp, #(0xf << ID_AA64MMFR2_EL1_VARange_SHIFT)cbnz \tmp, .Lskipoffs_\@orr \ttbr, \ttbr, #TTBR1_BADDR_4852_OFFSET
.Lskipoffs_\@ :
#endif.endm
/** Arrange a physical address in a TTBR register, taking care of 52-bit* addresses.** phys: physical address, preserved* ttbr: returns the TTBR value*/.macro phys_to_ttbr, ttbr, phys
#ifdef CONFIG_ARM64_PA_BITS_52orr \ttbr, \phys, \phys, lsr #46and \ttbr, \ttbr, #TTBR_BADDR_MASK_52
#elsemov \ttbr, \phys
#endif.endm
/*
将物理地址整理到 TTBR 寄存器中,注意处理52位的地址。
phys: 物理地址,保持不变
ttbr: 返回 TTBR 的值 */
这段代码定义了一个宏 phys_to_ttbr,用于将物理地址转换为 TTBR 寄存器的值。根据不同的配置选项,它执行不同的操作。首先,代码检查是否定义了 CONFIG_ARM64_PA_BITS_52 这个配置选项(可能是在配置文件中定义的宏)。如果定义了,表示支持使用 52 位的物理地址。如果支持 52 位物理地址,则执行以下操作:使用逻辑或指令 orr,将 phys 和右移46位后的 phys 相加,结果保存到寄存器 ttbr 中。这样做可以将低 46 位和高 6 位合并成 52 位的物理地址。
使用逻辑与指令 and,将 ttbr 和 TTBR_BADDR_MASK_52 相与,以保留 TTBR 的基地址字段(BADDR)的低 52 位。
如果不支持 52 位物理地址,则执行以下操作:使用移动指令 mov,直接将 phys 的值复制到寄存器 ttbr 中。
最后,宏定义结束(.endm)。总之,这段代码用于根据配置选项将物理地址转换为 TTBR 寄存器的值。当支持 52 位物理地址时,通过对物理地址进行位运算和掩码操作来获得 TTBR 值。当不支持 52 位物理地址时,直接将物理地址复制到 TTBR 寄存器。具体的应用场景和上下文需要查看代码的其他部分来确定。
orr \ttbr, \phys, \phys, lsr #46这句为啥要这样运算呢,为啥不直接把phy直接保存到ttbr中呢
orr \ttbr, \phys, \phys, lsr #46 这句代码的目的是将物理地址的高 6 位和低 46 位合并成一个 52 位的地址,并将结果保存到 TTBR 寄存器中。这里之所以需要进行逻辑或运算(orr)的原因是为了合并两个物理地址的值。首先,使用逻辑右移指令(lsr)将 phys 的值向右移动 46 位,得到高 6 位的值。然后,将两个 phys 值进行逻辑或运算,将高 6 位和低 46 位合并在一起,形成一个完整的 52 位物理地址。这样做的原因可能是因为在某些系统中,物理地址的高 6 位可能包含一些特殊的标识位或者其他有意义的信息。因此,为了在 TTBR 寄存器中保留这些信息,需要将两个物理地址进行合并。如果直接将 phys 的值复制到 ttbr 中,将丢失掉高 6 位的信息,可能导致错误的行为或结果。因此,这段代码选择使用逻辑或运算将两个物理地址合并成一个 52 位地址,并将合并的结果保存到 ttbr 寄存器中。
二、启动C代码部分
1. start_kernel
参考:
基于aarch64分析kernel源码 三:启动代码分析
Linux页表 - - 启动过程临时页表创建过程
相关文章:
[Linux kernel] [ARM64] boot 流程梳理
一、启动汇编代码部分 0. 链接文件找代码段入口 – _text arch/arm64/kernel/vmlinux.lds.S ENTRY(_text). KIMAGE_VADDR;.head.text : {_text .;HEAD_TEXT}.text : ALIGN(SEGMENT_ALIGN) { /* Real text segment */_stext .; /* Text and read-only data */IRQENTRY_TE…...
重建二叉树
输入一棵二叉树前序遍历和中序遍历的结果,请重建该二叉树。 注意: 二叉树中每个节点的值都互不相同;输入的前序遍历和中序遍历一定合法; 数据范围 树中节点数量范围 [0,100] 。 样例 给定: 前序遍历是:[3, 9, 2…...
支付整体架构
5.4 支付的技术架构 架构即未来,只有建立在技术架构设计良好的体系上,支付机构才能有美好的未来。如果支付的技术体系在架构上存在问题,那么就没有办法实现高可用性、高安全性、高效率和水平可扩展性。 总结多年来在海内外支付机构主持和参与…...
百度智能云:千帆大模型平台接入Llama 2等33个大模型,上线103个Prompt模板
大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的…...
烦人的幻灯片——拓扑排序
烦人的幻灯片 烦人的幻灯片问题描述输入输出格式输入格式输出格式 输入输出样例输入样例:输入样例一:输入样例二: 输出样例:输出样例一:输出样例二: 正确做法拓扑排序 代码 烦人的幻灯片 问题描述 李教授…...
无涯教程-Perl - ord函数
描述 此函数返回EXPR指定的字符的ASCII数值,如果省略则返回$_。例如,ord(A)返回值为65。 语法 以下是此函数的简单语法- ord EXPRord返回值 该函数返回整数。 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl -wprint("ord() ", ord(G), "\n"…...
Python爬虫:js逆向调式操作及调式中遇到debugger问题
Python爬虫:js逆向调式操作及调式中遇到debugger问题 1. 前言2. js逆向调式操作2.1 DOM事件断点2.2 XHR/提取断点(用于请求接口参数加密处理)2.3 请求返回的数据是加密的2.4 hook定位参数 3. 调式中遇到debugger问题3.1 解决方式(一律不在此处暂停)3.2 问题:点击一律…...
HTML网页制作技巧:打造出色的用户体验
HTML是构建网页的基础语言,掌握一些关键的技巧可以帮助您创建出色的用户体验。本文将介绍一些HTML网页制作的技巧,从布局和样式到交互和可访问性,为您提供有用的指导。无论您是初学者还是有经验的开发者,这些技巧都将对您的网页设…...
探究使用HTTP代理ip后无法访问网站的原因与解决方案
目录 访问网站的原理是什么 1. DNS解析 2. 建立TCP连接 3. 发送HTTP请求: 4. 服务器响应: 5. 浏览器渲染: 6. 页面展示: 使用代理IP后访问不了网站,有哪些方面的原因 1. 代理IP的可用性: 2. 代理…...
SpringBoot 全局异常处理进阶
待总结 参考文章: SpringBoot 全局异常处理进阶:使用 ControllerAdvice 对不同的 Controller 分别捕获异常并处理 SpringBoot 对 controller 层捕获全局异常并处理的方法(ControllerAdvice 和 ExceptionHandler) 注解RestCont…...
数据结构(一):顺序表详解
在正式介绍顺序表之前,我们有必要先了解一个名词:线性表。 线性表: 线性表是,具有n个相同特性的数据元素的有限序列。常见的线性表:顺序表、链表、栈、队列、数组、字符串... 线性表在逻辑上是线性结构,但…...
【周末闲谈】人工智能热潮下的AIGC到底指的是什么?
生成式人工智能AIGC(Artificial Intelligence Generated Content)是人工智能1.0时代进入2.0时代的重要标志。 个人主页:【😊个人主页】 系列专栏:【❤️周末闲谈】 系列目录 ✨第一周 二进制VS三进制 ✨第二周 文心一…...
sklearn垃圾邮件分类
在Python中,可以使用机器学习算法来进行垃圾邮件分类。下面是一个简单的示例,使用朴素贝叶斯算法进行垃圾邮件分类: import pandas as pd from sklearn.feature_extraction.text import CountVectorizer from sklearn.model_selection impor…...
UI美工设计岗位的工作职责
UI美工设计岗位的工作职责1 职责: 1、负责软件界面的美术设计、创意工作和制作工作; 2、根据各种相关软件的用户群,提出构思新颖、有高度吸引力的创意设计; 3、对页面进行优化,使用户操作更趋于人性化; 4、维护现有的应用产品; 5、收集和…...
ES6链判断运算符(?.)的正确打开方式
在实际应用中,如果读取对象内部 的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下下面这样: // 错误的写法 const firstName mes…...
删除块参照 删除块定义
删除块参照 void CDwgDatabaseUtil::DeleteBlockReference(CString strBlockName) {// 锁定文档acDocManager->lockDocument(acDocManager->curDocument());AcDbObjectId objRecId;if (...
机器学习笔记:李宏毅ChatGPT:生成式学习的两种策略
1 策略1 “各个击破”——autoregressive model “各个击破”——一个一个生成出来 2 策略2 : “一次到位”——non-autoregressve model 一步到位,全部生成出来 2.1 non-autoregressive model 如何确定长度? 两种策略 策略1:始…...
React 组件防止冒泡方法
背景 在使用 antd 组件库开发时,发现点击一个子组件,却触发了父组件的点击事件,比如,我在一个折叠面板里面放入一个下拉框或者对下拉框列表渲染做定制,每个下拉框候选项都有一个子组件… 解决 其实这就是 Javascri…...
MAUI+Blazor 如何开启浏览器调试工具
文章目录 前言如何开启调试模式输入快捷键打开浏览器有什么意义? 前言 MAUIBlazor其实就是浏览器套壳,我觉得很有意义,因为现在性能已经不是主要的限制了,很多时候讲究的快速开发。而且MAUIBlazor跨平台的未来感觉实在是太香了。…...
【Spring MVC】Spring MVC基于注解的程序开发
目录 一、什么是Spring MVC 二、Spring MVC项目的创建和使用 1、实现客户端和服务器端之间的连接 1.1、RequsestMapping注解 1.2、RequestMapper的简单使用 1.3、使用GetMapping和POSTMapping注解来实现HTTP连接 三、获取参数 1、实现获取单个参数 2、实现获取对象 3…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
LeetCode - 199. 二叉树的右视图
题目 199. 二叉树的右视图 - 力扣(LeetCode) 思路 右视图是指从树的右侧看,对于每一层,只能看到该层最右边的节点。实现思路是: 使用深度优先搜索(DFS)按照"根-右-左"的顺序遍历树记录每个节点的深度对于…...
破解路内监管盲区:免布线低位视频桩重塑停车管理新标准
城市路内停车管理常因行道树遮挡、高位设备盲区等问题,导致车牌识别率低、逃费率高,传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法,正成为破局关键。该设备安装于车位侧方0.5-0.7米高度,直接规避树枝遮…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
Oracle11g安装包
Oracle 11g安装包 适用于windows系统,64位 下载路径 oracle 11g 安装包...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...
