硬核26000字分析uboot启动过程

简介: 硬核26000字分析uboot启动全过程

更好的阅读体验请见:硬核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汇编指令再去理解。

  1. 设置栈指针
  2. sp低3位清0,目的是8字节对齐
  3. 跳转执行**board_init_f_alloc_reserve**
  4. 跳转执行**board_init_f**
  5. 设置gd这个结构体的起始地址
  6. 代码重定位
  7. 清BSS段
  8. 调用**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_fboard_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->flagsgd->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_rboard_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_listsbootcmd的内容。

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函数找到cmdtpcmdtp是指向结构体的指针,里面包含了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]就是runargv[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;
}

这个函数内容很多,其实就是做了两个工作:

  1. 执行了16行的bootm_start
  2. 找到boot_fn函数,就是do_bootm_linux

执行完后,最终会回到do_bootz函数,再重新执行do_bootm_states,不一样的是,此时states传入的宏有**BOOTM_STATE_OS_PREPBOOTM_STATE_OS_FAKE_GOBOOTM_STATE_OS_GO**。

  1. 执行boot_fn函数
  2. 执行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,逼自己看过很多书籍,自学的过程走过很多弯路,最后也如愿以偿。所以,我想把我的经验分享给朋友们。同时,打造一个嵌入式圈子,欢迎所有嵌入式行业的朋友进来。期待你的关注!

相关文章
|
6月前
|
存储 Linux 芯片
【启动】芯片启动过程全解析
【启动】芯片启动过程全解析
154 0
|
7月前
|
存储 监控 Java
三万字长文:JVM内存问题排查Cookbook
本文主要系统性地整理了排查思路,为大家遇到问题时提供全面的排查流程,不至于漏掉某些可能性误入歧途浪费时间。
|
运维 Devops
科普贴硬件---什么是裸板?如何开机?
科普贴硬件---什么是裸板?如何开机?
|
Linux 芯片 内存技术
uboot和linux内核移植流程简述
uboot和linux内核移植流程简述
291 0
|
算法 Unix Linux
2.5w字 + 36 张图爆肝操作系统面试题(一)
大家好,我是 cxuan,我之前汇总了一下关于操作系统的面试题,最近又重新翻阅了一下发现不是很全,现在也到了面试季了,所以我又花了一周的时间修订整理了一下这份面试题,这份面试题可以吊打市面上所有的操作系统面试题了,不是我说,是因为我系统查过,如果有不相信的大佬,欢迎狠狠的打我脸。
2.5w字 + 36 张图爆肝操作系统面试题(一)
|
Unix Linux 索引
学好Linux基础指令不再删库跑路
学好Linux基础指令不再删库跑路
112 0
学好Linux基础指令不再删库跑路
|
Java Linux
【收集】【Linux】常出没的指令(持续更新...)
【收集】【Linux】常出没的指令(持续更新...)
115 0
【收集】【Linux】常出没的指令(持续更新...)
|
存储 算法 调度
2.5w字 + 39 张图爆肝操作系统面试题(四)
大家好,我是 cxuan,我之前汇总了一下关于操作系统的面试题,最近又重新翻阅了一下发现不是很全,现在也到了面试季了,所以我又花了一周的时间修订整理了一下这份面试题,这份面试题可以吊打市面上所有的操作系统面试题了,不是我说,是因为我系统查过,如果有不相信的大佬,欢迎狠狠的打我脸。
2.5w字 + 39 张图爆肝操作系统面试题(四)
|
存储 算法 Java
2.5w字 + 41 张图爆肝操作系统面试题(六)
大家好,我是 cxuan,我之前汇总了一下关于操作系统的面试题,最近又重新翻阅了一下发现不是很全,现在也到了面试季了,所以我又花了一周的时间修订整理了一下这份面试题,这份面试题可以吊打市面上所有的操作系统面试题了,不是我说,是因为我系统查过,如果有不相信的大佬,欢迎狠狠的打我脸。
2.5w字 + 41 张图爆肝操作系统面试题(六)