Linux驱动开发:uboot启动流程详解
前言:uboot作为Linux驱动开发的 “三巨头” 之一,绝对是一座绕不开的大山。当然,即使不去细致了解uboot启动流程依旧不影响开发者对uboot的简单移植。但秉持着知其然知其所以然的学习态度,作者将给读者朋友细致化的过一遍uboot启动流程(考虑到硬件平台与uboot版本不一致,实际情况可能有些许出入)。
实验硬件:imx6ull;uboot版本:2016.03
想深挖uboot的启动流程就需要从uboot的链接脚本入手(程序的入口:程序执行的第一条指令被称为程序的入口,这个入口通常就是在链接脚本指定的),打开 arch/arm/cpu/armv7/u-boot.lds 这个文件(下载uboot源码后进行编译得到lds链接脚本):
#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start) //当前程序的入口函数,定义在arch/arm/lib/vectors.S
SECTIONS
{
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)/DISCARD/ : { *(.rel._secure*) }
#endif. = 0x00000000;. = ALIGN(4);.text :{*(.__image_copy_start)//uboot 拷贝的首地址*(.vectors)//vectors 段保存中断向量表arch/arm/cpu/armv7/start.o (.text*)//将 arch/arm/cpu/armv7/start.s 编译出来的代码放到 中断向量表后面*(.text*)//text 段,其他的代码段就放到这里}. = ALIGN(4);.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }. = ALIGN(4);.data : {*(.data*)}. = ALIGN(4);. = .;. = ALIGN(4);.u_boot_list : {KEEP(*(SORT(.u_boot_list*)));}. = ALIGN(4);.image_copy_end :{*(.__image_copy_end)//uboot 拷贝的结束地址}.rel_dyn_start :{*(.__rel_dyn_start)//.rel.dyn 段起始地址}.rel.dyn : {*(.rel*)}.rel_dyn_end :{*(.__rel_dyn_end)//.rel.dyn 段结束地址}.end :{*(.__end)}_image_binary_end = .;//镜像结束地址. = ALIGN(4096);.mmutable : {*(.mmutable)}.bss_start __rel_dyn_start (OVERLAY) : {KEEP(*(.__bss_start));//.bss 段起始地址__bss_base = .;}.bss __bss_base (OVERLAY) : {*(.bss*). = ALIGN(4);__bss_limit = .;}.bss_end __bss_limit (OVERLAY) : {KEEP(*(.__bss_end));//.bss 段结束地址}.dynsym _image_binary_end : { *(.dynsym) }.dynbss : { *(.dynbss) }.dynstr : { *(.dynstr*) }.dynamic : { *(.dynamic*) }.plt : { *(.plt*) }.interp : { *(.interp*) }.gnu.hash : { *(.gnu.hash) }.gnu : { *(.gnu*) }.ARM.exidx : { *(.ARM.exidx*) }.gnu.linkonce.armexidx : { *(.gnu.linkonce.armexidx.*) }
}
通过查看链接脚本u-boot.lds知道入口点是 arch/arm/lib/vectors.S 文件中的_start函数。
一、uboot总体启动流程
★作者将uboot的总体启动流程分为2部分:arch级初始化(架构)和板级初始化。

二、arch级初始化
如下图所示,uboot 程序的入口函数_start位于arch/arm/lib/vector.S文件中,_start 开始的是中断向量表,随后通过 b reset 函数跳转到 reset 函数, reset 函数在 arch/arm/cpu/armv7/start.S 里面。 reset 函数跳转到了 save_boot_params 函数,save_boot_params 函数又跳转到 save_boot_params_ret 函数,该函数主要分为五个功能:
①、设置CPU处于SVC特权模式,并且关闭FIQ和IRQ两个中断;
②、设置向量表重定位;
③、设置cp15寄存器(禁止Cache,MMU,TLBs);
④、配置关键寄存器和初始化
⑤、跳转_main,进入板级初始化
补充说明:_start 开始是中断向量表,这段话语部分读者可能存在疑惑,因为自己看 uboot 代码流程发现: _start 进入之后就紧跟着 b reset,并未出现定义中断向量表。其实,大部分开发板的中断向量表是固定定义在 0x0000000 开始的地址上的,比如 reset中断 的地址是 0x00000000

2.1 u-boot.lds链接代码
打开 arch/arm/cpu/armv7/u-boot.lds 这个文件, ENTRY 进入 _start ,代码具体如下:
#include <config.h>
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
#if defined(CONFIG_ARMV7_SECURE_BASE) && defined(CONFIG_ARMV7_NONSEC)/DISCARD/ : { *(.rel._secure*) }
#endif. = 0x00000000;. = ALIGN(4);.text :{*(.__image_copy_start)*(.vectors)CPUDIR/start.o (.text*)*(.text*)}
在 arch/arm/lib/vector.S 中寻找到 _start 定义,并且发现跳转到了 reset :
#include <config.h>
.globl _start.section ".vectors", "ax"
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endifb resetldr pc, _undefined_instructionldr pc, _software_interruptldr pc, _prefetch_abortldr pc, _data_abortldr pc, _not_usedldr pc, _irqldr pc, _fiq
,,,,
2.2 reset
reset 函数存在于 arch/arm/cpu/armv7/start.s 文件中,而 reset 函数跳转到 save_boot_params 函数,save_boot_params 函数又跳转到 save_boot_params_ret 中,具体如下:
//第一段:reset跳转到save_boot_params .globl reset.globl save_boot_params_retreset:/* Allow the board to save important registers */b save_boot_params
save_boot_params_ret:/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0//第二段:save_boot_params跳转到save_boot_params_retENTRY(save_boot_params)b save_boot_params_ret @ back to my callerENDPROC(save_boot_params).weak save_boot_params
2.3 cpsr和cp15 STCLR
reset 函数跳转到 save_boot_params_ret 函数后,具体如下:
save_boot_params_ret:/** 关闭 FIQ 和 IRQ 中断,设置 CPU 处于 SVC 特权模式* except if in HYP mode already(除非已经处于HYP特权模式,比SVC低一点的特权)*/mrs r0, cpsrand r1, r0, #0x1f @ mask mode bitsteq r1, #0x1a @ test for HYP modebicne r0, r0, #0x1f @ clear all mode bitsorrne r0, r0, #0x13 @ set SVC modeorr r0, r0, #0xc0 @ disable FIQ and IRQmsr cpsr,r0/**★SCTLR寄存器bit13清零(bit13控制中断向量表地址,0:可以重定位;1:默认为0xFFFF0000)将_start *的数值写入该寄存器,也就是中断向量表的起始地址为0x87800000*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Registerbic r0, #CR_V @ V = 0mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register/* Set vector address in CP15 VBAR register */ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INITbl cpu_init_cp15bl cpu_init_crit
#endifbl _main
从上述代码能看出,代码包含5个部分:cpsr,cp15,cpu_init_cp15,cpu_init_crit和_main。
2.3.1 cpsr
/** disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,* except if in HYP mode already*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
cpsr:给目标寄存器写入对应的数值(可以参考芯片手册),关闭 FIQ 和 IRQ,并且讲CPU设置为 SVC 特权模式。
为什么这样操作的原因:
(1) 禁止 FIQ 和 IRQ:uboot 作为芯片上电的第一道程序是至关重要的,而中断是很危险的,它的等级太高了,可以轻松打断程序的正常运行。所以,为了保证uboot的正常运行,需要关闭部分中断。
(2) 设置 SVC:uboot的运行避免不了对各类寄存器等操作,为了保证操作的正常,需要给CPU设置高特权模式。
2.3.2 cp15
/** Setup vector:* (OMAP4 spl TEXT_BASE is not 32 byte aligned.* Continue to use ROM code vector only in OMAP4 spl)*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Registerbic r0, #CR_V @ V = 0mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register/* Set vector address in CP15 VBAR register */ldr r0, =_startmcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
cp15 SCTLR:SCTLR寄存器bit13清零(bit13控制中断向量表地址,0:可以重定位;1:默认为0xFFFF0000)将_start的数值写入该寄存器,也就是中断向量表的起始地址为0x87800000
2.4 cpu_init_cp15
cpu_init_cp15 存在于 arch/arm/cpu/armv7/start.s 文件中:
/*************************************************************************** cpu_init_cp15** Setup CP15 registers (cache, MMU, TLBs). The I-cache is turned on unless* CONFIG_SYS_ICACHE_OFF is defined.**************************************************************************/
ENTRY(cpu_init_cp15)/** Invalidate L1 I/D*/mov r0, #0 @ set up for MCRmcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs /*禁止从TLB中取地址描述符,也就是禁止虚拟地址到物理地址的转换,因为刚开始操作的都是物理寄存器!*/mcr p15, 0, r0, c7, c5, 0 @ invalidate icache /*关闭指令cache*/mcr p15, 0, r0, c7, c5, 6 @ invalidate BP array /*关闭分支预测*/mcr p15, 0, r0, c7, c10, 4 @ DSB /*多核cpu之间进行数据同步*/mcr p15, 0, r0, c7, c5, 4 @ ISB /*进行指令同步,放弃流水线中已经取到的指令,重新取指令*//** 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, #0x00000002 @ set bit 1 (--A-) Alignorr r0, r0, #0x00000800 @ set bit 11 (Z---) BTB
#ifdef CONFIG_SYS_ICACHE_OFFbic r0, r0, #0x00001000 @ clear bit 12 (I) I-cache
#elseorr r0, r0, #0x00001000 @ set bit 12 (I) I-cache
#endifmcr p15, 0, r0, c1, c0, 0#ifdef CONFIG_ARM_ERRATA_716044mrc p15, 0, r0, c1, c0, 0 @ read system control registerorr r0, r0, #1 << 11 @ set bit #11mcr p15, 0, r0, c1, c0, 0 @ write system control register
#endif#if (defined(CONFIG_ARM_ERRATA_742230) || defined(CONFIG_ARM_ERRATA_794072))mrc p15, 0, r0, c15, c0, 1 @ read diagnostic registerorr r0, r0, #1 << 4 @ set bit #4mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif#ifdef CONFIG_ARM_ERRATA_743622mrc p15, 0, r0, c15, c0, 1 @ read diagnostic registerorr r0, r0, #1 << 6 @ set bit #6mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif#ifdef CONFIG_ARM_ERRATA_751472mrc p15, 0, r0, c15, c0, 1 @ read diagnostic registerorr r0, r0, #1 << 11 @ set bit #11mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_761320mrc p15, 0, r0, c15, c0, 1 @ read diagnostic registerorr r0, r0, #1 << 21 @ set bit #21mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endif
#ifdef CONFIG_ARM_ERRATA_845369mrc p15, 0, r0, c15, c0, 1 @ read diagnostic registerorr r0, r0, #1 << 22 @ set bit #22mcr p15, 0, r0, c15, c0, 1 @ write diagnostic register
#endifmov r5, lr @ Store my Callermrc p15, 0, r1, c0, c0, 0 @ r1 has Read Main ID Register (MIDR)mov r3, r1, lsr #20 @ get variant fieldand r3, r3, #0xf @ r3 has CPU variantand r4, r1, #0xf @ r4 has CPU revisionmov r2, r3, lsl #4 @ shift variant field for combined valueorr r2, r4, r2 @ r2 has combined CPU variant + revision#ifdef CONFIG_ARM_ERRATA_798870cmp r2, #0x30 @ Applies to lower than R3p0bge skip_errata_798870 @ skip if not affected revcmp r2, #0x20 @ Applies to including and above R2p0blt skip_errata_798870 @ skip if not affected revmrc p15, 1, r0, c15, c0, 0 @ read l2 aux ctrl regorr r0, r0, #1 << 7 @ Enable hazard-detect timeoutpush {r1-r5} @ Save the cpu info registersbl v7_arch_cp15_set_l2aux_ctrlisb @ Recommended ISB after l2actlr updatepop {r1-r5} @ Restore the cpu info - fall through
skip_errata_798870:
#endif#ifdef CONFIG_ARM_ERRATA_801819cmp r2, #0x24 @ Applies to lt including R2p4bgt skip_errata_801819 @ skip if not affected revcmp r2, #0x20 @ Applies to including and above R2p0blt skip_errata_801819 @ skip if not affected revmrc p15, 0, r0, c0, c0, 6 @ pick up REVIDR regand r0, r0, #1 << 3 @ check REVIDR[3]cmp r0, #1 << 3beq skip_errata_801819 @ skip erratum if REVIDR[3] is setmrc p15, 0, r0, c1, c0, 1 @ read auxilary control registerorr r0, r0, #3 << 27 @ Disables streaming. All write-allocate@ lines allocate in the L1 or L2 cache.orr r0, r0, #3 << 25 @ Disables streaming. All write-allocate@ lines allocate in the L1 cache.push {r1-r5} @ Save the cpu info registersbl v7_arch_cp15_set_acrpop {r1-r5} @ Restore the cpu info - fall through
skip_errata_801819:
#endif#ifdef CONFIG_ARM_ERRATA_454179cmp r2, #0x21 @ Only on < r2p1bge skip_errata_454179mrc p15, 0, r0, c1, c0, 1 @ Read ACRorr r0, r0, #(0x3 << 6) @ Set DBSM(BIT7) and IBE(BIT6) bitspush {r1-r5} @ Save the cpu info registersbl v7_arch_cp15_set_acrpop {r1-r5} @ Restore the cpu info - fall throughskip_errata_454179:
#endif#ifdef CONFIG_ARM_ERRATA_430973cmp r2, #0x21 @ Only on < r2p1bge skip_errata_430973mrc p15, 0, r0, c1, c0, 1 @ Read ACRorr r0, r0, #(0x1 << 6) @ Set IBE bitpush {r1-r5} @ Save the cpu info registersbl v7_arch_cp15_set_acrpop {r1-r5} @ Restore the cpu info - fall throughskip_errata_430973:
#endif#ifdef CONFIG_ARM_ERRATA_621766cmp r2, #0x21 @ Only on < r2p1bge skip_errata_621766mrc p15, 0, r0, c1, c0, 1 @ Read ACRorr r0, r0, #(0x1 << 5) @ Set L1NEON bitpush {r1-r5} @ Save the cpu info registersbl v7_arch_cp15_set_acrpop {r1-r5} @ Restore the cpu info - fall throughskip_errata_621766:
#endifmov pc, r5 @ back to my caller
ENDPROC(cpu_init_cp15)
cpu_init_cp15:关闭MMU和Cache
为什么这样操作的原因:
(1) 关闭MMU:因为MMU是把虚拟地址转化为物理地址得作用而我们现在是要设置控制寄存器,而控制寄存器本来就是实地址(物理地址),再使能MMU,不就是多此一举了吗?
(2) 关闭cache:cache和MMU是通过CP15管理的,刚上电的时候,CPU还不能管理他们。所以上电的时候MMU必须关闭,指令cache可关闭,可不关闭,但数据cache一定要关闭否则可能导致刚开始的代码里面,去取数据的时候,从cache里面取,而这时候RAM中数据还没有cache过来,导致数据预取异常。
2.5 cpu_init_crit
cpu_init_crit 存在于 arch/arm/cpu/armv7/start.s 文件中,cpu_init_crit 代码比较简单:b lowlevel_init。所以,直接去研究 lowlevel_init 函数,lowlevel_init 函数存在 arch/arm/cpu/armv7/lowlevel_init.S 文件中:
ENTRY(lowlevel_init)/** Setup a temporary stack. Global data is not available yet.*/ldr sp, =CONFIG_SYS_INIT_SP_ADDRbic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DMmov r9, #0
#else/** Set up global data for boards that still need it. This will be* removed soon.*/
#ifdef CONFIG_SPL_BUILDldr r9, =gdata
#elsesub sp, sp, #GD_SIZEbic sp, sp, #7mov r9, sp
#endif
#endif/** Save the old lr(passed in ip) and the current lr to stack*/push {ip, lr}/** Call the very early init function. This should do only the* absolute bare minimum to get started. It should not:** - set up DRAM* - use global_data* - clear BSS* - try to start a console** For boards with SPL this should be empty since SPL can do all of* this init in the SPL board_init_f() function which is called* immediately after this.*/bl s_initpop {ip, pc}
ENDPROC(lowlevel_init)
考虑到 cpu_init_crit 函数是 arch 级初始化中重要的函数,所以,作者这里详解给大家分析一下。

① 设置SP指向CONFIG_SYS_INIT_SP_ADDR=0X0091FF00,流程见上图。可结合下图自己分析

② 设置SP 8字节对齐
③ 设置gd(global data)大小为248B
④ sp地址(0x0091FE08)保存在r9寄存器中

⑤ 将ip和lr入栈
⑥ 跳转s_init //对于IMX6ULL而言,什么都没有做(空函数)
⑦ 将入栈的ip和lr出栈,并将lr赋给pc
三、板级初始化

_main 函数存在于 arch/arm/lib/crt0.S 中,_main 包含六个部分,简单来说:主要是把ROM中的程序拷贝至DDR(加快uboot的运行),然后载重定向,最后初始化各种外设。_mian 函数具体如下:
ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0).*/#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr sp, =(CONFIG_SPL_STACK)
#elseldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */mov r3, spbic r3, r3, #7mov sp, r3
#elsebic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endifmov r0, spbl board_init_f_alloc_reservemov sp, r0/* set up gd here, outside any C code */mov r9, r0bl board_init_f_init_reservemov r0, #0bl board_init_f#if ! defined(CONFIG_SPL_BUILD)/** Set up intermediate environment (new sp and gd) and call* relocate_code(addr_moni). Trick here is that we'll return* 'here' but relocated.*/ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */mov r3, spbic r3, r3, #7mov sp, r3
#elsebic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endifldr r9, [r9, #GD_BD] /* r9 = gd->bd */sub r9, r9, #GD_SIZE /* new GD is below bd */adr lr, hereldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */add lr, lr, r0
#if defined(CONFIG_CPU_V7M)orr lr, #1 /* As required by Thumb-only */
#endifldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */b relocate_code
here:
/** now relocate vectors*/bl relocate_vectors/* Set up final (full) environment */bl c_runtime_cpu_setup /* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || defined(CONFIG_SPL_FRAMEWORK)
# ifdef CONFIG_SPL_BUILD/* Use a DRAM stack for the rest of SPL, if requested */bl spl_relocate_stack_gdcmp r0, #0movne sp, r0movne r9, r0
# endifldr r0, =__bss_start /* this is auto-relocated! */#ifdef CONFIG_USE_ARCH_MEMSETldr r3, =__bss_end /* this is auto-relocated! */mov r1, #0x00000000 /* prepare zero to clear BSS */subs r2, r3, r0 /* r2 = memset len */bl memset
#elseldr r1, =__bss_end /* this is auto-relocated! */mov r2, #0x00000000 /* prepare zero to clear BSS */clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)itt lo
#endifstrlo r2, [r0] /* clear 32-bit BSS word */addlo r0, r0, #4 /* move to next */blo clbss_l
#endif#if ! defined(CONFIG_SPL_BUILD)bl coloured_LED_initbl red_led_on
#endif/* call board_init_r(gd_t *id, ulong dest_addr) */mov r0, r9 /* gd_t */ldr r1, [r9, #GD_RELOCADDR] /* dest_addr *//* call board_init_r */
#if defined(CONFIG_SYS_THUMB_BUILD)ldr lr, =board_init_r /* this is auto-relocated! */bx lr
#elseldr pc, =board_init_r /* this is auto-relocated! */
#endif/* we should not return here. */
#endifENDPROC(_main)
3.1 board_init_f_alloc_reserve
board_init_f_alloc_reserve 函数位于 common/init/board_init.c 文件中,具体如下:
ulong board_init_f_alloc_reserve(ulong top)
{/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)top -= CONFIG_SYS_MALLOC_F_LEN;
#endif/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */top = rounddown(top-sizeof(struct global_data), 16);return top;
}
board_init_f_alloc_reserve 函数目的:留出早期的 malloc 内存区域和 gd 内存区域,其中CONFIG_SYS_MALLOC_F_LEN=0X400( 在文件 include/generated/autoconf.h 中定义 ) ,sizeof(struct global_data)=248(GD_SIZE 值),完成以后的内存分布如图所示(top返回地址为:0x0091FA00):

3.2 board_init_f_init_reserve
board_init_f_init_reserve 函数同样位于common/init/board_init.c 文件中,具体如下:
void board_init_f_init_reserve(ulong base)
{struct global_data *gd_ptr;
#ifndef _USE_MEMCPYint *ptr;
#endif/** clear GD entirely and set it up.* Use gd_ptr, as gd may not be properly set yet.*/gd_ptr = (struct global_data *)base;/* zero the area */
#ifdef _USE_MEMCPYmemset(gd_ptr, '\0', sizeof(*gd));
#elsefor (ptr = (int *)gd_ptr; ptr < (int *)(gd_ptr + 1); )*ptr++ = 0;
#endif/* set GD unless architecture did it already */
#if !defined(CONFIG_ARM)arch_setup_gd(gd_ptr);
#endif/* next alloc will be higher by one GD plus 16-byte alignment */base += roundup(sizeof(struct global_data), 16);/** record early malloc arena start.* Use gd as it is now properly set for all architectures.*/#if defined(CONFIG_SYS_MALLOC_F)/* go down one 'early malloc arena' */gd->malloc_base = base;/* next alloc will be higher by one 'early malloc arena' size */base += CONFIG_SYS_MALLOC_F_LEN;
#endif
}
board_init_f_init_reserve 函数目的:初始化gd(全局变量)。
3.3 board_init_f
board_init_f 函数定义在文件 common/board_f.c 中,具体代码如下:
void board_init_f(ulong boot_flags)
{
#ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA/** For some archtectures, global data is initialized and used before* calling this function. The data should be preserved. For others,* CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack* here to host global data until relocation.*/gd_t data;gd = &data;/** Clear global data before it is accessed at debug print* in initcall_run_list. Otherwise the debug print probably* get the wrong vaule of gd->have_console.*/zero_global_data();
#endifgd->flags = boot_flags;gd->have_console = 0;if (initcall_run_list(init_sequence_f))hang();#if !defined(CONFIG_ARM) && !defined(CONFIG_SANDBOX) && \!defined(CONFIG_EFI_APP)/* NOTREACHED - jump_to_copy() does not return */hang();
#endif/* Light up LED1 */imx6_light_up_led1();
}
board_init_f 函数主要有两个工作:1、 初始化一系列外设,比如串口、定时器,或者打印一些消息等。2、初始化 gd 的各个成员变量,uboot 会将自己重定位到 DRAM 最后面的地址区域,也就是将自己拷贝到 DRAM 最后面的内存区域中。
board_init_f 函数中的 initcall_run_list 函数主要用于调用一系列函数,值保存在 init_sequence_f 函数中。

initcall_run_list 函数的具体主要功能如下(可结合上图分析):
1、gd->num_len=_bss_end-_start//uboot image大小,即代码长度,0X878A8E74-0x87800000=0XA8EF4
2、initf_malloc()//gd->malloc=CONFIG_SYS_MALLOC_F_LEN=0X400,内存池大小为0x400
3、arch_cpu_init()//初始化架构相关的内容,CPU级别操作
4、 initf_dm()//驱动模型一些初始化
5、 board_early_init_f()//初始化串口的IO配置(I.MX6ULL)
6、 timer_init()//初始化定时器(Cortex-A7内核)
…
14、 init_baud_rate()//根据环境变量baudrate设置gd->baudrate=115200
…
24、dram_init()//设置gd->ram_size=512MB 0X2000 0000B
…
44、set_up_addr()//设置地址gd->ram_zise=0X2000 0000;gd->ram_top=0XA0000000
(0X80000000+0X2000 0000);gd->relocadder=0XA0000000(重定位后最高地址)…
...
48、reserve_uboot//gd->mon_len=0X8EF4;gd->start_addr_sp=
0X9FF47000;gd->relocadder=0X9FF47000//uboot重定位后的起始地址
49、reserve_malloc//TOTAL_MALLOC_LEN=CONFIG_SYS_MALLOC_LEN(0X10000000B=16MB)+
CONFIG_ENV_SIZE(0X2000=8K)
50、reserve_board()//留出板子bd所占的内存区
52、reserve_global_data()//留出gd所占的内存区
…
55、resreve_stacks//留出栈空间,gd->start_addr_sp-16,然后16字节对齐
…
最终sp=gd->start_addr_sp=0X9EF44E90
…
61、setup_reloc//设置gd其他一些成员变量,供后面定位使用,并且将以前的gd拷贝到gd->new_gd处最终uboot重定位后偏移为0X18747000(0X9FF47000-0X87800000),新的gd首地址0X9EF44EB8,新的sp首地址0X9EF44E90
3.4 relocate_code
relocate_code 函数主要用于代码拷贝,在 relocate_code 函数之前还有语句 ldr r0,[r9,#GD_RELOCADDR],即r0=gd-> relocaddr= 0X9FF47000, uboot 重定位后的首地址。
relocate_code 函数在 arch/arm/lib/relocate.S 中,下面结合代码分析该函数
ENTRY(relocate_code) ldr r1,=__image_copy_start//r1=0X8780000源地址起始地址subs r4, r0, r1//r4=0X9FF47000-0X87800000=0X18747000 偏移beq relocate_done /* skip relocation */ldr r2, =__image_copy_end//r2=0X8785dc6c源地址结束地址copy_loop: //拷贝,将uboot从源地址0X8780000拷贝至0X9FF47000ldmia r1!, {r10-r11} /* copy from source address [r1] */stmia r0!, {r10-r11} /* copy to target address [r0] */cmp r1, r2 /* until source end address [r2] */blo copy_loop/** fix .rel.dyn relocations*/ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */and r1, r1, #0xffcmp r1, #23 /* relative fixup? */bne fixnext/* relative fix: increase location by offset */add r0, r0, r4ldr r1, [r0]add r1, r1, r4str r1, [r0]
fixnext:cmp r2, r3blo fixlooprelocate_done:#ifdef __XSCALE__/** On xscale, icache must be invalidated and write buffers drained,* even with cache disabled - 4.2.7 of xscale core developer's manual*/mcr p15, 0, r0, c7, c7, 0 /* invalidate icache */mcr p15, 0, r0, c7, c10, 4 /* drain write buffer */
#endif/* ARMv4- don't know bx lr but the assembler fails to see that */#ifdef __ARM_ARCH_4__mov pc, lr
#elsebx lr
#endifENDPROC(relocate_code)
注意:直接将uboot从0X87800000拷贝至其他地方后,函数调用、全局变量引用可能会出问题。uboot采用位置无关码来处理该类问题(简单说采用相对地址寻址,而不是采用绝对地址寻址,并且重定位后需要将Label+offset)。在使用 ld 进行链接的时候使用选项”- pie” 生成位置无关的可执行文件。具体为.rel.dyn段。
核心代码:/** fix .rel.dyn relocations*/ldr r2, =__rel_dyn_start /* r2 <- SRC &__rel_dyn_start */ldr r3, =__rel_dyn_end /* r3 <- SRC &__rel_dyn_end */
fixloop:ldmia r2!, {r0-r1} /* (r0,r1) <- (SRC location,fixup) */and r1, r1, #0xffcmp r1, #23 /* relative fixup? */bne fixnext/* relative fix: increase location by offset */add r0, r0, r4ldr r1, [r0]add r1, r1, r4str r1, [r0]
fixnext:cmp r2, r3blo fixloop
核心代码程序分析如下:
1、r2=__rel_dyn_start,也就是.rel.dyn 段的起始地址。
2、r3=__rel_dyn_end,也就是.rel.dyn 段的终止地址。
3、从.rel.dyn 段起始地址开始,每次读取两个 4 字节的数据存放到 r0 和 r1 寄存器中, r0 存放低 4 字节的数据,也就是 Label 地址; r1 存放高 4 字节的数据,也就是 Label 标志。
4、r1 中给的值与 0xff 进行与运算,其实就是取 r1 的低 8 位。
5、判断 r1 中的值是否等于 23(0X17)。//0X17就是判断是否是Label
6、如果 r1 不等于 23 的话就说明不是描述 Label 的,执行函数 fixnext ,否则的话继续执行下面的代码。
7、r0 保存着 Label 值, r4 保存着重定位后的地址偏移, r0+r4 就得到了重定位后的Label值。此时 r0 保存着重定位后的 Label 值。
8、读取重定位后 Label 所保存的变量地址,此时这个变量地址还是重定位前的地址,将得到的值放到 r1 寄存器中。
9、 r1+r4 即可得到重定位后的变量地址 。
10、重定位后的变量地址写入到重定位后的 Label 中。
11、比较 r2 和 r3,查看.rel.dyn 段重定位是否完成。
12、如果 r2 和 r3 不相等,说明.rel.dyn 重定位还未完成,因此跳到 fixloop 继续重定位 .rel.dyn 段。
3.5 relocate_vector
relocate_vectors 函数同样位于 arch/arm/lib/relocate.S 中,relocate_vectors 函数的目的:重定位中断向量表(设置VBAR寄存器为重定位后的中断向量表起始地址)
ENTRY(relocate_vectors)#ifdef CONFIG_CPU_V7M/** On ARMv7-M we only have to write the new vector address* to VTOR register.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */ldr r1, =V7M_SCB_BASEstr r0, [r1, V7M_SCB_VTOR]
#else
#ifdef CONFIG_HAS_VBAR/** If the ARM processor has the security extensions,* use VBAR to relocate the exception vectors.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */mcr p15, 0, r0, c12, c0, 0 /* Set VBAR */
#else/** Copy the relocated exception vectors to the* correct address* CP15 c1 V bit gives us the location of the vectors:* 0x00000000 or 0xFFFF0000.*/ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */mrc p15, 0, r2, c1, c0, 0 /* V bit (bit[13]) in CP15 c1 */ands r2, r2, #(1 << 13)ldreq r1, =0x00000000 /* If V=0 */ldrne r1, =0xFFFF0000 /* If V=1 */ldmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10}ldmia r0!, {r2-r8,r10}stmia r1!, {r2-r8,r10}
#endif
#endifbx lrENDPROC(relocate_vectors)
3.6 board_init_r
board_init_r 函数与 board_init_f 函数类似,用于初始化一系列外设。 board_init_r 函数位于 commmon/board_r.c 中,实际上
board_init_f 函数并没有初始化所有的外设,还需要做一些后续工作,这些后续工作就是由函数board_init_r 函数来完成。
board_init_r 函数中含有 init_sequence_r 函数,该函数用于初始化序列。而 init_sequence_r 函数又含有 run_main_loop 函数,用于进入 uboot 命令模式或启动linux内核。run_main_loop 函数中最重要的是 main_loop 函数,该函数位于 common/main.c 中。
board_init_r 函数如下:
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
#ifdef CONFIG_NEEDS_MANUAL_RELOCint i;
#endif#ifdef CONFIG_AVR32mmu_init_r(dest_addr);
#endif#if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64)gd = new_gd;
#endif#ifdef CONFIG_NEEDS_MANUAL_RELOCfor (i = 0; i < ARRAY_SIZE(init_sequence_r); i++)init_sequence_r[i] += gd->reloc_off;
#endifif (initcall_run_list(init_sequence_r))hang();/* NOTREACHED - run_main_loop() does not return */hang();
}
上段代码中关键的是 initcall_run_list 函数,调用 initcall_run_list 函数来执行初始化序列 init_sequence_r, init_sequence_r 是一个函数集合, init_sequence_r 也定义在文件 common/board_r.c 中,由于 init_sequence_f 的内容比较长,里面有大量的条件编译代码,这里为了缩小篇幅,将条件编译部分删除掉了,去掉条件编译以后的 init_sequence_r 定义如下:
1 init_fnc_t init_sequence_r[] = {
2 initr_trace,
3 initr_reloc,
4 initr_caches,
5 initr_reloc_global_data,
6 initr_barrier,
7 initr_malloc,
8 initr_console_record,
9 bootstage_relocate,
10 initr_bootstage,
11 board_init, /* Setup chipselects */
12 stdio_init_tables,
13 initr_serial,
14 initr_announce,
15 INIT_FUNC_WATCHDOG_RESET
16 INIT_FUNC_WATCHDOG_RESET
17 INIT_FUNC_WATCHDOG_RESET
18 power_init_board,
19 initr_flash,
20 INIT_FUNC_WATCHDOG_RESET
21 initr_nand,
22 initr_mmc,
23 initr_env,
24 INIT_FUNC_WATCHDOG_RESET
25 initr_secondary_cpu,
26 INIT_FUNC_WATCHDOG_RESET
27 stdio_add_devices,
28 initr_jumptable,
29 console_init_r, /* fully init console as a device */
30 INIT_FUNC_WATCHDOG_RESET
31 interrupt_init,
32 initr_enable_interrupts,
33 initr_ethaddr,
34 board_late_init,
35 INIT_FUNC_WATCHDOG_RESET
36 INIT_FUNC_WATCHDOG_RESET
37 INIT_FUNC_WATCHDOG_RESET
38 initr_net,
39 INIT_FUNC_WATCHDOG_RESET
40 run_main_loop,
41 };
第 2 行, initr_trace 函数,如果定义了宏 CONFIG_TRACE 的话就会调用函数 trace_init,初始化和调试跟踪有关的内容。
第 3 行, initr_reloc 函数用于设置 gd->flags,标记重定位完成。
第 4 行, initr_caches 函数用于初始化 cache,使能 cache。
第 5 行, initr_reloc_global_data 函数,初始化重定位后 gd 的一些成员变量。
第 6 行, initr_barrier 函数, I.MX6ULL 未用到。
第 7 行, initr_malloc 函数,初始化 malloc。
第 8 行, initr_console_record 函数,初始化控制台相关的内容, I.MX6ULL 未用到,空函数。
第 9 行, bootstage_relocate 函数,启动状态重定位。
第 10 行, initr_bootstage 函数,初始化 bootstage 什么的。
第 11 行, board_init 函数,板级初始化,包括 74XX 芯片, I2C、 FEC、 USB 和 QSPI 等。
这里执行的是 mx6ull_alientek_emmc.c 文件中的 board_init 函数。
第 12 行, stdio_init_tables 函数, stdio 相关初始化。
第 13 行, initr_serial 函数,初始化串口。
第 14 行, initr_announce 函数,与调试有关,通知已经在 RAM 中运行。
第 18 行, power_init_board 函数,初始化电源芯片
第 19 行, initr_flash 函数,对于 I.MX6ULL 而言,没有定义宏 CONFIG_SYS_NO_FLASH的话函数 initr_flash 才有效。但是 mx6_common.h 中定义了宏 CONFIG_SYS_NO_FLASH,所以此函数无效。
第 21 行, initr_nand 函数,初始化 NAND,如果使用 NAND 版本核心板的话就会初始化NAND。
第 22 行, initr_mmc 函数,初始化 EMMC,如果使用 EMMC 版本核心板的话就会初始化EMMC
第 23 行, initr_env 函数,初始化环境变量。
第 25 行, initr_secondary_cpu 函数,初始化其他 CPU 核, I.MX6ULL 只有一个核,因此此函数没用。
第 27 行, stdio_add_devices 函数,各种输入输出设备的初始化,如 LCD driver
第 28 行, initr_jumptable 函数,初始化跳转表。
第 29 行 , console_init_r 函 数 , 控 制 台 初 始 化 , 初 始 化 完 成 以 后 此 函 数 会 调 用stdio_print_current_devices 函数来打印出当前的控制台设备
第 31 行, interrupt_init 函数,初始化中断。
第 32 行, initr_enable_interrupts 函数,使能中断。
第 33 行, initr_ethaddr 函数,初始化网络地址,也就是获取 MAC 地址。读取环境变量“ethaddr”的值。
第 34 行, board_late_init 函数,板子后续初始化,如果环境变量存储在 EMMC 或者 SD 卡中的话此函数会调用 board_late_mmc_env_init 函数初始化 EMMC/SD。会切换到正在时候用的 emmc 设备
第 38 行 , initr_net 函 数 , 初 始 化 网 络 设 备
第 40 行, run_main_loop 行,主循环,处理命令。
★run_main_loop 函数中最重要的是 main_loop 函数,该函数主要功能如下:
1、打印启动进度
2、设置环境变量
3、cli_init()//初始化hush shell相关变量
4、run_preboot_environment_command()//获取环境变量prebooot的内容,preboot是一些预启动命令,一般不使用该环境变量
5、bootdelay_process()//获取bootdelay的值,然后保存到stored_bootdelay全局变量里面,获取bootcmd环境变量值,并且将其返回
6、autoboot_command(bootcmd)---> abortboot(stored_bootdelay)//参数为bootdelay,该函数用于处理倒计时---> abortboot_normal(bootdelay)//参数为bootdelay,该函数用于处理倒计时
7、cli_loop()//uboot命令处理函数 common/cli.c---> parse_file_outer()//common/cli_hush.c ---> rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);//hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令---> parse_stream()//命令解析---> run_list()//运行命令---> run_list_real()---> run_pipe_real()---> cmd_process()//处理命令,即执行命令。Uboot使用 U_BOOT_CMD 来定义一个命令。 CONFIG_CMD_XXX来使能uboot中的某个命令。U_BOOT_CMD最终是定义了一个cmd_tbl_t类型的变量,所有的命令最终都是存放在.u_boot_list段里面。cmd_tbl_t的cmd成员变量就是具体的命令执行函数,命令执行函数都是do_xxx。---> find_cmd()//从.u_boot_list段里查找命令,当找到对应的命令以cmd_tlb_t类型返回---> cmd_call()//cmdtp->cmd,直接引用cmd成员变量

以上就是本篇博客对uboot启动流程的详细解读。
四、uboot启动流程总结

uboot启动流程总结:
uboot的语言构成:10%的汇编语言;90%的C语言
uboot的启动特性:稳定性;速度
uboot的简化版启动流程:
1、设置状态寄存器 cpsr ,使CPU进入 SVC 特权模式,并且禁止 FIQ 和 IRQ;
2、关闭看门狗、中断、MMU、Cache;
3、初始化部分寄存器和外设(时钟、串口、Flash、内存);
4、自搬移uboot到内存中运行;
5、设置栈空间并初始化global_data;
6、剩余大部分硬件的初始化;
7、搬移Linux内核到内存;
相关文章:
Linux驱动开发:uboot启动流程详解
前言:uboot作为Linux驱动开发的 “三巨头” 之一,绝对是一座绕不开的大山。当然,即使不去细致了解uboot启动流程依旧不影响开发者对uboot的简单移植。但秉持着知其然知其所以然的学习态度,作者将给读者朋友细致化的过一遍uboot启动…...
分治与减治算法实验: 排序中减治法的程序设计
目录 前言 实验内容 实验目的 实验分析 实验过程 流程演示 写出伪代码 实验代码 代码详解 运行结果 总结 前言 本文介绍了算法实验排序中减治法的程序设计。减治法是一种常用的算法设计技术,它通过减少问题的规模来求解问题。减治法可以应用于排序问题&…...
leetcode两数、三数、四数之和
如有错误,感谢不吝赐教、交流 文章目录 两数之和题目方法一:暴力两重循环(不可取)方法二:HashMap空间换时间 三数之和题目方法一:当然是暴力破解啦方法二:同两数之和的原理,借助Has…...
使用Docker部署wikitten个人知识库
使用Docker部署wikitten个人知识库 一、wikitten介绍1.wikitten简介2.wikitten特点 二、本地实践环境介绍三、本地环境检查1.检查Docker服务状态2.检查Docker版本 四、部署wikitten个人知识库1.创建数据目录2.下载wikitten镜像3.创建wikitten容器4.查看wikitten容器状态5.检查w…...
【MYSQL】Java的JDBC编程(idea连接数据库)
1. 配置 (1)新建一个项目 (2)Build System 那里选择Maven,下一步Create (3)配置pom.xml文件 首先查看自己的MYSQL版本:进入MySQL命令窗口 我的MYSQL版本是8.0版本的. 下一步,…...
机器学习——主成分分析法(PCA)概念公式及应用python实现
机器学习——主成分分析法(PCA) 文章目录 机器学习——主成分分析法(PCA)一、主成分分析的概念二、主成分分析的步骤三、主成分分析PCA的简单实现四、手写体识别数字降维 一、主成分分析的概念 主成分分析(PCA&#x…...
手写axios源码系列二:创建axios函数对象
文章目录 一、模块化目录介绍二、创建 axios 函数对象1、创建 axios.js 文件2、创建 defaults.js 文件3、创建 _Axios.js 文件4、总结 当前篇章正式进入手写 axios 源码系列,我们要真枪实弹的开始写代码了。 因为 axios 源码的代码量比较庞大,所以我们这…...
HTB-Time
HTB-Time 信息收集80端口 立足pericles -> root 信息收集 80端口 有两个功能,一个是美化JSON数据。 一个是验证JSON,并且输入{“abc”:“abc”}之类的会出现报错。 Validation failed: Unhandled Java exception: com.fasterxml.jackson.core.JsonPa…...
零基础C/C++开发到底要学什么?
作者:黑马程序员 链接:https://www.zhihu.com/question/597037176/answer/2999707086 先和我一起看看,C/C学完了可以做什么: 软件工程师:负责设计、开发、测试和维护各类型的软件应用程序;游戏开发&#x…...
OpenStack中的CPU与内存超分详解
目录 什么是超分 CPU超分 查看虚拟机虚拟CPU运行在哪些物理CPU上 内存超分 内存预留 内存共享 如何设置内存预留和内存共享 全局设置 临时设置 什么是超分 超分通常指的是CPU或者GPU的分区或者分割,以在一个物理CPU或GPU内模拟多个逻辑CPU或GPU的功能。这…...
main.m文件解析--@autoreleasepool和UIApplicationMain
iOS 程序入口UIApplicationMain详解,相信大家新建一个工程的时候都会看到一个main.m文件,只不过我们很少了解它,现在我们分析一下它的作用是什么? 一、main.m文件 int main(int argc, char * argv[]) {autoreleasepool {return …...
C语言复习之顺序表(十五)
📖作者介绍:22级树莓人(计算机专业),热爱编程<目前在c阶段>——目标C、Windows,MySQL,Qt,数据结构与算法,Linux,多线程,会持续分享…...
学系统集成项目管理工程师(中项)系列10_立项管理
1. 系统集成项目管理至关重要的一个环节 2. 重点在于是否要启动一个项目,并为其提供相应的预算支持 3. 项目建议 3.1. Request for Proposal, RFP 3.2. 立项申请 3.3. 项目建设单位向上级主管部门提交的项目申请文件,是对拟建项目提出的总体设想 3…...
电视盒子哪个好?数码小编盘点2023电视盒子排行榜
随着网络剧的热播,电视机又再度受宠,电视盒子也成为不可缺少的小家电。但面对复杂的参数和品牌型号,挑选时不知道电视盒子哪款最好,小编根据销量和用户评价整理半个月后盘点了电视盒子排行榜前五,对电视盒子哪个好感兴…...
flink动态表的概念详解
目录 前言🚩 动态表和持续不断查询 stream转化成表 连续查询 查询限制 表转化为流 前言🚩 传统的数据库SQL和实时SQL处理的差别还是很大的,这里简单列出一些区别: 尽管存在这些差异,但使用关系查询和SQL处理流并…...
ArcGIS Pro用户界面
目录 1 功能区 1.1 快速访问工具栏 1.2 自定义快速访问工具栏 1.3 自定义功能区选项 1.3.1 添加组和命令 1.3.2 添加新选项卡 2 视图 3 用户界面排列 编辑 4 窗格 4.1 内容窗格 4.2 目录窗格 4.3 目录视图(类似ArcCatalog) 4.4 浏览对话框…...
HDCTF 2023 Pwn WriteUp
Index 前言Pwnner分析EXP: KEEP_ON分析EXP: Minions分析EXP: 后记: 前言 本人是菜狗,比赛的时候只做出来1题,2题有思路但是不会,还是太菜了。 栈迁移还是不会,但又都是栈迁移的题,真头大。得找时间好好学学…...
【 Spring 事务 】
文章目录 一、为什么需要事务(简单回顾)二、MySQL 中的事务使⽤三、Spring 中事务的实现3.1 Spring 编程式事务(手动事务)3.2 Spring 声明式事务(自动事务)3.2.1 Transactional 作⽤范围3.2.2 Transactional 参数说明3.2.3 Transactional 不进行事务回滚的情况3.2.4 Transactio…...
【刷题之路】LeetCode 203. 移除链表元素
【刷题之路】LeetCode 203. 移除链表元素 一、题目描述二、解题1、方法1——在原链表上动刀子1.1、思路分析1.2、代码实现 2、方法2——使用额外的链表2.1、思路分析2.2、代码实现 一、题目描述 原题连接: 203. 移除链表元素 题目描述: 给你一个链表的…...
关于Open Shift(OKD) 中 用户认证、权限管理、SCC 管理的一些笔记
写在前面 因为参加考试,会陆续分享一些 OpenShift 的笔记博文内容为 openshift 用户认证和权限管理以及 scc 管理相关笔记学习环境为 openshift v3 的版本,有些旧这里如果专门学习 openshift ,建议学习 v4 版本理解不足小伙伴帮忙指正 对每个…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
