u-boot(六) - 详细启动流程

一,u-boot启动第一阶段

1,启动流程

ENTRY(_start) //arch/arm/lib/vectors.S
----b resets //arch/arm/cpu/armv7/start.S
--------b save_boot_params
------------b save_boot_params_ret //将cpu的工作模式设置为SVC32模式(即管理模式),同时将中断禁止位与快速中断禁止位都设置为1, 以此屏蔽IRQ和FIQ的中断
--------bl    cpu_init_cp15 //关闭mmu,不需要它转换地址,直接操作寄存器方便快捷
--------bl    cpu_init_crit
------------b    lowlevel_init //arch/arm/cpu/armv7/lowlevel_init.S ,与特定开发板相关的初始化函数,在这个函数里会做一些pll初始化,如果不是从内存启动,则会做内存初始化,方便后续拷贝到内存中运行。
--------bl    _main //arch/arm/lib/crt0.S //初始化c语言环境,以便调用board_init_f函数。这个环境只提供了一个堆栈和一个存储GD(全局数据)结构的地方,两者都位于一些可用的RAM中。在调用board_init_f()之前,GD应该被归零。
------------bl          board_init_f_alloc_reserve // common/init/board_init.c,该函数主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址
------------bl        board_init_f_init_reserve //common/init/board_init.c 将GD区域清零,返回最初malloc区域的地址
------------bl        board_init_f //common/board_f.c
----------------initcall_run_list(init_sequence_f)
------------b    relocate_code //arch/arm/lib/relocate.S,实现u-boot自身重定位的
----------------ldr       r1, =__image_copy_start /* r1 <- SRC &__image_copy_start */
----------------ldr       r2, =__image_copy_end   /* r2 <- SRC &__image_copy_end */ 从__image_copy_start地址到__image_copy_end地址中间包含了代码段、数据段以及只读数据段,但是不包括动态链接rel_dyn部分
----------------ldr       r2, =__rel_dyn_start    /* r2 <- SRC &__rel_dyn_start */
----------------ldr       r3, =__rel_dyn_end      /* r3 <- SRC &__rel_dyn_end */ 直到修改完整个__rel_dyn段后结束,完成重定位
------------bl    relocate_vectors //主要完成的工作就是实现异常向量表的重定位,拷贝到正确的地址中去------------bl    c_runtime_cpu_setup //关闭指令缓存I-cache,重定位后到了新的介质中运行也是要设置一下运行环境------------ldr    r0, =__bss_start    /* this is auto-relocated! */
------------ldr    r3, =__bss_end        /* this is auto-relocated! */
------------bl    memset //清除BSS段------------ldr    pc, =board_init_r    /* this is auto-relocated! */ 第二阶段的入口board_init_r,common/board_r.c
----------------initcall_run_list(init_sequence_r)

_main:

arch/arm/lib/crt0.S

/*
* entry point of crt0 sequence
*/ENTRY(_main)/*
* Set up initial C runtime environment and call board_init_f(0).
*/#if defined(CONFIG_TPL_BUILD) && defined(CONFIG_TPL_NEEDS_SEPARATE_STACK)ldr    r0, =(CONFIG_TPL_STACK)
#elif defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr    r0, =(CONFIG_SPL_STACK)
#elseldr    r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endifbic    r0, r0, #7    /* 8-byte alignment for ABI compliance */mov    sp, r0bl    board_init_f_alloc_reservemov    sp, r0/* set up gd here, outside any C code */mov    r9, r0 //gd是一个保存在ARM的r9寄存器中的gd_t结构体的指针bl    board_init_f_init_reserve#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_EARLY_BSS)CLEAR_BSS
#endifmov    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    r0, [r9, #GD_START_ADDR_SP]    /* sp = gd->start_addr_sp */bic    r0, r0, #7    /* 8-byte alignment for ABI compliance */mov    sp, r0ldr    r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */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) || CONFIG_IS_ENABLED(FRAMEWORK)#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)CLEAR_BSS
#endif# 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
# 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 CONFIG_IS_ENABLED(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)

2,global data

2.1 为什么使用global data

u-boot是一个bootloader,有些情况下,它可能位于系统的只读存储器(ROM或者flash)中,并从那里开始执行。

因此,这种情况下,在u-boot执行的前期(在将自己copy到可读写的存储器之前),它所在的存储空间,是不可写的,这会有两个问题:

    1)堆栈无法使用,无法执行函数调用,也即C环境不可用。

    2)没有data段(或者正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据。

对于问题1,通常的解决方案是:

u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。

一般来说,大部分的平台(如很多ARM平台),都有自己的SRAM,可用作堆栈空间。如果实在不行,也有可借用CPU的data cache的方法。

对于问题2,解决方案要稍微复杂一些:

首先,对于开发者来说,在u-boot被拷贝到可读写的RAM(这个动作称作relocation)之前,永远不要使用全局变量。

其次,在relocation之前,不同模块之间,确实有通过全局变量的形式传递数据的需求。怎么办?这就是global data需要解决的事情。

2.2 GD空间分配和初始化

为了在relocation前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:

    1)定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据。

    2)堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间,并将开始地址(就是一个struct global_data指针)保存在一个寄存器中,后续的传递,都是通过保存在寄存器中的指针实现。

arch/arm/lib/crt0.S

ENTRY(_main)/** Set up initial C runtime environment and call board_init_f(0).*/#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)ldr       r0, =(CONFIG_SPL_STACK)
#elseldr       r0, =(CONFIG_SYS_INIT_SP_ADDR) //加载CONFIG_SYS_INIT_SP_ADDR到r0寄存器
#endifbic       r0, r0, #7      /* 8-byte alignment for ABI compliance */ 遵从ABI的8字节对齐mov       sp, r0 //将堆栈指针指向r0寄存器的值bl        board_init_f_alloc_reserve //主要作用是保留早期malloc区域,且为GD(全局数据区)留出空间,函数返回值也是r0,r0保存着预留早期malloc区域和GD后的地址mov       sp, r0/* set up gd here, outside any C code */mov       r9, r0 //定义一个寄存器全局变量指针,并指定其使用的寄存器是r9,类型为gd_tbl        board_init_f_init_reserve //该函数主要作用是将GD区域清零,返回最初malloc区域的地址mov       r0, #0 //清空r0,然后把参数r0传给board_init_f函数,并调用board_init_fbl        board_init_f
2.3 gt_t 和 bd_t 结构体

gd_t结构体几乎包含了u-boot中用到的所有全局变量, gd_t和bd_t都u-boot中两个重要的数据结构,在初始化操作很多都要靠这两个数据结构来保存或传递。 gd_t结构体如下所示:

include/asm-generic/global_data.h

typedef struct global_data {bd_t *bd;unsigned long flags;unsigned int baudrate;unsigned long cpu_clk;        /* CPU clock in Hz!        */unsigned long bus_clk;/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */unsigned long pci_clk;unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)unsigned long fb_base;        /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST) || defined(CONFIG_LOGBUFFER)unsigned long post_log_word;    /* Record POST activities */unsigned long post_log_res;    /* success of POST test */unsigned long post_init_f_time;    /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPESunsigned long board_type;
#endifunsigned long have_console;    /* serial_init() was called */
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)unsigned long precon_buf_idx;    /* Pre-Console buffer index */
#endifunsigned long env_addr;        /* Address  of Environment struct */unsigned long env_valid;    /* Checksum of Environment valid? */unsigned long ram_top;        /* Top address of RAM used by U-Boot */unsigned long relocaddr;    /* Start address of U-Boot in RAM */phys_size_t ram_size;        /* RAM size */unsigned long mon_len;        /* monitor len */unsigned long irq_sp;        /* irq stack pointer */unsigned long start_addr_sp;    /* start_addr_stackpointer */unsigned long reloc_off;struct global_data *new_gd;    /* relocated global data */#ifdef CONFIG_DMstruct udevice    *dm_root;    /* Root instance for Driver Model */struct udevice    *dm_root_f;    /* Pre-relocation root instance */struct list_head uclass_root;    /* Head of core tree */
#endif
#ifdef CONFIG_TIMERstruct udevice    *timer;        /* Timer instance for Driver Model */
#endifconst void *fdt_blob;        /* Our device tree, NULL if none */void *new_fdt;            /* Relocated FDT */unsigned long fdt_size;        /* Space reserved for relocated FDT */struct jt_funcs *jt;        /* jump table */char env_buf[32];        /* buffer for getenv() before reloc. */
#ifdef CONFIG_TRACEvoid        *trace_buff;    /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)int        cur_i2c_bus;    /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXCvoid *srdata[10];
#endifunsigned long timebase_h;unsigned long timebase_l;
#ifdef CONFIG_SYS_MALLOC_F_LENunsigned long malloc_base;    /* base address of early malloc() */unsigned long malloc_limit;    /* limit address */unsigned long malloc_ptr;    /* current address */
#endif
#ifdef CONFIG_PCIstruct pci_controller *hose;    /* PCI hose for early use */phys_addr_t pci_ram_top;    /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAYint pcidelay_done;
#endifstruct udevice *cur_serial_dev;    /* current serial device */struct arch_global_data arch;    /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORDstruct membuff console_out;    /* console output */struct membuff console_in;    /* console input */
#endif
#ifdef CONFIG_DM_VIDEOulong video_top;        /* Top of video frame buffer area */ulong video_bottom;        /* Bottom of video frame buffer area */
#endif
} gd_t;

bd_t结构体如下所示:

typedef struct bd_info {unsigned long    bi_memstart;    /* start of DRAM memory */phys_size_t    bi_memsize;    /* size     of DRAM memory in bytes */unsigned long    bi_flashstart;    /* start of FLASH memory */unsigned long    bi_flashsize;    /* size     of FLASH memory */unsigned long    bi_flashoffset; /* reserved area for startup monitor */unsigned long    bi_sramstart;    /* start of SRAM memory */unsigned long    bi_sramsize;    /* size     of SRAM memory */
#ifdef CONFIG_AVR32unsigned char   bi_phy_id[4];   /* PHY address for ATAG_ETHERNET */unsigned long   bi_board_number;/* ATAG_BOARDINFO */
#endif
#ifdef CONFIG_ARMunsigned long    bi_arm_freq; /* arm frequency */unsigned long    bi_dsp_freq; /* dsp core frequency */unsigned long    bi_ddr_freq; /* ddr frequency */
#endif
#if defined(CONFIG_5xx) || defined(CONFIG_8xx) || defined(CONFIG_MPC8260) \|| defined(CONFIG_E500) || defined(CONFIG_MPC86xx)unsigned long    bi_immr_base;    /* base of IMMR register */
#endif
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)unsigned long    bi_mbar_base;    /* base of internal registers */
#endif
#if defined(CONFIG_MPC83xx)unsigned long    bi_immrbar;
#endifunsigned long    bi_bootflags;    /* boot / reboot flag (Unused) */unsigned long    bi_ip_addr;    /* IP Address */unsigned char    bi_enetaddr[6];    /* OLD: see README.enetaddr */unsigned short    bi_ethspeed;    /* Ethernet speed in Mbps */unsigned long    bi_intfreq;    /* Internal Freq, in MHz */unsigned long    bi_busfreq;    /* Bus Freq, in MHz */
#if defined(CONFIG_CPM2)unsigned long    bi_cpmfreq;    /* CPM_CLK Freq, in MHz */unsigned long    bi_brgfreq;    /* BRG_CLK Freq, in MHz */unsigned long    bi_sccfreq;    /* SCC_CLK Freq, in MHz */unsigned long    bi_vco;        /* VCO Out from PLL, in MHz */
#endif
#if defined(CONFIG_MPC512X)unsigned long    bi_ipsfreq;    /* IPS Bus Freq, in MHz */
#endif /* CONFIG_MPC512X */
#if defined(CONFIG_MPC5xxx) || defined(CONFIG_M68K)unsigned long    bi_ipbfreq;    /* IPB Bus Freq, in MHz */unsigned long    bi_pcifreq;    /* PCI Bus Freq, in MHz */
#endif
#if defined(CONFIG_EXTRA_CLOCK)unsigned long bi_inpfreq;    /* input Freq in MHz */unsigned long bi_vcofreq;    /* vco Freq in MHz */unsigned long bi_flbfreq;    /* Flexbus Freq in MHz */
#endif
#if defined(CONFIG_405)   || \defined(CONFIG_405GP) || \defined(CONFIG_405EP) || \defined(CONFIG_405EZ) || \defined(CONFIG_405EX) || \defined(CONFIG_440)unsigned char    bi_s_version[4];    /* Version of this structure */unsigned char    bi_r_version[32];    /* Version of the ROM (AMCC) */unsigned int    bi_procfreq;    /* CPU (Internal) Freq, in Hz */unsigned int    bi_plb_busfreq;    /* PLB Bus speed, in Hz */unsigned int    bi_pci_busfreq;    /* PCI Bus speed, in Hz */unsigned char    bi_pci_enetaddr[6];    /* PCI Ethernet MAC address */
#endif#ifdef CONFIG_HAS_ETH1unsigned char   bi_enet1addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2unsigned char    bi_enet2addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3unsigned char   bi_enet3addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4unsigned char   bi_enet4addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5unsigned char   bi_enet5addr[6];    /* OLD: see README.enetaddr */
#endif#if defined(CONFIG_405GP) || defined(CONFIG_405EP) || \defined(CONFIG_405EZ) || defined(CONFIG_440GX) || \defined(CONFIG_440EP) || defined(CONFIG_440GR) || \defined(CONFIG_440EPX) || defined(CONFIG_440GRX) || \defined(CONFIG_460EX) || defined(CONFIG_460GT)unsigned int    bi_opbfreq;        /* OPB clock in Hz */int        bi_iic_fast[2];        /* Use fast i2c mode */
#endif
#if defined(CONFIG_4xx)
#if defined(CONFIG_440GX) || \defined(CONFIG_460EX) || defined(CONFIG_460GT)int        bi_phynum[4];           /* Determines phy mapping */int        bi_phymode[4];          /* Determines phy mode */
#elif defined(CONFIG_405EP) || defined(CONFIG_405EX) || defined(CONFIG_440)int        bi_phynum[2];           /* Determines phy mapping */int        bi_phymode[2];          /* Determines phy mode */
#elseint        bi_phynum[1];           /* Determines phy mapping */int        bi_phymode[1];          /* Determines phy mode */
#endif
#endif /* defined(CONFIG_4xx) */ulong            bi_arch_number;    /* unique id for this board */ulong            bi_boot_params;    /* where this board expects params */
#ifdef CONFIG_NR_DRAM_BANKSstruct {            /* RAM configuration */phys_addr_t start;phys_size_t size;} bi_dram[CONFIG_NR_DRAM_BANKS];
#endif /* CONFIG_NR_DRAM_BANKS */
} bd_t;

3,前置的板级初始化操作

3.1 board_init_f

global data准备好之后,u-boot会执行前置的板级初始化动作,即board_init_f。所谓的前置的初始化动作,主要是relocation之前的初始化操作,也就是说:执行board_init_f的时候,u-boot很有可能还在只读的存储器中。

common/board_f.c

void board_init_f(ulong boot_flags)
{gd->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) && !CONFIG_IS_ENABLED(X86_64) && \      !defined(CONFIG_ARC)/* NOTREACHED - jump_to_copy() does not return */hang();
#endif
}

对global data进行简单的初始化之后,调用位于init_sequence_f数组中的各种初始化API,进行各式各样的初始化动作。后面将会简单介绍一些和ARM平台有关的、和平台的移植工作有关的、比较重要的API。其它API,大家可以参考source code自行理解。

3.2 init_sequence_f

common/board_f.c

static const init_fnc_t init_sequence_f[] = {setup_mon_len,          /* 设置gd->mon_len为编译出来的u-boot.bin+bss段的大小 */fdtdec_setup,           /* 和设备树有关 */initf_malloc,           /* 初始化并设置内存池 */log_init,               /* log初始化 */initf_bootstage,        /* 用于记录board_init_f()的引导阶段 */setup_spl_handoff,initf_console_record,   /* 平台信息记录初始化 */arch_cpu_init,                  /* 空函数 */mach_cpu_init,                  /* 空函数 */initf_dm,               /* 驱动模型初始化 */arch_cpu_init_dm,       /* 空函数 */board_early_init_f,     /* 设置时钟和GPIO */timer_init,                        /* 定时器初始化 */env_init,                  /* 找到最适合存放环境变量的地址,并初始化 */init_baud_rate,              /* 波特率初始化 */serial_init,                    /* 串口初始化 */console_init_f,              /* 使能在重定位之前用的串口功能 gd->have_console = 1 */display_options,        /* 显示banner,如u-boot版本、编译时间等信息 */display_text_info,           /* 显示调试信息 */print_cpuinfo,                  /* 显示cpu信息,如cpu速度 */show_board_info,        /* 显示板子信息 */announce_dram_init,     /* 准备显示DRAM大小,在u-boot启动时可以看到DRAM大小的信息 */dram_init,                         /* DRAM初始化,对于本imx6ull设置dg->ram_size = 512 MiB */setup_dest_addr,        /* 设置重定位地址,gd->relocaddr = gd->ram_top */reserve_round_4k,       /* 4字节对齐,将内存指针调到下一个4 kB */reserve_mmu,            /* 为mmu区域腾出空间 */reserve_video,          /* 预留video显示内存 */reserve_trace,reserve_uboot,          /* 预留U-Boot代码、data和bss区  */reserve_malloc,         /* 预留malloc区 */reserve_board,          /* 预留存放板子信息区 */setup_machine,          /* 板子ID,这里没有用到 */reserve_global_data,    /* 预留GD区域,栈gd->start_addr_sp指向gd段基地址*/reserve_fdt,            /* 预留设备树区域 */reserve_bootstage,reserve_bloblist,reserve_arch,           /* 架构相关预留区 */reserve_stacks,         /* 预留栈区,gd->start_addr_sp指向栈底基地址 */dram_init_banksize,     /* DRAM的大小初始化 */show_dram_config,       /* 显示DRAM的配置 */display_new_sp,         /* 显示新的栈地址 */reloc_fdt,              /* 和设备树有关 */reloc_bootstage,        /* 和u-boot阶段有关 */reloc_bloblist,         /* 和blob列表有关 */setup_reloc,            /* 重定位 */NULL,
};
3.3 env_init

common/env_mmc.c

int env_init(void)
{/* use default */gd->env_addr    = (ulong)&default_environment[0]; //default_environment[]数组存放着默认的环境变量,将默认环境变量default_environment的地址赋值给全局变量gd->env_addrgd->env_valid    = 1;return 0;
}

include/env_default.h

static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef    CONFIG_ENV_CALLBACK_LIST_DEFAULTENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
#endif
#ifdef    CONFIG_ENV_FLAGS_LIST_DEFAULTENV_FLAGS_VAR "=" CONFIG_ENV_FLAGS_LIST_DEFAULT "\0"
#endif
#ifdef    CONFIG_BOOTARGS"bootargs="    CONFIG_BOOTARGS            "\0"
#endif
#ifdef    CONFIG_BOOTCOMMAND"bootcmd="    CONFIG_BOOTCOMMAND        "\0"
#endif
#ifdef    CONFIG_RAMBOOTCOMMAND"ramboot="    CONFIG_RAMBOOTCOMMAND        "\0"
#endif
#ifdef    CONFIG_NFSBOOTCOMMAND"nfsboot="    CONFIG_NFSBOOTCOMMAND        "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)"bootdelay="    __stringify(CONFIG_BOOTDELAY)    "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)"baudrate="    __stringify(CONFIG_BAUDRATE)    "\0"
#endif
#ifdef    CONFIG_LOADS_ECHO"loads_echo="    __stringify(CONFIG_LOADS_ECHO)    "\0"
#endif
#ifdef    CONFIG_ETHPRIME"ethprime="    CONFIG_ETHPRIME            "\0"
#endif
#ifdef    CONFIG_IPADDR"ipaddr="    __stringify(CONFIG_IPADDR)    "\0"
#endif
#ifdef    CONFIG_SERVERIP"serverip="    __stringify(CONFIG_SERVERIP)    "\0"
#endif
#ifdef    CONFIG_SYS_AUTOLOAD"autoload="    CONFIG_SYS_AUTOLOAD        "\0"
#endif
#ifdef    CONFIG_PREBOOT"preboot="    CONFIG_PREBOOT            "\0"
#endif
#ifdef    CONFIG_ROOTPATH"rootpath="    CONFIG_ROOTPATH            "\0"
#endif
#ifdef    CONFIG_GATEWAYIP"gatewayip="    __stringify(CONFIG_GATEWAYIP)    "\0"
#endif
#ifdef    CONFIG_NETMASK"netmask="    __stringify(CONFIG_NETMASK)    "\0"
#endif
#ifdef    CONFIG_HOSTNAME"hostname="    __stringify(CONFIG_HOSTNAME)    "\0"
#endif
#ifdef    CONFIG_BOOTFILE"bootfile="    CONFIG_BOOTFILE            "\0"
#endif
#ifdef    CONFIG_LOADADDR"loadaddr="    __stringify(CONFIG_LOADADDR)    "\0"
#endif
#ifdef    CONFIG_CLOCKS_IN_MHZ"clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)"pcidelay="    __stringify(CONFIG_PCI_BOOTDELAY)"\0"
#endif
#ifdef    CONFIG_ENV_VARS_UBOOT_CONFIG"arch="        CONFIG_SYS_ARCH            "\0"
#ifdef CONFIG_SYS_CPU"cpu="        CONFIG_SYS_CPU            "\0"
#endif
#ifdef CONFIG_SYS_BOARD"board="    CONFIG_SYS_BOARD        "\0""board_name="    CONFIG_SYS_BOARD        "\0"
#endif
#ifdef CONFIG_SYS_VENDOR"vendor="    CONFIG_SYS_VENDOR        "\0"
#endif
#ifdef CONFIG_SYS_SOC"soc="        CONFIG_SYS_SOC            "\0"
#endif
#endif
#ifdef    CONFIG_EXTRA_ENV_SETTINGSCONFIG_EXTRA_ENV_SETTINGS
#endif"\0"
#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED}
#endif
};

可以根据宏定义去配置默认的环境变量,如bootargs、bootcmd、bootdelay等。

例如修改Uboot-2017.03/configs/mx6ull_14x14_evk_defconfig 中的CONFIG_BOOTDELAY,便可以设置默认的u-boot延时时间。

3.4 dram_init
#if defined(CONFIG_ARM) || defined(CONFIG_X86) || defined(CONFIG_NDS32) || \defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || \defined(CONFIG_SH)dram_init,        /* configure available RAM banks */
#endif

调用dram_init接口,初始化系统的DDR。dram_init应该由平台相关的代码实现。

如果DDR在SPL中已经初始化过了,则不需要重新初始化,只需要把DDR信息保存在global data中即可,例如:

gd->ram_size = …
3.5 DRAM空间的分配

DRAM初始化完成后,就可以着手规划u-boot需要使用的部分,如下图:

总结如下:

1)考虑到后续的kernel是在RAM的低端位置解压缩并执行的,为了避免麻烦,u-boot将使用DRAM的顶端地址,即gd->ram_top所代表的位置。其中gd->ram_top是由setup_dest_addr函数配置的。

2)u-boot所使用的DRAM,主要分为三类:各种特殊功能所需的空间,如log buffer、MMU page table、LCD fb buffer、trace buffer、等等;u-boot的代码段、数据段、BSS段所占用的空间(就是u-boot relocate之后的执行空间),由gd->relocaddr标示;堆栈空间,从gd->start_addr_sp处递减。

3)特殊功能以及u-boot所需空间,是由reserve_xxx系列函数保留的,具体可参考source code,这里不再详细分析。

4)reserve空间分配完毕后,堆栈紧随其后,递减即可。

3.6 reloc_fdt

reloc_fdt函数负责将设备树数据搬运到新分配的new_fdt地址中去,如下所示:

static int reloc_fdt(void)
{
#ifndef CONFIG_OF_EMBED    /* 有定义 */if (gd->flags & GD_FLG_SKIP_RELOC)return 0;if (gd->new_fdt) {memcpy(gd->new_fdt, gd->fdt_blob, gd->fdt_size); //将老的设备树段拷贝到新的设备树段gd->fdt_blob = gd->new_fdt; //将老的设备树地址更新为新的设备树地址}
#endifreturn 0;
}
3.7 setup_reloc

计算relocation有关的信息,主要是 gd->reloc_off,计算公式如下:

gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;

其中CONFIG_SYS_TEXT_BASE是u-boot relocation之前在(只读)memory的位置(也是编译时指定的位置),gd->relocaddr是relocation之后的位置,因此gd->reloc_off代表u-boot relocation操作之后的偏移量,后面relocation时会用到。

同时,该函数顺便把global data拷贝到了上图所示的“new global data”处,其实就是global data的relocation。

memcpy(gd->new_gd, (char *)gd, sizeof(gd_t));

4,u-boot的relocation

relocate_code函数会传入一个参数,该参数为gd->relocaddr,也就是uboot重定位的目的地址

接下来,对relocate_code函数进行分析,该函数用于重定位uboot,函数的定义在下面的汇编文件中:

uboot/arch/arm/lib/relocate.S

/*
* void relocate_code(addr_moni)
*
* This function relocates the monitor code.
*
* NOTE:
* To prevent the code below from containing references with an R_ARM_ABS32
* relocation record type, we never refer to linker-defined symbols directly.
* Instead, we declare literals which contain their relative location with
* respect to relocate_code, and at run time, add relocate_code back to them.
*/ENTRY(relocate_code)ldr    r1, =__image_copy_start/* r1 <- SRC &__image_copy_start */ //r1保存源image开始地址//r0 = gd->relocaddr,r4 = r0 - r1 = 0x8ff3b000 - 0x87800000 = 0x873b000subs    r4, r0, r1 /* r4 <- relocation offset */ //r4 = gd->reloc_off,保存偏移地址beq    relocate_done    /* skip relocation */ //如果r0和r1相等,则不需要uboot重定位ldr    r2, =__image_copy_end  /* r2 <- SRC &__image_copy_end */  //r2保存源image结束地址copy_loop:ldmia    r1!, {r10-r11}  /* copy from source address [r1] */ //拷贝uboot代码到r10和r11stmia    r0!, {r10-r11}  /* copy to  target address [r0] */ //将uboot代码写到目的地址cmp    r1, r2     /* until source end address [r2] */ //判断uboot是否拷贝完成blo    copy_loop  //循环拷贝/** fix .rel.dyn relocations //修复.rel.dyn段*/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:ENDPROC(relocate_code)

函数传入的参数为r0 = 0x8ff3b000,uboot重定位的目的地址,函数进来后,将__image_copy_start的数值赋值给r1,也就是uboot复制开始地址,从表格可以知道r1 = 0x87800000,接下来进行一个减法操作,此时r4将保存着uboot的偏移量,也就是r4 = gd->reloc_off。

接下来会进行一个判断,判断uboot重定位的目的地址是否和uboot源地址是否相等,如果相等的话,表示不需要重定位,跳到relocate_done处,继续运行,如果不相等的话,则是需要进行重定位。

将__image_copy_end的值赋值给r2,也就是uboot复制结束的地址,从表格中可以知道,此时r2 = 0x87868960,接下来,开始进行uboot代码的拷贝,进入到copy_loop循环,从r1,也就是__image_copy_start开始,读取uboot代码到r10和r11寄存器,一次就拷贝两个32位的数据,r1自动更新,保存下一个需要拷贝的地址,然后将r10和r11里面的数据,写到r0保存的地址,也就是uboot重定位的目的地址,r0自动更新。

接下来,比较r1和r2是否相等,也就是判断uboot代码是否拷贝完成,如果没完成的话,跳转到copy_loop处,继续循环,直到uboot拷贝完成。

函数relocate_code()的第二部分是修复.rel.dyn的定位问题,.rel.dyn段存放了.text段中需要重定位地址(也就是搬移了__image_copy_start~__image_copy_end的代码后,需要对其中存的绝对地址进行更新,即绝对地址+偏移地址)的集合,uboot重定位就是uboot将自身拷贝到DRAM的另外一个地址继续运行(DRAM的高地址),一个可执行的bin文件,它的链接地址和运行地址一定要相等,也就是链接到哪个地址,运行之前就需要拷贝到哪个地址中进行运行,在前面的重定位后,运行地址和链接地址就不一样了,这样在进行寻址就会出问题,对.rel.dyn的定位问题进行修复,就是为了解决该问题。

下面,使用一个简单的例子进行分析:

先在新适配的板级文件中添加测试代码,文件如下:

uboot/board/freescale/mx6ul_comp6ul_nand/mx6ul_comp6ul_nand.c

 添加的内容如下:

static int rel_a = 0;void rel_test(void)
{rel_a = 100;printf("rel_test\n");
}/* 在board_init()函数中调用rel_test()函数 */
int board_init(void)
{......rel_test();return 0;
}

接下来对uboot源码重新编译,并将u-boot文件进行反汇编分析,使用命令如下:

$ cd uboot

$ make

$ arm-linux-gnueabihf-objdump -D -m arm u-boot > u-boot.dis

 然后,打开u-boot.dis文件,并在文件中,找到rel_a变量、rel_test函数和board_init函数相关的汇编代码,如下所示:

/* rel_test寻址分析 */
8780424c <rel_test>:
8780424c:    e59f300c     ldr    r3, [pc, #12]    ; 87804260 <rel_test+0x14>
87804250:    e3a02064     mov    r2, #100    ; 0x64
87804254:    e59f0008     ldr    r0, [pc, #8]    ; 87804264 <rel_test+0x18>
87804258:    e5832000     str    r2, [r3]
8780425c:    ea00f1a9     b    87840908 <printf>
87804260:     87868994             ; <UNDEFINED> instruction: 0x87868994
87804264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
878043e4:    e59f2038     ldr    r2, [pc, #56]    ; 87804424 <board_init+0x1bc>
878043e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
878043ec:    e3833030     orr    r3, r3, #48    ; 0x30
878043f0:    e5823068     str    r3, [r2, #104]    ; 0x68
878043f4:    ebffff94     bl    8780424c <rel_test>
...
...
87868994 <rel_a>:
87868994:    00000000     andeq    r0, r0, r0

 

在0x878043f4处,也就是board_init()函数中调用了rel_test()函数,在汇编代码中,可以看到是使用了bl指令,而bl指令是相对寻址的(pc + offset),因此,可以知道,uboot中的函数调用时与绝对地址无关。

接下来,分析rel_test()函数对全局变量rel_a的调用过程,在0x8780424c处开始,先设置r3的值为pc + 12地址处的值,由于ARM流水线的原因,当前pc的值为当前的地址加8,所以pc = 0x8780424c + 8 = 0x87804254,因此r3 = pc + 12 = ‬0x87804254 + 12 = 0x87804260,从反汇编的代码中可以看到0x87804260处的值为0x87868994,所以,最终r3的值为0x87868994,而且从反汇编代码中可以看到0x87868994就是变量rel_a的地址,rel_test()函数继续执行,将100赋值到r2寄存器,然后通过str指令,将r2的值写到r3的地址处,也就是将0x87868994地址处的值赋值100,就是函数中调用的rel_a = 100;语句。

从上面的分析,可以知道rel_a = 100;的执行过程为:

  • 在函数rel_test()的末尾处有一个地址为0x87804260的内存空间,此内存空间中保存着变量rel_a的地址;

  • 函数rel_test()想要访问变量rel_a,首先需要访问0x87804260内存空间获取变量rel_a的地址,而访问0x87804260是通过偏移来访问的,与位置无关的操作;

  • 通过访问0x87804260获取变量rel_a的地址,然后对变量rel_a进行赋值操作;

  • 函数rel_test()对变量rel_a的访问并没有直接进行,而是通过一个偏移地址0x87804260,找到变量rel_a真正的地址,然后才对其操作,偏移地址的专业术语为Label。

 从分析中,可以知道,该偏移地址就是uboot重定位运行是否会出错的原因,在上面可以知道,uboot重定位的偏移量为0x8ff3b000,将上面给出的反汇编代码进行重定位后,也就是加上地址偏移量0x873b000后,如下:

/* rel_test寻址分析(重定位后) */
8ff3f24c‬ <rel_test>:
8ff3f24c:    e59f300c     ldr    r3, [pc, #12]
8ff3f250:    e3a02064     mov    r2, #100    ; 0x64
8ff3f254:    e59f0008     ldr    r0, [pc, #8]    
8ff3f258:    e5832000     str    r2, [r3]
8ff3f25c:    ea00f1a9     b    87840908 <printf>
8ff3f260:     87868994             ; <UNDEFINED> instruction: 0x87868994
8ff3f264:    878494ce     strhi    r9, [r4, lr, asr #9]
...
...
8ff3f3e4:    e59f2038     ldr    r2, [pc, #56]    
8ff3f3e8:    e5923068     ldr    r3, [r2, #104]    ; 0x68
8ff3f3ec:    e3833030     orr    r3, r3, #48    ; 0x30
8ff3f3f0:    e5823068     str    r3, [r2, #104]    ; 0x68
8ff3f3f4:    ebffff94     bl    8780424c <rel_test>
...
...
8ffa3994 <rel_a>:
8ffa3994:    00000000     andeq    r0, r0, r0

 

函数rel_test()假设调用后对rel_a变量进行访问,分析和上面一样,先通过pc和偏移量找到0x8ff3f260,该地址是重定位后的地址,可此时该地址的值为0x87868994,也就是没有重定位后的rel_a变量的地址,但是从上面可以知道,uboot重定位后,rel_a变量的新地址为0x8ffa3994,因此,我们可以知道,如果uboot重定位后,要想正常访问rel_a变量,必须要将0x8ff3f260地址中的值0x87868994加上偏移量0x873b000,才能保证uboot重定位后,能正常访问到rel_a变量,将.rel.dyn段进行重定位修复,就是为了解决链接地址和运行地址不一致的问题。

在uboot中,对于重定位后链接地址与运行地址不一致的解决办法就是使用位置无关码,在uboot编译使用ld链接的时候使用参数"-pie"可生成与位置无关的可执行程序,使用该参数后,会生成一个.rel.dyn段,uboot则是靠该段去修复重定位后产生的问题的,在uboot的反汇编文件中,有.rel.dyn段代码,如下:

Disassembly of section .rel.dyn:

87868988 <__rel_dyn_end-0x91a0>:

87868988:    87800020     strhi    r0, [r0, r0, lsr #32]

8786898c:    00000017     andeq    r0, r0, r7, lsl r0

87868990:    87800024     strhi    r0, [r0, r4, lsr #32]

87868994:    00000017     andeq    r0, r0, r7, lsl r0

...

...

87868f08:    87804260     strhi    r4, [r0, r0, ror #4]

87868f0c:    00000017     andeq    r0, r0, r7, lsl r0

87868f10:    87804264     strhi    r4, [r0, r4, ror #4]

87868f14:    00000017     andeq    r0, r0, r7, lsl r0

...

...

rel.dyn段的格式如下,类似下面的两行就是一组:

 87868f08:    87804260     strhi    r4, [r0, r0, ror #4]

87868f0c:    00000017     andeq    r0, r0, r7, lsl r0

 

也就是两个4字节数据为一组,其中高4字节是Label地址的标识0x17,低4字节就是Label的地址,首先会判断Label地址标识是否正确,也就是判断高4字节是否为0x17,如果是的话,低4字节就是Label地址,在上面给出的两行代码中就是存放rel_a变量地址的Label,0x87868f0c的值为0x17,说明低4字节是Label地址,也就是0x87804260,需要将0x87804260 + offset处的值改为重定位后的变量rel_a地址。

在对.rel.dyn段以及Label的相关概念有一定的了解后,接下来,我们分析函数relocate_code()的第二部分代码,修复.rel.dyn段重定位问题,代码如下:

/** fix .rel.dyn relocations //修复.rel.dyn段*/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

relocate_code()函数第二部分代码调用后,将__rel_dyn_start的值赋给r2,也就是r2中保存着.rel.dyn段的起始地址,然后将__rel_dyn_end的值赋给r3,也就是r3中保存着.rel.dyn段的结束地址。

接下来,进入到fixloop处执行,使用ldmia指令,从.rel.dyn段起始地址开始,每次读取两个4字节数据存放到r0和r1寄存器中,其中r0存放低4字节的数据,也就是Label的地址,r1存放高4字节的数据,也就是Label的标识,然后将r1的值与0xff相与,取r1值的低8位,并将结果保存到r1中,接下来,判断r1中的值是否等于23(0x17),如果r1不等于23的话,也就说明不是描述Label,跳到fixnext处循环执行,直到r2和r3相等,也就是遍历完.rel.dyn段。

如果r1的值等于23(0x17)的话,继续执行,r0保存着Label地址,r4保存着uboot重定位的偏移,因此,r0 + r4就得到了重定位后的Label地址,也就是上面分析的0x87804260 + 0x873b000 = 0x8ff3f260 = r0,此时r0已经保存着重定位后的Label地址,然后使用ldr指令,读取r0中保存的值到r1中,也就是Label地址所保存的rel_a变量的地址,但是此时,该rel_a变量的地址仍然是重定位之前的地址0x87868994,所以,此时r1 = 0x87868994,接下来,使用add指令,将r1中的值加上r4,也就是加上uboot重定位偏移量,所以,此时r1 = 0x87868994 + 0x873b000 = 0x8ffa3994,这不就是前面分析的uboot重定位后的rel_a变量的地址吗?接下来使用str指令,将r1中的值写入到r0保存的地址中,也就是将Label地址中的值设置为0x8ffa3994,就是重定位后rel_a变量的新的地址。

比较r2和r3的值,查看.rel.dyn段重定位修复是否完成,循环直到完成,才能执行完relocate_code()函数。

第二部分执行完成后,就解决了.rel.dyn段的重定位问题,从而解决了uboot重定位后,链接地址和运行地址不一致的问题。

 

二,u-boot启动第二阶段

relocate完成之后,真正的C运行环境才算建立了起来,接下来会执行“后置的板级初始化操作”,即board_init_r函数。board_init_r和board_init_f的设计思路基本一样,也有一个很长的初始化序列----init_sequence_r,该序列中包含如下的初始化函数:

common/board_r.c

1)initr_trace,初始化并使能u-boot的tracing system,涉及的配置项有CONFIG_TRACE。
2)initr_reloc,设置relocation完成的标志。
3)initr_caches,使能dcache、icache等,涉及的配置项有CONFIG_ARM。
4)initr_malloc,malloc有关的初始化。
5)initr_dm,relocate之后,重新初始化DM,涉及的配置项有CONFIG_DM。
6)board_init,具体的板级初始化,需要由board代码根据需要实现,涉及的配置项有CONFIG_ARM。
7)set_cpu_clk_info,Initialize clock framework,涉及的配置项有CONFIG_CLOCKS。
8)initr_serial,重新初始化串口(不太明白什么意思)。
9)initr_announce,宣布已经在RAM中执行,会打印relocate后的地址。
10)board_early_init_r,由板级代码实现,涉及的配置项有CONFIG_BOARD_EARLY_INIT_R。
11)arch_early_init_r,由arch代码实现,涉及的配置项有CONFIG_ARCH_EARLY_INIT_R。
12)power_init_board,板级的power init代码,由板级代码实现,例如hold住power。
13)initr_flash、initr_nand、initr_onenand、initr_mmc、initr_dataflash,各种flash设备的初始化。
14)initr_env,环境变量有关的初始化。
15)initr_secondary_cpu,初始化其它的CPU core。
16)stdio_add_devices,各种输入输出设备的初始化,如LCD driver等。
17)interrupt_init,中断有关的初始化。
18)initr_enable_interrupts,使能系统的中断,涉及的配置项有CONFIG_ARM(ARM平台u-boot实在开中断的情况下运行的)。
19)initr_status_led,状态指示LED的初始化,涉及的配置项有CONFIG_STATUS_LED、STATUS_LED_BOOT。
20)initr_ethaddr,Ethernet的初始化,涉及的配置项有CONFIG_CMD_NET。
21)board_late_init,由板级代码实现,涉及的配置项有CONFIG_BOARD_LATE_INIT。
22)等等…
23)run_main_loop/main_loop,执行到main_loop,开始命令行操作。

三,u-boot加载内核阶段

... ... 

参考链接:

27. Uboot启动流程分析——下 — [野火]嵌入式Linux镜像构建与部署——基于LubanCat-i.MX6ULL开发板 文档

u-boot启动流程分析(2)_板级(board)部分

https://www.cnblogs.com/god-of-death/p/16971688.html

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

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

相关文章

NodeClub:NodeJS构造开源交流社区

NodeClub&#xff1a; 连接每一个想法&#xff0c;NodeClub让社区更生动- 精选真开源&#xff0c;释放新价值。 概览 NodeClub是一个基于Node.js和MongoDB构建的社区系统&#xff0c;专为开发者和社区爱好者设计。它提供了一套完整的社区功能&#xff0c;包括用户管理、内容发…

Mongodb在UPDATE操作中使用$push向数组中插入数据

学习mongodb&#xff0c;体会mongodb的每一个使用细节&#xff0c;欢迎阅读威赞的文章。这是威赞发布的第69篇mongodb技术文章&#xff0c;欢迎浏览本专栏威赞发布的其他文章。如果您认为我的文章对您有帮助或者解决您的问题&#xff0c;欢迎在文章下面点个赞&#xff0c;或者关…

qt dll编写和调用

dll编写 新建项目 头文件 #ifndef LIB1_H #define LIB1_H#include "lib1_global.h"class LIB1_EXPORT Lib1 { public:Lib1(); };//要导出的函数&#xff0c;使用extern "C"&#xff0c;否则名称改变将找不到函数extern "C" LIB1_EXPORT int ad…

探索未来边界:前沿技术引领新纪元

目录 引言 一、人工智能与深度学习&#xff1a;智慧生活的引擎 1.医疗应用 2.智能家居 3.自动驾驶 二、量子计算&#xff1a;解锁宇宙的密钥 1.量子比特示意图 2.量子计算机实物图 3.分子模拟应用 三、生物技术&#xff1a;生命科学的革新 1.CRISPR-Cas9基因编辑图 2.合成生…

buuctf----warmup_csaw_2016

进来医院先来一套常规检查 啥保护都没,看大佬说基本栈溢出 CT一看 OK cat flag 更喜欢了 40060D 找到地址 get也来了,稳啦! 0x80-0x40 8 根据上道题的exp from pwn import * ghust remote("node5.buuoj.cn",27229) addr 0x40060D payload bA * 0x40 bB*8…

C++设计模式——Bridge桥接模式

一&#xff0c;桥接模式简介 桥接模式是一种结构型设计模式&#xff0c;用于将抽象与实现分离&#xff0c;这里的"抽象"和"实现"都有可能是接口函数或者类。 桥接模式让抽象与实现之间解耦合&#xff0c;使得开发者可以更关注于实现部分&#xff0c;调用…

具有不确定性感知注意机制的肺结节分割和不确定区域预测| 文献速递-深度学习结合医疗影像疾病诊断与病灶分割

Title 题目 Lung Nodule Segmentation and UncertainRegion Prediction With an Uncertainty-Aware Attention Mechanism 具有不确定性感知注意机制的肺结节分割和不确定区域预测 01 文献速递介绍 肺结节分割在肺癌计算机辅助诊断&#xff08;CAD&#xff09;系统中至关重…

java Springboot网上音乐商城(源码+sql+论文)

1.1 研究目的和意义 随着市场经济发展&#xff0c;尤其是我国加入WTO &#xff0c;融入经济全球化潮流&#xff0c;已进入国内外市场经济发展新时期&#xff0c;音乐与市场联系越来越紧密&#xff0c;我国音乐和网上业务也进入新历史发展阶段。为了更好地服务于市场&#xff0…

不想搭集群,直接用spark

为了完成布置的作业&#xff0c;需要用到spark的本地模式&#xff0c;根本用不到集群&#xff0c;就不想搭建虚拟机&#xff0c;hadoop集群啥的&#xff0c;很繁琐&#xff0c;最后写作业还用不到集群&#xff08;感觉搭建集群对于我完成作业来说没有什么意义&#xff09;&…

Cisco Packet Tracer实验(二)

二、用交换机构建 LAN 构建物件如下&#xff1a; 四个PC 两个交换机 一个Multi Switch多功能拓展控制器 连线必须是这个直线&#xff01;&#xff01;&#xff01;不是虚线 最后实现效果如下&#xff1a; 全部的线是绿的&#xff0c;就表示是通的。 尝试一下&#xff0c;看PC…

SolidWorks对设计电脑硬件配置要求是怎么样的

SolidWorks&#xff0c;作为达索系统&#xff08;Dassault Systemes&#xff09;旗下的子公司&#xff0c;一直以其出色的机械设计软件解决方案而著称。它是基于Parasolid内核开发&#xff0c;是单核三维设计软件&#xff0c;面上使用比较多的版本有SolidWorks2022、SolidWorks…

Golang | Leetcode Golang题解之第149题直线上最多的点数

题目&#xff1a; 题解&#xff1a; func maxPoints(points [][]int) (ans int) {n : len(points)if n < 2 {return n}for i, p : range points {if ans > n-i || ans > n/2 {break}cnt : map[int]int{}for _, q : range points[i1:] {x, y : p[0]-q[0], p[1]-q[1]if…

4. 案例研究-接口程序

4. 案例研究-接口程序 本章通过一个案例研究, 来展示设计互相配合的函数的过程.4.1 turtle 模块 创建一个文件mypolygon.py, 并输入如下代码:import turtle bob turtle.Turtle() print(bob)# 这一句的作用是让画板停留, 等手动点击x关闭画板, 程序才结束. # 否则程序执行完毕…

8.12 面要素符号化综述

文章目录 前言面要素介绍总结 前言 本章介绍如何使用矢量面要素符号化说明&#xff1a;文章中的示例代码均来自开源项目qgis_cpp_api_apps 面要素介绍 地理空间的要素分为点、线和面&#xff0c;对应的符号也分三类&#xff1a;Marker Symbol、Line Symbol和Fill Symbol&…

c#中上传超过30mb的文件,接口一直报404,小于30mb的却可以上传成功

在一次前端实现上传视频文件时,超过30mb的文件上传,访问接口一直报404,但是在Swagger中直接访问接口确是正常的,且在后端控制器中添加了限制特性,如下 但是却仍然报404,在apifox中请求接口也是报404, 网上说: 在ASP.NET Core中,配置请求过来的文件上传的大小限制通常…

生命在于学习——Python人工智能原理(3.4)

三、深度学习 7、过拟合与欠拟合 过拟合和欠拟合是所有机器学习算法都要考虑的问题。 &#xff08;1&#xff09;基本定义 a、欠拟合 欠拟合是指机器学习模型无法完全捕获数据集中的复杂模式&#xff0c;导致模型在新数据上的表现不佳&#xff0c;这通常是由于模型过于简单…

C++进阶,一文带你彻底搞懂左右值引用以及移动语义和完美转发!

目录 一、左值引用1.左值2.左值引用3.左值引用的用途&#xff08;1&#xff09;修改实参&#xff08;2&#xff09;减少拷贝&#xff08;3&#xff09;使用左值引用可以在外部修改对象内的成员变量的值 二、右值引用1.右值&#xff08;1&#xff09;纯右值&#xff08;2&#x…

一文解答 | 代码签名证书怎么选

在当代软件开发中&#xff0c;代码签名证书对于确保软件的完整性、安全性及其可信度至关重要。它通过数字签名验证代码的来源和未被篡改的状态&#xff0c;向最终用户确保软件的可靠性。选择合适的代码签名证书既有利于保护软件开发商的声誉&#xff0c;也有助于建立用户对软件…

虚拟化 之三 详解 jailhouse(ARM 平台)的构建过程、配置及使用

嵌入式平台下,由于资源的限制,通常不具备通用性的 Linux 发行版,各大主流厂商都会提供自己的 Linux 发行版。这个发行版通常是基于某个 Linux 发行版构建系统来构建的,而不是全部手动构建,目前主流的 Linux 发行版构建系统是 Linux 基金会开发的 Yocto 构建系统。 基本环…

ChatGPT:自然语言处理的新纪元与OpenAI的深度融合

随着人工智能技术的蓬勃发展&#xff0c;自然语言处理&#xff08;NLP&#xff09;领域取得了显著的进步。OpenAI作为这一领域的领军者&#xff0c;以其卓越的技术实力和创新能力&#xff0c;不断推动着NLP领域向前发展。其中ChatGPT作为OpenAI的重要成果更是在全球范围内引起了…