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)
#else
    ldr    r0, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
    bic    r0, r0, #7    /* 8-byte alignment for ABI compliance */
    mov    sp, r0
    bl    board_init_f_alloc_reserve
    mov    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
#endif

    mov    r0, #0
    bl    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, r0
    ldr    r9, [r9, #GD_NEW_GD]        /* r9 <- gd->new_gd */

    adr    lr, here
    ldr    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 */
#endif
    ldr    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_gd
    cmp    r0, #0
    movne    sp, r0
    movne    r9, r0
# endif

#if ! defined(CONFIG_SPL_BUILD)
    bl coloured_LED_init
    bl 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
#else
    ldr    pc, =board_init_r    /* this is auto-relocated! */
#endif
    /* we should not return here. */
#endif

ENDPROC(_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)
#else
   ldr       r0, =(CONFIG_SYS_INIT_SP_ADDR) //加载CONFIG_SYS_INIT_SP_ADDR到r0寄存器
#endif
   bic       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_t
   bl        board_init_f_init_reserve //该函数主要作用是将GD区域清零,返回最初malloc区域的地址

   mov       r0, #0 //清空r0,然后把参数r0传给board_init_f函数,并调用board_init_f
   bl        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_TYPES
    unsigned long board_type;
#endif
    unsigned long have_console;    /* serial_init() was called */
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
    unsigned long precon_buf_idx;    /* Pre-Console buffer index */
#endif
    unsigned 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_DM
    struct 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_TIMER
    struct udevice    *timer;        /* Timer instance for Driver Model */
#endif

    const 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_TRACE
    void        *trace_buff;    /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)
    int        cur_i2c_bus;    /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXC
    void *srdata[10];
#endif
    unsigned long timebase_h;
    unsigned long timebase_l;
#ifdef CONFIG_SYS_MALLOC_F_LEN
    unsigned long malloc_base;    /* base address of early malloc() */
    unsigned long malloc_limit;    /* limit address */
    unsigned long malloc_ptr;    /* current address */
#endif
#ifdef CONFIG_PCI
    struct pci_controller *hose;    /* PCI hose for early use */
    phys_addr_t pci_ram_top;    /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAY
    int pcidelay_done;
#endif
    struct udevice *cur_serial_dev;    /* current serial device */
    struct arch_global_data arch;    /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORD
    struct membuff console_out;    /* console output */
    struct membuff console_in;    /* console input */
#endif
#ifdef CONFIG_DM_VIDEO
    ulong 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_AVR32
    unsigned char   bi_phy_id[4];   /* PHY address for ATAG_ETHERNET */
    unsigned long   bi_board_number;/* ATAG_BOARDINFO */
#endif
#ifdef CONFIG_ARM
    unsigned 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;
#endif
    unsigned 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_ETH1
    unsigned char   bi_enet1addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2
    unsigned char    bi_enet2addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3
    unsigned char   bi_enet3addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4
    unsigned char   bi_enet4addr[6];    /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5
    unsigned 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 */
#else
    int        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_BANKS
    struct {            /* 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_addr
    gd->env_valid    = 1;

    return 0;
}

include/env_default.h

static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef    CONFIG_ENV_CALLBACK_LIST_DEFAULT
    ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
#endif
#ifdef    CONFIG_ENV_FLAGS_LIST_DEFAULT
    ENV_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_SETTINGS
    CONFIG_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; //将老的设备树地址更新为新的设备树地址
   }
#endif

   return 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 = 0x873b000
    subs    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和r11
    stmia    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, #0xff
    cmp    r1, #23            /* relative fixup? */
    bne    fixnext

    /* relative fix: increase location by offset */
    add    r0, r0, r4
    ldr    r1, [r0]
    add    r1, r1, r4
    str    r1, [r0]
fixnext:
    cmp    r2, r3
    blo    fixloop

relocate_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, #0xff
    cmp    r1, #23            /* relative fixup? */
    bne    fixnext

    /* relative fix: increase location by offset */
    add    r0, r0, r4
    ldr    r1, [r0]
    add    r1, r1, r4
    str    r1, [r0]
fixnext:
    cmp    r2, r3
    blo    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.mfbz.cn/a/707331.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PHP开发工程师系统性教学】——thinkPHP的分页操作,不管有几页,用这个你想分几页就分几页

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

力扣384. 打乱数组

Problem: 384. 打乱数组 文章目录 题目描述思路复杂度Code 题目描述 思路 打乱数组的主要算法&#xff1a; 从1 - n每次生成[i ~ n - i]的一个随机数字&#xff0c;再将原数组下标位置为i的元素和该随机数字位置的元素交换 复杂度 打乱数组的主要算法 时间复杂度: O ( n ) O(…

YOLOv8可视化界面,web网页端检测

YOLOv8可视化界面&#xff0c;web网页端检测。支持图片检测&#xff0c;视频检测&#xff0c;摄像头检测等&#xff0c;支持检测、分割等多种任务&#xff0c;实时显示检测画面。支持自定义数据集&#xff0c;计数&#xff0c;……

编译原理:语法分析

目录 引言上下文无关文法 CFG: Context-Free Grammar定义推导方法最左推导和最右推导 分析树分析树->抽象语法树常见的上下文无关文法文法设计二义性文法扩展巴科斯范式&#xff1a;EBNF extended Backus Normal Form 文法和语言分类相关术语直接推导推导*推导句型、句子、语…

酷开科技丨酷开系统智慧中心,解锁AI智能家居生活的无限可能

想象一下&#xff0c;未来的AI电视不再是冷冰冰的机器&#xff0c;而是家庭的智能伙伴。它学习你的喜好&#xff0c;预测你的需求&#xff0c;用声音和触感与你交流。它控制家中的灯光、温度&#xff0c;甚至帮你订购生活用品。 在探索智能家居的未来发展时&#xff0c;酷开系…

计算机图形学入门11:图形管线与着色器

1.什么是图形管线 把场景中的物体经过一系列的处理&#xff0c;最后一张图像的形式在屏幕上显示出来&#xff0c;这一系列过程就是图形管线(Graphics Pipeline)&#xff0c;也叫实时渲染管线(Real-time Rendering Pipeline)。如下图所示&#xff0c;为整个渲染管线的过程。 渲染…

WPF学习(1)--类与类的继承

在面向对象编程中&#xff0c;继承是一种机制&#xff0c;允许一个类&#xff08;称为子类或派生类&#xff09;从另一个类&#xff08;称为父类或基类&#xff09;继承属性和方法。继承使我们能够创建一个通用类&#xff0c;然后根据需要扩展或修改它以创建更具体的类。以下是…

基于改进字典学习的旋转机械故障诊断方法(MATLAB)

在过去的二十年里&#xff0c;稀疏表示在各个领域引起了广泛的关注。它的核心思想是将信号描述为尽量少的字典原子&#xff0c;在计算机视觉、生物学、特征提取和机械故障诊断方面显示出强大而可靠的能力。SR通常分为两个步骤&#xff1a;构建字典和学习稀疏系数。对于稀疏系数…

借力AI,助力网络钓鱼(邮件)检测

引言 互联网时代&#xff0c;邮件系统依然是人们工作、生活中的很重要的一部分&#xff0c;与此同时&#xff0c;邮件系统的发展带来的钓鱼邮件问题也成为网络中的最大的安全隐患之一。本文将为大家解开网络钓鱼&#xff08;邮件&#xff09;的神秘面纱&#xff0c;一探究竟&a…

证照之星是一款很受欢迎的证件照制作软件

证照之星是一款很受欢迎的证件照制作软件&#xff0c;证照之星可以为用户提供“照片旋转、裁切、调色、背景处理”等功能&#xff0c;满足用户对证件照制作的基本需求。本站证照之星下载专题为大家提供了证照之星电脑版、安卓版、个人免费版等多个版本客户端资源&#xff0c;此…

软件性能测试基本概述

大家好&#xff0c;在性能测试的世界里&#xff0c;确保软件系统的高效运行至关重要。性能测试不仅仅是为了评估软件系统的性能&#xff0c;更是为了保障用户体验、提高系统稳定性和可靠性。本文将带您一览性能测试的基本概述&#xff0c;从性能测试的定义、重要性&#xff0c;…

OpenGL3.3_C++_Windows(5)

变换 && 3D空间的2D图形 /\/\/\/\/\//\/\/\/\/\/\/\/\//\/\///\/\/\/\//\/\/\/\//\//\/\/\/\/\\/GLM库从0.9.9版本起&#xff0c;默认会将矩阵类型初始化为一个零矩阵&#xff08;所有元素均为0&#xff09;&#xff0c;而不是单位矩阵&#xff08;对角元素为1&#…

学习Java中的Future类

学习Java中的Future类 Future接口在Java 5中引入&#xff0c;作为java.util.concurrent包的一部分。它代表一个异步计算的结果&#xff0c;当计算完成时可以通过Future对象获取结果。Future对象提供了一些方法来检查任务是否完成、等待任务完成并获取任务结果、取消任务等。 …

django学习入门系列之第二点《浏览器能识别的标签3》

文章目录 列表表格往期回顾 列表 无序列表 <!-- <ul </ul> 无序列表 --> <ul><li> 内容1 </li><li> 内容2 </li><li> 内容3 </li><li> 内容4 </li> </ul>有序列表 <!-- <ol> &…

html实现粘贴excel数据,在页面表格中复制

录入数据时&#xff0c;有时候需要把excel中的数据一条条粘贴到页面中&#xff0c;当数据量过多时&#xff0c;这种操作很令人崩溃。本篇文章实现了从excel复制好多行数据后,可在页面粘贴的功能 具体实现代码 <!DOCTYPE html> <html lang"en"> <head…

Spring Boot登录开发 - 邮箱登录/注册接口实现

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

微信小程序请求request封装

公共基础路径封装 // config.js module.exports {// 测试BASE_URL: https://cloud.chejj.cn,// 正式// BASE_URL: https://cloud.mycjj.com };请求封装 // request.js import config from ../config/baseUrl// 请求未返回时的loading const showLoading () > wx.showLoadi…

碎片化知识如何被系统性地吸收?

一、方法论 碎片化知识指的是通过各种渠道快速获取的零散信息和知识点&#xff0c;这些信息由于其不完整性和孤立性&#xff0c;不易于记忆和应用。为了系统性地吸收碎片化知识&#xff0c;可以采用以下策略&#xff1a; 1. **构建知识框架**&#xff1a; - 在开始吸收之前&am…

Java | Leetcode Java题解之第150题逆波兰表达式求值

题目&#xff1a; 题解&#xff1a; class Solution {public int evalRPN(String[] tokens) {int n tokens.length;int[] stack new int[(n 1) / 2];int index -1;for (int i 0; i < n; i) {String token tokens[i];switch (token) {case "":index--;stack…
最新文章