更好的阅读体验请见:硬核26000字分析uboot启动过程
汇编阶段
最先执行的是汇编文件start.S,这个文件跟架构有关,例如芯片架构是arm926ejs,那路径就在arch/arm/cpu/start.S
。
.globl reset reset: /* * set the cpu to SVC32 mode */ mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0 /* * we do sys-critical inits only at reboot, * not when booting from ram! */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif ** bl _main**
bl命令是先跳转然后返回到跳转前的地方。
没有定义**CONFIG_SKIP_LOWLEVEL_INIT
**宏,所以执行cpu_init_crit
,再跳转到_main。
#ifndef CONFIG_SKIP_LOWLEVEL_INIT cpu_init_crit: /* * flush D cache before disabling it */ mov r0, #0 flush_dcache: mrc p15, 0, r15, c7, c10, 3 bne flush_dcache mcr p15, 0, r0, c8, c7, 0 /* invalidate TLB */ mcr p15, 0, r0, c7, c5, 0 /* invalidate I Cache */ /* * disable MMU and D cache * enable I cache if CONFIG_SYS_ICACHE_OFF is not defined */ mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00000300 /* clear bits 9:8 (---- --RS) */ bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */ #ifdef CONFIG_SYS_EXCEPTION_VECTORS_HIGH orr r0, r0, #0x00002000 /* set bit 13 (--V- ----) */ #else bic r0, r0, #0x00002000 /* clear bit 13 (--V- ----) */ #endif orr r0, r0, #0x00000002 /* set bit 1 (A) Align */ #ifndef CONFIG_SYS_ICACHE_OFF orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */ #endif mcr p15, 0, r0, c1, c0, 0 /* * Go setup Memory and board specific bits prior to relocation. */ mov ip, lr /* perserve link reg across call */ bl lowlevel_init /* go setup pll,mux,memory */ mov lr, ip /* restore link */ mov pc, lr /* back to my caller */ #endif /* CONFIG_SKIP_LOWLEVEL_INIT */
cpu_init_crit
主要是对内存的一些初始化,初学者可以不用关注,即便是工作了也很少会涉及到这里,因此看不懂也没关系,直接跳过,只需要了解这个过程。
跳转到_main
函数,那么_main
函数在哪?
在uboot的顶层目录,使用grep -nr "_main"
搜索一下。
可以看到有很多,根据实际情况,我们的芯片是32位的,所以定义应该是在
arch/arm/lib/crt0.S
,然后我们分析下*arch/arm/lib/crt0.S
*这个文件,以下带序号的是我增加的注释。
ENTRY(_main) /* * Set up initial C runtime environment and call board_init_f(0). */ ***1. 设置栈指针*** #if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK) ldr sp, =(CONFIG_SPL_STACK) #else ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) #endif **2. sp低3位清0,目的是8字节对齐** #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endif **3. 跳转执行board_init_f_alloc_reserve** mov r0, sp bl board_init_f_alloc_reserve mov sp, r0 /* set up gd here, outside any C code */ mov r9, r0 bl board_init_f_init_reserve **4. 跳转执行board_init_f** 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. */ **5. 设置gd这个结构体的起始地址** ldr sp, [r9, #**GD_START_ADDR_SP**] /* sp = gd->start_addr_sp */ #if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */ mov r3, sp bic r3, r3, #7 mov sp, r3 #else bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ #endif ldr r9, [r9, #GD_BD] /* r9 = gd->bd */ sub r9, r9, #GD_SIZE /* new GD is below bd */ **6. 代码重定位** 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) || defined(CONFIG_SPL_FRAMEWORK) # 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 ldr r0, =__bss_start /* this is auto-relocated! */ **7. 清BSS段** #ifdef CONFIG_USE_ARCH_MEMSET ldr r3, =__bss_end /* this is auto-relocated! */ mov r1, #0x00000000 /* prepare zero to clear BSS */ subs r2, r3, r0 /* r2 = memset len */ bl memset #else ldr r1, =__bss_end /* this is auto-relocated! */ mov r2, #0x00000000 /* prepare zero to clear BSS */ clbss_l:cmp r0, r1 /* while not at end of BSS */ #if defined(CONFIG_CPU_V7M) itt lo #endif strlo r2, [r0] /* clear 32-bit BSS word */ addlo r0, r0, #4 /* move to next */ blo clbss_l #endif #if ! defined(CONFIG_SPL_BUILD) bl coloured_LED_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 */ **8. 调用board_init_r** /* call board_init_r */ #if defined(CONFIG_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)
里面细节可以不用纠结,但是里面步骤可以记住,面试通常会问到。如果对汇编感兴趣,也可以先了解下这几条Arm汇编指令再去理解。
- 设置栈指针
- sp低3位清0,目的是8字节对齐
- 跳转执行**
board_init_f_alloc_reserve
** - 跳转执行**
board_init_f
** - 设置gd这个结构体的起始地址
- 代码重定位
- 清BSS段
- 调用**
board_init_r
**
GD_START_ADDR_SP
这个宏定义是通过计算偏移值得到的,DEFINE(GD_START_ADDR_SP, offsetof(struct global_data, start_addr_sp))
,定义在*lib/asm-offsets.c
*。
board_init_f_alloc_reserve
ulong board_init_f_alloc_reserve(ulong top) { /* Reserve early malloc arena */ #if defined(CONFIG_SYS_MALLOC_F) top -= CONFIG_SYS_MALLOC_F_LEN; #endif /* LAST : reserve GD (rounded up to a multiple of 16 bytes) */ top = rounddown(top-sizeof(struct global_data), 16); return top; }
这个函数是给预留一部分内存给全局变量gd结构体,并进行16字节对齐。返回的是已分配空间的地址。
board_init_f
学习uboot要知道的最重要的两个函数就是board_init_f
和board_init_r
。
void board_init_f(ulong boot_flags) { #ifdef CONFIG_SYS_GENERIC_GLOBAL_DATA /* * For some archtectures, global data is initialized and used before * calling this function. The data should be preserved. For others, * CONFIG_SYS_GENERIC_GLOBAL_DATA should be defined and use the stack * here to host global data until relocation. */ gd_t data; gd = &data; /* * Clear global data before it is accessed at debug print * in initcall_run_list. Otherwise the debug print probably * get the wrong vaule of gd->have_console. */ zero_global_data(); #endif 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) /* NOTREACHED - jump_to_copy() does not return */ hang(); #endif }
**CONFIG_SYS_GENERIC_GLOBAL_DATA
**没有定义,跳过。
然后对gd->flags
和gd->have_console
赋值。
gd
是指向struct global_data
的指针,这个地址通常用宏**DECLARE_GLOBAL_DATA_PTR
**获取。
所以gd
这个结构体的地址在汇编文件*arch/arm/lib/crt0.S
*里已经确定了,r9寄存器的值就是gd结构体的地址。
**DECLARE_GLOBAL_DATA_PTR
宏定义在arch/arm/include/asm/global_data.h
#ifdef CONFIG_ARM64 #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18") #else #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9") #endif #endif
内存布局关系可以参考网上一张图。
然后执行initcall_run_list
函数,跑init_sequence_f
这个函数指针数组里的函数,这里面的函数都是成功返回0,任一函数返回非0值就会失败,然后卡住,需要复位,具体看hang();
的实现。
initcall_run_list
int initcall_run_list(const init_fnc_t init_sequence[]) { const init_fnc_t *init_fnc_ptr; for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { unsigned long reloc_ofs = 0; int ret; if (gd->flags & GD_FLG_RELOC) reloc_ofs = gd->reloc_off; #ifdef CONFIG_EFI_APP reloc_ofs = (unsigned long)image_base; #endif debug("initcall: %p", (char *)*init_fnc_ptr - reloc_ofs); if (gd->flags & GD_FLG_RELOC) debug(" (relocated to %p)\n", (char *)*init_fnc_ptr); else debug("\n"); ret = (*init_fnc_ptr)(); if (ret) { printf("initcall sequence %p failed at call %p (err=%d)\n", init_sequence, (char *)*init_fnc_ptr - reloc_ofs, ret); return -1; } } return 0; }
init_sequence_f
里面的函数较多,都是各种初始化,这里不去一一分析,大概知道里面会初始化串口、CPU、内存相关等内容就行。
board_init_r
void board_init_r(gd_t *new_gd, ulong dest_addr) { #ifdef CONFIG_NEEDS_MANUAL_RELOC int i; #endif #ifdef CONFIG_AVR32 mmu_init_r(dest_addr); #endif #if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64) gd = new_gd; #endif #ifdef CONFIG_NEEDS_MANUAL_RELOC for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++) init_sequence_r[i] += gd->reloc_off; #endif if (**initcall_run_list**(init_sequence_r)) hang(); /* NOTREACHED - run_main_loop() does not return */ hang(); }
board_init_r
跟board_init_f
其实很类似,都是执行一个数组里的函数,board_init_r
执行init_sequence_r
这个数组里的函数,也是各种初始化函数,前面的初始化函数我们不一一分析,工作中用到再去看,数组的最后是执行run_main_loop
函数。
static int run_main_loop(void) { #ifdef CONFIG_SANDBOX sandbox_main_loop_init(); #endif /* main_loop() can return to retry autoboot, if so just run it again */ for (;;) **main_loop**(); return 0; }
可以看到这是一个死循环里的函数,因为uboot不可能一直往下跑。
main_loop
void main_loop(void) { const char *s; bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); #ifndef CONFIG_SYS_GENERIC_BOARD puts("Warning: Your board does not use generic board. Please read\n"); puts("doc/README.generic-board and take action. Boards not\n"); puts("upgraded by the late 2014 may break or be removed.\n"); #endif #ifdef CONFIG_VERSION_VARIABLE setenv("ver", version_string); /* set version variable */ #endif /* CONFIG_VERSION_VARIABLE */ 1. 初始化shell环境,我们可以在uboot里执行相关的命令,跟在linux上执行命令类似 cli_init(); run_preboot_environment_command(); #if defined(CONFIG_UPDATE_TFTP) update_tftp(0UL, NULL, NULL); #endif /* CONFIG_UPDATE_TFTP */ 2. 进入倒计时处理 s = bootdelay_process(); if (cli_process_fdt(&s)) cli_secure_boot_cmd(s); autoboot_command(s); cli_loop(); }
main_loop
函数主要是初始化shell环境,然后进入一个倒计时处理,用过都知道uboot启动内核前会有个倒计时,如果我们在这个倒计时前中断,就可以停在uboot设置环境变量里,就可以做修改环境变量、升级、更新镜像、用相关命令调试等。
bootdelay_process
const char *bootdelay_process(void) { char *s; int bootdelay; #ifdef CONFIG_BOOTCOUNT_LIMIT unsigned long bootcount = 0; unsigned long bootlimit = 0; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #ifdef CONFIG_BOOTCOUNT_LIMIT bootcount = bootcount_load(); bootcount++; bootcount_store(bootcount); setenv_ulong("bootcount", bootcount); bootlimit = getenv_ulong("bootlimit", 10, 0); #endif /* CONFIG_BOOTCOUNT_LIMIT */ **1. 从环境变量里读取倒计时多少秒** s = getenv("bootdelay"); bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; #if !defined(CONFIG_FSL_FASTBOOT) && defined(is_boot_from_usb) if (is_boot_from_usb()) { disconnect_from_pc(); printf("Boot from USB for mfgtools\n"); bootdelay = 0; set_default_env("Use default environment for \ mfgtools\n"); } else { printf("Normal Boot\n"); } #endif #ifdef CONFIG_OF_CONTROL bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay", bootdelay); #endif debug("### main_loop entered: bootdelay=%d\n\n", bootdelay); #if defined(CONFIG_MENU_SHOW) bootdelay = menu_show(bootdelay); #endif bootretry_init_cmd_timeout(); #ifdef CONFIG_POST if (gd->flags & GD_FLG_POSTFAIL) { s = getenv("failbootcmd"); } else #endif /* CONFIG_POST */ #ifdef CONFIG_BOOTCOUNT_LIMIT if (bootlimit && (bootcount > bootlimit)) { printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n", (unsigned)bootlimit); s = getenv("altbootcmd"); } else #endif /* CONFIG_BOOTCOUNT_LIMIT */ **s = getenv("bootcmd");** #if !defined(CONFIG_FSL_FASTBOOT) && defined(is_boot_from_usb) if (is_boot_from_usb()) { s = getenv("bootcmd_mfg"); printf("Run bootcmd_mfg: %s\n", s); } #endif **2. 把bootdelay复值给全局变量stored_bootdelay ** process_fdt_options(gd->fdt_blob); stored_bootdelay = bootdelay; **3. 返回字符串s,此时字符串s的内容就是bootcmd的内容,见57行,从环境变量获取bootcmd内容** return s; }
autoboot_command
void autoboot_command(const char *s) { debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) { #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) int prev = disable_ctrlc(1); /* disable Control C checking */ #endif run_command_list(s, -1, 0); #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) disable_ctrlc(prev); /* restore Control C checking */ #endif } #ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { s = getenv("menucmd"); if (s) run_command_list(s, -1, 0); } #endif /* CONFIG_MENUKEY */ }
倒计时在第5行完成,假设倒计时结束前终止了,则直接返回,执行cli_loop
函数,这就是那个shell环境,我们可以在里面输入命令,设置环境变量等。
如果倒计时完了没有终止,则会执行run_command_list
,s
是bootcmd
的内容。
run_command_list
int run_command_list(const char *cmd, int len, int flag) { int need_buff = 1; char *buff = (char *)cmd; /* cast away const */ int rcode = 0; if (len == -1) { len = strlen(cmd); #ifdef CONFIG_SYS_HUSH_PARSER /* hush will never change our string */ need_buff = 0; #else /* the built-in parser will change our string if it sees \n */ need_buff = strchr(cmd, '\n') != NULL; #endif } if (need_buff) { buff = malloc(len + 1); if (!buff) return 1; memcpy(buff, cmd, len); buff[len] = '\0'; } #ifdef CONFIG_SYS_HUSH_PARSER rcode = parse_string_outer(buff, FLAG_PARSE_SEMICOLON); #else /* * This function will overwrite any \n it sees with a \0, which * is why it can't work with a const char *. Here we are making * using of internal knowledge of this function, to avoid always * doing a malloc() which is actually required only in a case that * is pretty rare. */ rcode = **cli_simple_run_command_list**(buff, flag); #endif if (need_buff) free(buff); return rcode; }
第4行把常量转为非常量buff指针,传给cli_simple_run_command_list
。
cli_simple_run_command_list
int cli_simple_run_command_list(char *cmd, int flag) { char *line, *next; int rcode = 0; /* * Break into individual lines, and execute each line; terminate on * error. */ next = cmd; line = cmd; while (*next) { if (*next == '\n') { *next = '\0'; /* run only non-empty commands */ if (*line) { debug("** exec: \"%s\"\n", line); if (cli_simple_run_command(line, 0) < 0) { rcode = 1; break; } } line = next + 1; } ++next; } if (rcode == 0 && *line) rcode = (**cli_simple_run_command**(line, 0) < 0); return rcode; }
这个函数的逻辑就是拆分bootcmd
,去执行单个命令。
例如一条bootcmd的命令是run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
那它会在cli_simple_run_command
函数里面解析,遇到';'就会分割;
然后执行cmd_process
函数执行,先执行run mfgtool_args
,然后再执行bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
cmd_process
enum command_ret_t cmd_process(int flag, int argc, char * const argv[], int *repeatable, ulong *ticks) { enum command_ret_t rc = CMD_RET_SUCCESS; cmd_tbl_t *cmdtp; /* Look up command in command table */ cmdtp = find_cmd(argv[0]); if (cmdtp == NULL) { printf("Unknown command '%s' - try 'help'\n", argv[0]); return 1; } /* found - check max args */ if (argc > cmdtp->maxargs) rc = CMD_RET_USAGE; #if defined(CONFIG_CMD_BOOTD) /* avoid "bootd" recursion */ else if (cmdtp->cmd == do_bootd) { if (flag & CMD_FLAG_BOOTD) { puts("'bootd' recursion detected\n"); rc = CMD_RET_FAILURE; } else { flag |= CMD_FLAG_BOOTD; } } #endif /* If OK so far, then do the command */ if (!rc) { if (ticks) *ticks = get_timer(0); **rc = cmd_call(cmdtp, flag, argc, argv);** if (ticks) *ticks = get_timer(*ticks); *repeatable &= cmdtp->repeatable; } if (rc == CMD_RET_USAGE) rc = cmd_usage(cmdtp); return rc; }
第8行,先通过find_cmd
函数找到cmdtp
,cmdtp
是指向结构体的指针,里面包含了cmd和对应的回调函数。
struct cmd_tbl_s { char *name; /* Command Name */ int maxargs; /* maximum number of arguments */ int repeatable; /* autorepeat allowed? */ /* Implementation function */ int (*cmd)(struct cmd_tbl_s *, int, int, char * const []); char *usage; /* Usage message (short) */ #ifdef CONFIG_SYS_LONGHELP char *help; /* Help message (long) */ #endif #ifdef CONFIG_AUTO_COMPLETE /* do auto completion on the arguments */ int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]); #endif };
那么通过argv[0]
参数找到的是谁呢?
前面说到,cmd_process
是执行;隔开的命令,此时传进来的是run mfgtool_args
,那么argv[0]
就是run
,argv[1]
就是mfgtool_args
。
所以,现在是通过run
这个命令找到其对应的struct cmd_tbl_s
,对我们来说,进到cmd目录,使用grep -nr "run" *.c
,找到其定义在nvedit.c,1143行。
找到cmdtp
之后,34行执行cmd_call
。
cmd_call
static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { int result; result = (cmdtp->cmd)(cmdtp, flag, argc, argv); if (result) debug("Command failed, result=%d\n", result); return result; }
run
对应的cmdtp->cmd
就是do_run
。所以这里就是执行do_run
函数。
后面执行的bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
命令同样如此,通过bootz找到对应的cmdtp
,对应执行的是do_booz
函数,这里往下就是启动内核的。
do_bootz
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) { int ret; /* Consume 'bootz' */ argc--; argv++; if (**bootz_start**(cmdtp, flag, argc, argv, &images)) return 1; /* * We are doing the BOOTM_STATE_LOADOS state ourselves, so must * disable interrupts ourselves */ bootm_disable_interrupts(); images.os.os = IH_OS_LINUX; ret = do_bootm_states(cmdtp, flag, argc, argv, **BOOTM_STATE_OS_PREP **| **BOOTM_STATE_OS_FAKE_GO **| **BOOTM_STATE_OS_GO**, &images, 1); return ret; }
执行bootz_start
。
bootz_start
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], bootm_headers_t *images) { int ret; ulong zi_start, zi_end; 1. 这里进入do_bootm_states,只是执行了bootm_start和找boot_fn ret = **do_bootm_states**(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1); /* Setup Linux kernel zImage entry point */ if (!argc) { images->ep = load_addr; debug("* kernel: default image load address = 0x%08lx\n", load_addr); } else { images->ep = simple_strtoul(argv[0], NULL, 16); debug("* kernel: cmdline image address = 0x%08lx\n", images->ep); } **2. 设置内核镜像的起始地址和结束地址** **ret = bootz_setup(images->ep, &zi_start, &zi_end);** if (ret != 0) return 1; lmb_reserve(&images->lmb, images->ep, zi_end - zi_start); /* * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not * have a header that provide this informaiton. */ if (bootm_find_images(flag, argc, argv)) return 1; #ifdef CONFIG_SECURE_BOOT extern uint32_t authenticate_image( uint32_t ddr_start, uint32_t image_size); if (authenticate_image(images->ep, zi_end - zi_start) == 0) { printf("Authenticate zImage Fail, Please check\n"); return 1; } #endif return 0; }
函数内容很多,关注重点,最开始就执行**do_bootm_states
**。
do_bootm_states
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int states, bootm_headers_t *images, int boot_progress) { boot_os_fn *boot_fn; ulong iflag = 0; int ret = 0, need_boot_fn; images->state |= states; /* * Work through the states and see how far we get. We stop on * any error. */ **1. 入参states传了BOOTM_STATE_START,所以会执行16行** if (states & BOOTM_STATE_START) ret = bootm_start(cmdtp, flag, argc, argv); if (!ret && (states & BOOTM_STATE_FINDOS)) ret = bootm_find_os(cmdtp, flag, argc, argv); if (!ret && (states & BOOTM_STATE_FINDOTHER)) { ret = bootm_find_other(cmdtp, flag, argc, argv); argc = 0; /* consume the args */ } /* Load the OS */ if (!ret && (states & BOOTM_STATE_LOADOS)) { ulong load_end; iflag = bootm_disable_interrupts(); ret = bootm_load_os(images, &load_end, 0); if (ret == 0) lmb_reserve(&images->lmb, images->os.load, (load_end - images->os.load)); else if (ret && ret != BOOTM_ERR_OVERLAP) goto err; else if (ret == BOOTM_ERR_OVERLAP) ret = 0; #if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY) if (images->os.os == IH_OS_LINUX) fixup_silent_linux(); #endif } /* Relocate the ramdisk */ #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH if (!ret && (states & BOOTM_STATE_RAMDISK)) { ulong rd_len = images->rd_end - images->rd_start; ret = boot_ramdisk_high(&images->lmb, images->rd_start, rd_len, &images->initrd_start, &images->initrd_end); if (!ret) { setenv_hex("initrd_start", images->initrd_start); setenv_hex("initrd_end", images->initrd_end); } } #endif #if defined(CONFIG_OF_LIBFDT) && defined(CONFIG_LMB) if (!ret && (states & BOOTM_STATE_FDT)) { boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr); ret = boot_relocate_fdt(&images->lmb, &images->ft_addr, &images->ft_len); } #endif /* From now on, we need the OS boot function */ if (ret) return ret; **2. 找boot_fn函数** ** boot_fn = bootm_os_get_boot_func(images->os.os);** need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE | BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO); if (boot_fn == NULL && need_boot_fn) { if (iflag) enable_interrupts(); printf("ERROR: booting os '%s' (%d) is not supported\n", genimg_get_os_name(images->os.os), images->os.os); bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS); return 1; } /* Call various other states that are not generally used */ if (!ret && (states & BOOTM_STATE_OS_CMDLINE)) ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images); if (!ret && (states & BOOTM_STATE_OS_BD_T)) ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images); ** 3.第二次执行该函数的时候,states传入了BOOTM_STATE_OS_PREP,所以会执行90行** if (!ret && (states & **BOOTM_STATE_OS_PREP**)) ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images); #ifdef CONFIG_TRACE /* Pretend to run the OS, then run a user command */ if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) { char *cmd_list = getenv("fakegocmd"); ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO, images, boot_fn); if (!ret && cmd_list) ret = run_command_list(cmd_list, -1, flag); } #endif /* Check for unsupported subcommand. */ if (ret) { puts("subcommand not supported\n"); return ret; } **4**.** 第二次执行该函数的时候,执行boot_selected_os** /* Now run the OS! We hope this doesn't return */ if (!ret && (states & **BOOTM_STATE_OS_GO**)) ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO, images, boot_fn); /* Deal with any fallout */ err: if (iflag) enable_interrupts(); if (ret == BOOTM_ERR_UNIMPLEMENTED) bootstage_error(BOOTSTAGE_ID_DECOMP_UNIMPL); else if (ret == BOOTM_ERR_RESET) do_reset(cmdtp, flag, argc, argv); return ret; }
这个函数内容很多,其实就是做了两个工作:
- 执行了16行的
bootm_start
- 找到
boot_fn
函数,就是do_bootm_linux
执行完后,最终会回到do_bootz
函数,再重新执行do_bootm_states
,不一样的是,此时states传入的宏有**BOOTM_STATE_OS_PREP
、BOOTM_STATE_OS_FAKE_GO
和BOOTM_STATE_OS_GO
**。
- 执行
boot_fn
函数 - 执行
boot_selected_os
函数
boot_selected_os
int boot_selected_os(int argc, char * const argv[], int state, bootm_headers_t *images, boot_os_fn *boot_fn) { arch_preboot_os(); boot_fn(state, argc, argv, images); /* Stand-alone may return when 'autostart' is 'no' */ if (images->os.type == IH_TYPE_STANDALONE || state == BOOTM_STATE_OS_FAKE_GO) /* We expect to return */ return 0; bootstage_error(BOOTSTAGE_ID_BOOT_OS_RETURNED); #ifdef DEBUG puts("\n## Control returned to monitor - resetting...\n"); #endif return BOOTM_ERR_RESET; }
这里调用boot_fn
,执行do_bootm_linux
,也是第二次执行了。
每次执行的目的是不一样的,根据status选择。
do_bootm_linux
int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images) { /* No need for those on ARM */ if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE) return -1; if (flag & BOOTM_STATE_OS_PREP) { boot_prep_linux(images); return 0; } if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) { **boot_jump_linux**(images, flag); return 0; } boot_prep_linux(images); boot_jump_linux(images, flag); return 0; }
boot_jump_linux
static void boot_jump_linux(bootm_headers_t *images, int flag) { #ifdef CONFIG_ARM64 void (*kernel_entry)(void *fdt_addr, void *res0, void *res1, void *res2); int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1, void *res2))images->ep; debug("## Transferring control to Linux (at address %lx)...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); announce_and_cleanup(fake); if (!fake) { do_nonsec_virt_switch(); kernel_entry(images->ft_addr, NULL, NULL, NULL); } #else unsigned long machid = gd->bd->bi_arch_number; char *s; void (*kernel_entry)(int zero, int arch, uint params); unsigned long r2; int fake = (flag & BOOTM_STATE_OS_FAKE_GO); 1. 设置内核入口地址 kernel_entry = (void (*)(int, int, uint))images->ep; s = getenv("machid"); if (s) { if (strict_strtoul(s, 16, &machid) < 0) { debug("strict_strtoul failed!\n"); return; } printf("Using machid 0x%lx from environment\n", machid); } debug("## Transferring control to Linux (at address %08lx)" \ "...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); **2. 这个也很重要,会卸载一些驱动,释放内存等。** **announce_and_cleanup(fake);** if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params; if (!fake) { #ifdef CONFIG_ARMV7_NONSEC if (armv7_boot_nonsec()) { armv7_init_nonsec(); secure_ram_addr(_do_nonsec_entry)(kernel_entry, 0, machid, r2); } else #endif 3. 跳转到内核镜像的地址执行 **kernel_entry(0, machid, r2);** } #endif }
到此uboot的正常启动流程就结束了。
里面还有很多细节,工作中根据实际情况去分析就行。uboot的启动流程面试中经常会问到,因此熟悉它是非常必要的。
你有什么想问的?欢迎评论区留言或滴滴我。
ps:我是哆哆,一枚二本机械狗,从华为外包逆袭到芯片原厂公司,目前从业于半导体行业,在一家芯片原厂公司任职Linux固件工程师,喜欢阅读内核源码,深入操作系统的世界,除此之外,星主还喜欢个人成长,秉承终身学习,终身成长,坚持运动。
星主从最开始玩51单片机,到stm32,然后玩Linux,逼自己看过很多书籍,自学的过程走过很多弯路,最后也如愿以偿。所以,我想把我的经验分享给朋友们。同时,打造一个嵌入式圈子,欢迎所有嵌入式行业的朋友进来。期待你的关注!