• Tiny4412 u-boot分析(2)u-boot启动流程


    从大方面来说,u-boot的启动分成两个阶段,第一个阶段主要的职责是准备初始化的环境,主要有以下几点

    ①设置异常向量表

    ②把CPU的工作模式设置为SVC32模式

    ③关闭中断、MMU和cache

    ④关闭看门狗

    ⑤初始化内存、时钟、串口

    ⑥设置堆栈

    ⑦代码搬移

    ⑧清bss段

    ⑨跳转到c语言中执行(第二阶段)

    此时系统还没有进入C语言的运行阶段,并没有堆栈,也就不需要额外的RAM。

    第二阶段在上一段建立好C语言运行环境的基础上,进行各种外设的初始化,并循环执行用户命令。主要流程图如下

    当我们执行make命令来构建u-boot时,它的构建过程是:首先使用交叉编译工具将各目录下的源文件生成目标文件(*.o),目标文件生成后,会将若干个目标文件组合成静态库文件(*.a),最后通过链接各个静态库文件生成ELF格式的可执行文件。在链接的过程中,需要根据链接脚本(一般是各个以lds为后缀的文本文件),确定目标文件的各个段,链接文件通常是board/<board>/目录中的u-boot.lds文件。一般在链接脚本中通过

    ENTRY(_start)

    来指定入口为_start标号,通过文本段(.text)的第一个目标来制定u-boot入口文件。所以我们通过这个链接脚本文件可以确定u-boot执行的入口。

    Tiny4412 u-boot的链接脚本内容为

    //  board/samsung/tiny4412/u-boot.lds
    OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
    OUTPUT_ARCH(arm)
    ENTRY(_start)
    SECTIONS
    {
        . = 0x00000000;
        . = ALIGN(4);
        .text    :
        {
            arch/arm/cpu/armv7/start.o    (.text)
            board/samsung/tiny4412/libtiny4412.o (.text)
            arch/arm/cpu/armv7/exynos/libexynos.o    (.text)
            *(.text)
        }
        . = ALIGN(4);
        .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
        . = ALIGN(4);
        .data : {
            *(.data)
        }
        . = ALIGN(4);
        . = .;
        __u_boot_cmd_start = .;
        .u_boot_cmd : { *(.u_boot_cmd) }
        __u_boot_cmd_end = .;
        . = ALIGN(4);
        .rel.dyn : {
            __rel_dyn_start = .;
            *(.rel*)
            __rel_dyn_end = .;
        }
        .dynsym : {
            __dynsym_start = .;
            *(.dynsym)
        }
        .bss __rel_dyn_start (OVERLAY) : {
            __bss_start = .;
            *(.bss)
             . = ALIGN(4);
            _end = .;
        }
        /DISCARD/ : { *(.dynstr*) }
        /DISCARD/ : { *(.dynamic*) }
        /DISCARD/ : { *(.plt*) }
        /DISCARD/ : { *(.interp*) }
        /DISCARD/ : { *(.gnu*) }
    }

    在本链接脚本文件中,定义了起始地址为0x00000000,每个段使用4字节对齐(.ALIGN(4)),几个段分别为代码段(.text)、只读数据段(.rodata)、数据段(.data)其中,代码段的第一个目标为arch/arm/cpu/armv7/start.o,在其中定义了映像文件的入口_start。

    下面来具体分析一下这个start.S。

    在文件的一开始定义了映像的入口_start和中断向量表。

    .globl _start  //定义u-boot入口
    _start: b       reset   //设置中断向量表
            ldr     pc, _undefined_instruction
            ldr     pc, _software_interrupt
            ldr     pc, _prefetch_abort
            ldr     pc, _data_abort
            ldr     pc, _not_used
            ldr     pc, _irq
            ldr     pc, _fiq
    _undefined_instruction: .word undefined_instruction
    _software_interrupt:    .word software_interrupt
    _prefetch_abort:        .word prefetch_abort
    _data_abort:            .word data_abort
    _not_used:              .word not_used
    _irq:                   .word irq 
    _fiq:                   .word fiq 
    _pad:                   .word 0x12345678 /* now 16*4=64 */

    系统开机进入到u-boot运行时,首先进入到u-boot的入口_start标号处,然后通过 b  reset 跳转到reset标号处,我们就到reset标号一探究竟。

    /*
     * the actual reset code
     */
    reset:
        /*
             *设置CPU工作模式为SVC32模式
         * set the cpu to SVC32 mode
         */
        mrs    r0, cpsr
        bic    r0, r0, #0x1f
        orr    r0, r0, #0xd3
        msr    cpsr,r0
    //......
    //调用 cpu_init_crit 
    #ifndef CONFIG_SKIP_LOWLEVEL_INIT
        bl    cpu_init_crit
    #endif

    首先会将CPU的工作模式设置为svc32模式,然后便调用 cpu_init_crit ,需要注意的是,这里使用的是 bl 指令,也就是说在运行完 cpu_init_crit 标号处的代码之后,会通过

    mov    pc, lr            @ back to my caller

    指令回到reset中继续执行

    bl    cpu_init_crit

    下面的指令(所以这里我们应该使用 调用来描述更为贴切)。下面我们去看一下 cpu_init_crit 指令处做了哪些事

    /*************************************************************************
     *
     * CPU_init_critical registers
     *
     * setup important registers
     * setup memory timing
     *
     *************************************************************************/
    cpu_init_crit:
           
            //调用 cache_init
        bl cache_init
        
        /*
             *使  L1 I/D 无效
         * Invalidate L1 I/D
         */
        mov    r0, #0            @ set up for MCR
        mcr    p15, 0, r0, c8, c7, 0    @ invalidate TLBs
        mcr    p15, 0, r0, c7, c5, 0    @ invalidate icache
        /*
             * 关闭 MMU 和 cache
         * disable MMU stuff and caches
         */
        mrc    p15, 0, r0, c1, c0, 0
        bic    r0, r0, #0x00002000    @ clear bits 13 (--V-)
        bic    r0, r0, #0x00000007    @ clear bits 2:0 (-CAM)
        orr    r0, r0, #0x00000002    @ set bit 1 (--A-) Align
        orr    r0, r0, #0x00000800    @ set bit 12 (Z---) BTB
        mcr    p15, 0, r0, c1, c0, 0
        /*
         * Jump to board specific initialization...
         * The Mask ROM will have already initialized
         * basic memory. Go here to bump up clock rate and handle
         * wake up conditions.
         */
        mov    ip, lr            @ persevere link reg across call
            //调用 lowlevel_init
        bl    lowlevel_init        @ go setup pll,mux,memory
        mov    lr, ip            @ restore link
            //返回到 reset 标号继续执行
        mov    pc, lr            @ back to my caller
    /*

    首先分析 cache_init ,它被定义在  board/samsung/tiny4412/lowlevel_init.S 文件中

        .globl cache_init
    cache_init:
        mov pc, lr

    可以看出来,这是一个空函数(暂且将它叫做函数-_-!!)。

    接下来我们就要去分析 lowlevel_init 了,它也被定义在board/samsung/tiny4412/lowlevel_init.S 文件中

        .globl lowlevel_init
    lowlevel_init:
        
        //初始化串口
        bl uart_asm_init
        
      //Read booting information
      //读取启动信息
        bl read_om
        /* when we already run in ram, we don't need to relocate U-Boot.
         * and actually, memory controller must be configured before U-Boot
         * is running in ram.
         */
        ldr    r0, =0xff000fff
        bic    r1, pc, r0        /* r0 <- current base addr of code */
        ldr    r2, _TEXT_BASE    /* r1 <- original base addr in ram */
        bic    r2, r2, r0        /* r0 <- current base addr of code */
        cmp r1, r2            /* compare r0, r1 */
        beq after_copy        /* r0 == r1 then skip sdram init and u-boot.bin loading */
       //初始化内存
        /* Memory initialize */
        bl mem_ctrl_asm_init
      //初始化系统时钟
        /* init system clock */
        bl system_clock_init
        
      
      /*
          eg:
            1: ;A
            cmp r0, #0
            beq 1f ; r0==0那么向前跳转到B处执行
            bne 1b ; 否则向后跳转到A处执行
            :1: ;B
      */
      
      // 向前跳转到1: 标号处执行
        b  1f
    1:
      //初始化 trust zone
        bl tzpc_init
        b load_uboot
    after_copy:
    #ifdef CONFIG_ENABLE_MMU
        bl enable_mmu
    #endif
        /* store second boot information in u-boot C level variable */
        ldr r0, =CONFIG_PHY_UBOOT_BASE
        sub r0, r0, #8
        ldr r1, [r0]
        ldr r0, _second_boot_info
        str r1, [r0]
        /* Print 'K' */
        ldr    r0, =S5PV310_UART_CONSOLE_BASE
        ldr    r1, =0x4b4b4b4b
        str    r1, [r0, #UTXH_OFFSET]
      //第二阶段入口,调用C语言函数:board_init_f
        ldr r0, _board_init_f
        mov pc, r0
        
    _board_init_f:
        .word board_init_f
    load_uboot:
        ldr    r0, =INF_REG_BASE
        ldr    r1, [r0, #INF_REG3_OFFSET]
        cmp    r1, #BOOT_NAND
        beq    nand_boot
        cmp    r1, #BOOT_ONENAND
        beq    onenand_boot
        cmp     r1, #BOOT_MMCSD
        beq     mmcsd_boot
        cmp    r1, #BOOT_EMMC
        beq    emmc_boot
        cmp    r1, #BOOT_EMMC_4_4
        beq    emmc_boot_4_4
        cmp     r1, #BOOT_NOR
        beq     nor_boot
        cmp     r1, #BOOT_SEC_DEV
        beq     mmcsd_boot
    nand_boot:
        mov    r0, #0x1000
        bl    copy_uboot_to_ram
        b    after_copy
    onenand_boot:
        bl    onenand_bl2_copy /*goto 0x1010*/
        b    after_copy
    mmcsd_boot:
    #ifdef CONFIG_SMDKC220
    //#ifdef CONFIG_CLK_BUS_DMC_200_400
        ldr    r0, =ELFIN_CLOCK_BASE
        ldr    r2, =CLK_DIV_FSYS2_OFFSET
        ldr    r1, [r0, r2]
        orr r1, r1, #0xf
        str r1, [r0, r2]
    //#endif
    #else
    #if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
        ldr    r0, =ELFIN_CLOCK_BASE
        ldr    r2, =CLK_DIV_FSYS2_OFFSET
        ldr    r1, [r0, r2]
        orr r1, r1, #0xf
        str r1, [r0, r2]
    #endif
    #endif
        bl      movi_uboot_copy
        b       after_copy
    emmc_boot:
    #if defined(CONFIG_CLK_1000_400_200) || defined(CONFIG_CLK_1000_200_200) || defined(CONFIG_CLK_800_400_200)
        ldr    r0, =ELFIN_CLOCK_BASE
        ldr    r2, =CLK_DIV_FSYS1_OFFSET
        ldr    r1, [r0, r2]
        orr     r1, r1, #0xf
        str     r1, [r0, r2]
    #endif
        bl    emmc_uboot_copy
        b    after_copy
    emmc_boot_4_4:
        /* read TCBCNT to get Transferred CIU card byte count */
        ldr r0, =0x1255005c
        ldr r1, [r0]
        ldr r2, =0x6000
        cmp r1, r2
        /* store second boot information in DRAM */
        ldr r0, =CONFIG_PHY_UBOOT_BASE
        sub r0, r0, #8
        mov r3, #0
        movlo r3, #1
        str r3, [r0]
        /* if transferred CIU card byte count >= 0x6000 (24 KB)  */
        /* BL1 and BL2 are loaded from emmc 4.4                  */
        /* Otherwise BL1 and BL2 are loaded from sdmmc ch2.      */
        blo mmcsd_boot
        /* mmc ch4 devider value change */
        bl    mmc_ch4_devider_change
        /* u-boot image copy from boot partition to DRAM. */
        bl    emmc_4_4_uboot_copy
        /* Exit Boot mood */
        bl    emmc_4_4_endbootOp_eMMC
        b    after_copy

    在 lowlevel_init 中,主要做了一些初始化工作,比如系统时钟、内存、串口等的初始化工作,然后初始化堆栈、清bss段,并进行了代码搬移,为第二阶段C语言程序运行提供保障。最后通过

    ldr r0, _board_init_f
        mov pc, r0

    指令跳转到第二阶段C语言函数 board_init_f 函数处。接着我们就去分析一下这个函数。

    在分析board_init_f函数之前,先来了解以下gd_t数据结构

    // arch/arm/include/asm/global_data.h
    typedef    struct    global_data {
        bd_t        *bd;
        unsigned long    flags;
        unsigned long    baudrate;
        unsigned long    have_console;    /* serial_init() was called */
        unsigned long    env_addr;    /* Address  of Environment struct */
        unsigned long    env_valid;    /* Checksum of Environment valid? */
        unsigned long    fb_base;    /* base address of frame buffer */
    #ifdef CONFIG_VFD
        unsigned char    vfd_type;    /* display type */
    #endif
    #ifdef CONFIG_FSL_ESDHC
        unsigned long    sdhc_clk;
    #endif
    #ifdef CONFIG_AT91FAMILY
        /* "static data" needed by at91's clock.c */
        unsigned long    cpu_clk_rate_hz;
        unsigned long    main_clk_rate_hz;
        unsigned long    mck_rate_hz;
        unsigned long    plla_rate_hz;
        unsigned long    pllb_rate_hz;
        unsigned long    at91_pllb_usb_init;
    #endif
    #ifdef CONFIG_ARM
        /* "static data" needed by most of timer.c on ARM platforms */
        unsigned long    timer_rate_hz;
        unsigned long    tbl;
        unsigned long    tbu;
        unsigned long long    timer_reset_value;
        unsigned long    lastinc;
    #endif
        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;
    #if !(defined(CONFIG_SYS_NO_ICACHE) && defined(CONFIG_SYS_NO_DCACHE))
        unsigned long    tlb_addr;
    #endif
        void        **jt;        /* jump table */
        char        env_buf[32];    /* buffer for getenv() before reloc. */
    } gd_t;
    #define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

    u-boot中使用一个结构体gd_t来存储全局区的数据,使用一个存储在寄存器中的指针gd来记录全局数据区的地址。

    DECLARE_GLOBAL_DATA_PTR

    在board/samsung/tiny4412/tiny4412.c被声明。

    u-boot 中还有一个数据结构 bd_t用来存放板级相关的全局数据,是gd_t中结构体指针成员bd的结构体类型。

    //   arch/arm/include/asm/u-boot.h
    typedef struct bd_info {
        int            bi_baudrate;    /* serial console baudrate */
        unsigned long    bi_ip_addr;    /* IP Address */
        ulong            bi_arch_number;    /* unique id for this board */
        ulong            bi_boot_params;    /* where this board expects params */
        struct                /* RAM configuration */
        {
        ulong start;
        ulong size;
        }            bi_dram[CONFIG_NR_DRAM_BANKS];
    } bd_t;

    u-boot启动内核时要给内核传递参数,这时需要使用gd_t、bd_t结构体中的信息来设置标记列表。了解了这两个数据结构我们就去分析一下board_init_f函数

    //   arch/arm/lib/board.c
    void board_init_f(ulong bootflag)
    {
        bd_t *bd;
        init_fnc_t **init_fnc_ptr;
        gd_t *id;
        ulong addr, addr_sp;
        //计算全局数据结构的地址,保存在gd指针中
        /* Pointer is writable since we allocated a register for it */
        gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
        /* compiler optimization barrier needed for GCC >= 3.4 */
        __asm__ __volatile__("": : :"memory");
        
        memset((void*)gd, 0, sizeof (gd_t));
        gd->mon_len = _bss_end_ofs;
        逐个调用init_sequence数组的初始化函数
        for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
            if ((*init_fnc_ptr)() != 0) {
                hang();
            }
        }
        debug ("monitor len: %08lX
    ", gd->mon_len);
        /*
         * Ram is setup, size stored in gd !!
         */
        debug ("ramsize: %08lX
    ", gd->ram_size);
        //填充gd数据结构
        gd->bd->bi_baudrate = gd->baudrate;
        /* Ram ist board specific, so move it to board code ... */
        dram_init_banksize();
        display_dram_config();    /* and display it */
        gd->relocaddr = addr;
        gd->start_addr_sp = addr_sp;
        gd->reloc_off = addr - _TEXT_BASE;
        debug ("relocation Offset is: %08lx
    ", gd->reloc_off);
        memcpy(id, (void *)gd, sizeof (gd_t));
        //调用arch/arm/cpu/armv7/start.S  relocate_code
        relocate_code(addr_sp, id, addr);
        /* NOTREACHED - relocate_code() does not return */
    }

    u-boot使用一个init_sequence数组来存储大多数开发板都要执行的初始化函数的函数指针

    // arch/arm/lib/board.c
    typedef int (init_fnc_t)(void);
    init_fnc_t *init_sequence[] = {
    #if defined(CONFIG_ARCH_CPU_INIT)
        arch_cpu_init,        /* basic arch cpu dependent setup */
    #endif
    #if defined(CONFIG_BOARD_EARLY_INIT_F)
        board_early_init_f,
    #endif
        timer_init,        /* initialize timer */
    #ifdef CONFIG_FSL_ESDHC
        get_clocks,
    #endif
        env_init,        /* initialize environment */
    #if defined(CONFIG_S5P6450) && !defined(CONFIG_S5P6460_IP_TEST)
        init_baudrate,        /* initialze baudrate settings */
        serial_init,        /* serial communications setup */
    #endif
        console_init_f,        /* stage 1 init of console */
        display_banner,        /* say that we are here */
    #if defined(CONFIG_DISPLAY_CPUINFO)
        print_cpuinfo,        /* display cpu info (and speed) */
    #endif
    #if defined(CONFIG_DISPLAY_BOARDINFO)
        checkboard,        /* display board info */
    #endif
    #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
        init_func_i2c,
    #endif
        dram_init,        /* configure available RAM banks */
    #if defined(CONFIG_CMD_PCI) || defined(CONFIG_PCI)
        arm_pci_init,
    #endif
        NULL,
    };

    board_init_f函数在调用完初始化函数指针、填充完gd结构之后,调用了arch/arm/cpu/armv7/start.S中的relocate_code去看一下relocate_code做了什么

        .globl    relocate_code
    relocate_code:
        mov    r4, r0    /* save addr_sp */
        mov    r5, r1    /* save addr of gd */
        mov    r6, r2    /* save addr of destination */
        /* Set up the stack                            */
    stack_setup:
        mov    sp, r4
        adr    r0, _start
    #if defined(CONFIG_S5PC110) && defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)
        sub    r0, r0, #16
    #endif
    #ifndef CONFIG_PRELOADER
        cmp    r0, r6
        beq    clear_bss        /* skip relocation */
    #endif
        mov    r1, r6            /* r1 <- scratch for copy_loop */
        ldr    r2, _TEXT_BASE
        ldr    r3, _bss_start_ofs
        add    r2, r0, r3        /* r2 <- source end address        */
    copy_loop:
        ldmia    r0!, {r9-r10}        /* copy from source address [r0]    */
        stmia    r1!, {r9-r10}        /* copy to   target address [r1]    */
        cmp    r0, r2            /* until source end address [r2]    */
        blo    copy_loop
    #ifndef CONFIG_PRELOADER
        /*
         * fix .rel.dyn relocations
         */
        ldr    r0, _TEXT_BASE        /* r0 <- Text base */
        sub    r9, r6, r0        /* r9 <- relocation offset */
        ldr    r10, _dynsym_start_ofs    /* r10 <- sym table ofs */
        add    r10, r10, r0        /* r10 <- sym table in FLASH */
        ldr    r2, _rel_dyn_start_ofs    /* r2 <- rel dyn start ofs */
        add    r2, r2, r0        /* r2 <- rel dyn start in FLASH */
        ldr    r3, _rel_dyn_end_ofs    /* r3 <- rel dyn end ofs */
        add    r3, r3, r0        /* r3 <- rel dyn end in FLASH */
    fixloop:
        ldr    r0, [r2]        /* r0 <- location to fix up, IN FLASH! */
        add    r0, r0, r9        /* r0 <- location to fix up in RAM */
        ldr    r1, [r2, #4]
        and    r7, r1, #0xff
        cmp    r7, #23            /* relative fixup? */
        beq    fixrel
        cmp    r7, #2            /* absolute fixup? */
        beq    fixabs
        /* ignore unknown type of fixup */
        b    fixnext
    fixabs:
        /* absolute fix: set location to (offset) symbol value */
        mov    r1, r1, LSR #4        /* r1 <- symbol index in .dynsym */
        add    r1, r10, r1        /* r1 <- address of symbol in table */
        ldr    r1, [r1, #4]        /* r1 <- symbol value */
        add    r1, r1, r9        /* r1 <- relocated sym addr */
        b    fixnext
    fixrel:
        /* relative fix: increase location by offset */
        ldr    r1, [r0]
        add    r1, r1, r9
    fixnext:
        str    r1, [r0]
        add    r2, r2, #8        /* each rel.dyn entry is 8 bytes */
        cmp    r2, r3
        blo    fixloop
    clear_bss:
        ldr    r0, _bss_start_ofs
        ldr    r1, _bss_end_ofs
        ldr    r3, _TEXT_BASE        /* Text base */
        mov    r4, r6            /* reloc addr */
        add    r0, r0, r4
        add    r1, r1, r4
        mov    r2, #0x00000000        /* clear                */
    clbss_l:str    r2, [r0]        /* clear loop...            */
        add    r0, r0, #4
        cmp    r0, r1
        bne    clbss_l
    #endif    /* #ifndef CONFIG_PRELOADER */
    /*
     * We are done. Do not return, instead branch to second part of board
     * initialization, now running from RAM.
     */
     
    //调用board_init_r
    jump_2_ram:
        ldr    r0, _board_init_r_ofs
        adr    r1, _start
        add    lr, r0, r1
    @    add    lr, lr, r9
        /* setup parameters for board_init_r */
        mov    r0, r5        /* gd_t */
        mov    r1, r6        /* dest_addr */
        /* jump to it ... */
        mov    pc, lr
    _board_init_r_ofs:
        .word board_init_r - _start

    可见,最后调用了board_init_r函数

    //   arch/arm/lib/board.c
    void board_init_r(gd_t *id, ulong dest_addr)
    {
        char *s;
        bd_t *bd;
        ulong malloc_start;
        gd = id;
        bd = gd->bd;
        gd->flags |= GD_FLG_RELOC;    /* tell others: relocation done */
        monitor_flash_len = _bss_start_ofs;
        debug ("monitor flash len: %08lX
    ", monitor_flash_len);
        board_init();    /* Setup chipselects */
        debug ("Now running in RAM - U-Boot at: %08lx
    ", dest_addr);
        /* The Malloc area is immediately below the monitor copy in DRAM */
        malloc_start = dest_addr - TOTAL_MALLOC_LEN;
        mem_malloc_init(malloc_start, TOTAL_MALLOC_LEN);
        //初始化MMC
    #ifdef CONFIG_GENERIC_MMC
        mmc_initialize(bd);
    #endif
         //初始化环境变量
        /* initialize environment */
        env_relocate();
         //将环境变量中的IP填充到gd结构体
        /* IP Address */
        gd->bd->bi_ip_addr = getenv_IPaddr("ipaddr");
        stdio_init();    /* get the devices list going. */
        jumptable_init();
          //初始化并使能中断
         /* set up exceptions */
        interrupt_init();
        /* enable exceptions */
        enable_interrupts();
        /* Initialize from environment */
        if ((s = getenv("loadaddr")) != NULL) {
            load_addr = simple_strtoul(s, NULL, 16);
        }
         //进入到main_loop
        /* main_loop() can return to retry autoboot, if so just run it again. */
        for (;;) {
            main_loop();
        }
        /* NOTREACHED - no way out of command loop except booting */
    }

    最后调用了main_loop

    待完成:

    ①main_loop分析

    ②启动内核过程

    。。。。。。

  • 相关阅读:
    php 按照中文字母名字排序,并把相应的头像显示出来
    小程序单图上传到服务器
    小程序上传图片多图上传
    php 截取 小程序上传到服务器图片,
    小程序选中传值过去 不选中默认第一个
    小程序数据放入全局变量可以使用
    小程序用户openid设置放缓存
    小程序计算两者商家与用户之间的距离
    微擎小程序上传图片
    小程序获取用户的地理位置与商家的相距距离
  • 原文地址:https://www.cnblogs.com/CoderTian/p/5995409.html
Copyright © 2020-2023  润新知