[IMX][UBoot] 10.启动流程 (6) - bootz 命令启动 Linux

文章链接

UBoot 启动流程 (1) - 基本流程

UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f

UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code

UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r

UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop

UBoot 启动流程 (6) - bootz 命令启动 Linux


目录

1.系统镜像相关的数据结构

1.1.镜像的头部信息 - image_header_t

1.2.镜像的基本信息 - image_info_t

1.3.镜像文件在 uboot 中的描述 - bootm_headers_t

1.3.1.镜像头部信息

1.3.2.系统信息

1.3.3.引导状态

1.3.4.线性内存管理器 - LMB

2.booz 命令启动内核的流程 - do_bootz()

2.1.根据阶段执行对应的函数 - do_bootm_states()

2.2.images 初始化 - bootz_start()

2.2.1.初始化 images 和 LMB - bootm_start()

2.2.2.设置加载地址

2.2.3.检查 zImage 是否有效 - bootz_setup()

2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()

2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()

2.3.禁用中断 - bootm_disable_interrupts()

2.4.准备运行环境并启动内核

2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()

2.4.2.Linux 启动函数 - do_bootm_linux()

2.4.3.Linux 运行环境准备 - boot_prep_linux()

2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()

3.启动过程概览


1.系统镜像相关的数据结构

  • image_header_t 类型的结构体,保存系统镜像的头部信息,如 CRC 校验码、操作系统类型等;

  • image_info_t 类型的结构体,保存系统镜像的基本信息,如系统镜像的起始地址,加载地址等;

  • bootm_headers_t 类型的结构体,保存系统镜像的所有信息 (包括镜像头部信息、镜像基本信息等),以及平台中涉及系统加载的相关信息 (如系统镜像在内存中的起始地址,设备树的加载地址等);

1.1.镜像的头部信息 - image_header_t

image_header_t 保存系统镜像的头部信息:

// include/image.h
typedef struct image_header {__be32      ih_magic;   /* Image Header Magic Number    */__be32      ih_hcrc;    /* Image Header CRC Checksum    */__be32      ih_time;    /* Image Creation Timestamp */__be32      ih_size;    /* Image Data Size      */__be32      ih_load;    /* Data  Load  Address      */__be32      ih_ep;      /* Entry Point Address      */__be32      ih_dcrc;    /* Image Data CRC Checksum  */uint8_t     ih_os;      /* Operating System     */uint8_t     ih_arch;    /* CPU architecture     */uint8_t     ih_type;    /* Image Type           */uint8_t     ih_comp;    /* Compression Type     */uint8_t     ih_name[IH_NMLEN];  /* Image Name       */
} image_header_t;

其成员的含义如下 (其中 __be32 表示大端序 uint32 数据类型):

  • ih_magic:镜像头部的魔数,根据该值判断文件是否为镜像文件;

  • ih_hcrc:镜像头部的 CRC 校验码,判断文件是否有效 (是否出错);

  • ih_time:该镜像文件的创建时间;

  • ih_size:整个系统镜像文件的大小;

  • ih_load:系统在内存中的加载地址 (程序存储起始地址);

  • ih_ep:系统在内存中的运行地址 (程序运行起始地址);

  • ih_dcrc:整个系统镜像文件的 CRC 校验码;

  • ih_os:系统代码,例如,Linux 系统的 ih_os 值为 0x05:

// include/image.h
/** Operating System Codes*/
#define IH_OS_INVALID       0   /* Invalid OS */
#define IH_OS_OPENBSD       1   /* OpenBSD    */
...
#define IH_OS_LINUX         5   /* Linux      */
...
#define IH_OS_QNX           16  /* QNX        */
...
#define IH_OS_OPENRTOS      24  /* OpenRTOS   */
  • ih_arch:CPU 的架构代码,例如,ARM 架构 SoC 的 ih_arch 为 0x02:

// include/image.h
/** CPU Architecture Codes (supported by Linux)*/
#define IH_ARCH_INVALID     0   /* Invalid CPU  */
...
#define IH_ARCH_ARM         2   /* ARM      */
#define IH_ARCH_I386        3   /* Intel x86    */
...
#define IH_ARCH_MIPS        5   /* MIPS     */
...
#define IH_ARCH_ARM64       22  /* ARM64    */
...
#define IH_ARCH_X86_64      24  /* AMD x86_64, Intel and Via */
  • ih_type:镜像类型,例如系统镜像、Boot 镜像等:

// include/image.h
/** Image Types** "Standalone Programs" are directly runnable in the environment*  provided by U-Boot; it is expected that (if they behave*  well) you can continue to work in U-Boot after return from*  the Standalone Program.* "OS Kernel Images" are usually images of some Embedded OS which*  will take over control completely. Usually these programs*  will install their own set of exception handlers, device*  drivers, set up the MMU, etc. - this means, that you cannot*  expect to re-enter U-Boot except by resetting the CPU.* "RAMDisk Images" are more or less just data blocks, and their*  parameters (address, size) are passed to an OS kernel that is*  being started.* "Multi-File Images" contain several images, typically an OS*  (Linux) kernel image and one or more data images like*  RAMDisks. This construct is useful for instance when you want*  to boot over the network using BOOTP etc., where the boot*  server provides just a single image file, but you want to get*  for instance an OS kernel and a RAMDisk image.**  "Multi-File Images" start with a list of image sizes, each*  image size (in bytes) specified by an "uint32_t" in network*  byte order. This list is terminated by an "(uint32_t)0".*  Immediately after the terminating 0 follow the images, one by*  one, all aligned on "uint32_t" boundaries (size rounded up to*  a multiple of 4 bytes - except for the last file).** "Firmware Images" are binary images containing firmware (like*  U-Boot or FPGA images) which usually will be programmed to*  flash memory.** "Script files" are command sequences that will be executed by*  U-Boot's command interpreter; this feature is especially*  useful when you configure U-Boot to use a real shell (hush)*  as command interpreter (=> Shell Scripts).*/
#define IH_TYPE_INVALID     0   /* Invalid Image        */
#define IH_TYPE_STANDALONE  1   /* Standalone Program       */
#define IH_TYPE_KERNEL      2   /* OS Kernel Image      */
...
#define IH_TYPE_RKIMAGE     23  /* Rockchip Boot Image      */
#define IH_TYPE_RKSD        24  /* Rockchip SD card     */
#define IH_TYPE_RKSPI       25  /* Rockchip SPI image       */
#define IH_TYPE_ZYNQIMAGE   26  /* Xilinx Zynq Boot Image */
#define IH_TYPE_COUNT       27  /* Number of image types */
  • ih_comp:文件压缩类型:

// include/image.h
/** Compression Types*/
#define IH_COMP_NONE        0   /* No    Compression Used   */
#define IH_COMP_GZIP        1   /* gzip  Compression Used   */
#define IH_COMP_BZIP2       2   /* bzip2 Compression Used   */
#define IH_COMP_LZMA        3   /* lzma  Compression Used   */
#define IH_COMP_LZO         4   /* lzo   Compression Used   */
#define IH_COMP_LZ4         5   /* lz4   Compression Used   */
  • ih_name[IH_NMLEN]:镜像名称,名称最大长度为 32 个字符 (包含 31 个有效字符和 1 个结束符 \0):

// include/image.h
#define IH_NMLEN        32  /* Image Name Length        */

1.2.镜像的基本信息 - image_info_t

image_info_t 类型的结构体中,包含了系统镜像的基本信息,如起始地址、所支持的 CPU 架构等:

// include/image.h
typedef struct image_info {ulong       start, end;     /* start/end of blob */ulong       image_start, image_len; /* start of image within blob, len of image */ulong       load;           /* load addr for the image */uint8_t     comp, type, os; /* compression, type of image, os type */uint8_t     arch;           /* CPU architecture */
} image_info_t;

其各个成员的含义如下:

  • start, end:镜像文件在存储介质中的起始地址和结束地址;

  • image_start, image_len:系统程序在文件中的起始地址和长度;

  • load:系统程序在内存中的加载地址 (程序的起始地址,如 0x80008000 等);

  • comp:系统镜像的压缩格式;

  • type:镜像类型,例如系统镜像、Boot 镜像等:

  • os:系统类型,如 Linux、Windows 等;

  • arch:系统对应的架构,如 ARM、MIPS 等;

1.3.镜像文件在 uboot 中的描述 - bootm_headers_t

uboot 中使用 bootm_headers_t 类型的变量 images 描述镜像文件 (如 zImage、uImage 等):

// include/image.h
typedef struct bootm_headers {/** Legacy os image header, if it is a multi component image* then boot_get_ramdisk() and get_fdt() will attempt to get* data from second and third component accordingly.*/image_header_t  *legacy_hdr_os;      /* image header pointer */image_header_t   legacy_hdr_os_copy; /* header copy */ulong            legacy_hdr_valid;
...image_info_t    os; /* os image info */ulong           ep; /* entry point of OS */ulong       rd_start, rd_end; /* ramdisk start/end */char        *ft_addr;   /* flat dev tree address */ulong       ft_len;     /* length of flat device tree */ulong       initrd_start;ulong       initrd_end;ulong       cmdline_start;ulong       cmdline_end;bd_t        *kbd;
...int     verify;     /* getenv("verify")[0] != 'n' */#define BOOTM_STATE_START   (0x00000001)
#define BOOTM_STATE_FINDOS  (0x00000002)
#define BOOTM_STATE_FINDOTHER   (0x00000004)
#define BOOTM_STATE_LOADOS  (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT     (0x00000020)
#define BOOTM_STATE_OS_CMDLINE  (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO  (0x00000200)    /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO   (0x00000400)int     state;
...struct lmb  lmb;        /* for memory mgmt */
...
} bootm_headers_t;
-----------------------------------------------------------------------------// cmd/bootm.c
bootm_headers_t images;     /* pointers to os/initrd/fdt images */

1.3.1.镜像头部信息

  • *legacy_hdr_os 为镜像头部信息的指针;

  • legacy_hdr_os_copy 为镜像头部信息的副本;

  • legacy_hdr_valid 标记镜像头部信息是否有效;

// include/image.h
typedef struct bootm_headers {image_header_t  *legacy_hdr_os;      /* image header pointer */image_header_t   legacy_hdr_os_copy; /* header copy */ulong            legacy_hdr_valid;...

1.3.2.系统信息

  • os 为系统镜像的基本信息;

  • ep 为系统程序在镜像文件中的起始地址;

  • rd_start, rd_end 分别为虚拟磁盘 RAMDisk 在内存中的起始地址和结束地址;

  • *ft_addr 为设备树的起始地址;

  • ft_len 为设备树的长度;

  • initrd_start 为初始化 RAMDisk 的起始地址;

  • initrd_end 为初始化 RAMDisk 的结束地址;

  • cmdline_start 为 Linux 启动命令行的起始地址;

  • cmdline_end 为 Linux 启动命令行的结束地址;

  • *kbd 为开发板的硬件信息 bd 的指针;

  • verify 为校验使能标志,若环境变量 verify 不为 n 则启用校验;

// include/image.h
typedef struct bootm_headers {...image_info_t    os; /* os image info */ulong           ep; /* entry point of OS */ulong       rd_start, rd_end; /* ramdisk start/end */char        *ft_addr;   /* flat dev tree address */ulong       ft_len;     /* length of flat device tree */ulong       initrd_start;ulong       initrd_end;ulong       cmdline_start;ulong       cmdline_end;bd_t        *kbd;
...int     verify;     /* getenv("verify")[0] != 'n' */

1.3.3.引导状态

  • BOOTM_STATE_xxx 宏定义内核的引导状态,即内核加载过程中的各个阶段;

  • state 为内核当前的引导状态;

// include/image.h
typedef struct bootm_headers {...
#define BOOTM_STATE_START   (0x00000001)
#define BOOTM_STATE_FINDOS  (0x00000002)
#define BOOTM_STATE_FINDOTHER   (0x00000004)
#define BOOTM_STATE_LOADOS  (0x00000008)
#define BOOTM_STATE_RAMDISK (0x00000010)
#define BOOTM_STATE_FDT     (0x00000020)
#define BOOTM_STATE_OS_CMDLINE  (0x00000040)
#define BOOTM_STATE_OS_BD_T (0x00000080)
#define BOOTM_STATE_OS_PREP (0x00000100)
#define BOOTM_STATE_OS_FAKE_GO  (0x00000200)    /* 'Almost' run the OS */
#define BOOTM_STATE_OS_GO   (0x00000400)int     state;

1.3.4.线性内存管理器 - LMB

  • lmb (Logical Memory Blocks) 为线性内存块管理器,用于动态内存分配:

    • memory 为可用的内存区域;

    • reserved 为预留内存区域,如设备映射内存、启动代码所占用的内存;

// include/lmb.h
struct lmb {struct lmb_region memory;struct lmb_region reserved;
};
-------------------------------------------// include/image.h
typedef struct bootm_headers {...struct lmb  lmb;        /* for memory mgmt */

2.booz 命令启动内核的流程 - do_bootz()

bootz 命令用于启动 Linux 内核,其对应的执行函数为 do_bootz(),包含如下几个阶段:

  • bootz_start() 初始化 images 结构体,设置系统的加载地址,并为系统镜像预留内存;

  • bootm_disable_interrupts() 在启动过程中禁用所有中断;

  • 准备内核的执行环境并启动内核;

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 初始化 images 结构体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(); // 标记需要启动的系统类型为 Linuximages.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;
}

2.1.根据阶段执行对应的函数 - do_bootm_states()

内核的加载过程分为多个阶段,do_bootm_states() 根据阶段标志 states 调用对应的函数:

// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{.../* Work through the states and see how far we get. We stop on any error. */// 初始化阶段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));...}/* Relocate the ramdisk */// RAMDisk 重定位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 && (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);}// 查找内核对应的启动函数/* From now on, we need the OS boot function */...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) {...}// 执行内核对应的启动函数/* 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);if (!ret && (states & BOOTM_STATE_OS_PREP))ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);...// 运行内核/* 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);|--> boot_fn(state, argc, argv, images);// 启用中断,执行复位/* 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;
}

2.2.images 初始化 - bootz_start()

uboot 使用 bootm_headers_t 类型的结构体 images 描述镜像文件,内核启动前,先调用 bootz_start() 初始化 images 结构体以及运行环境 (分配内存、查找设备树等):

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 执行初始化阶段 BOOTM_STATE_STARTret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);/* Setup Linux kernel zImage entry point */// 设置 OS 在内存中的起始地址 0x80800000if (!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);}// 检查镜像文件是否有效ret = bootz_setup(images->ep, &zi_start, &zi_end);...// 在内存中为镜像文件预留空间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.*/// 查找 RAMDisk、设备树等其他模块if (bootm_find_images(flag, argc, argv))return 1;...return 0;
}

2.2.1.初始化 images 和 LMB - bootm_start()

do_bootm_states() 调用 BOOTM_STATE_START 阶段对应的函数 bootm_start():

  • 调用 memset() 清空 images 结构体;

  • boot_start_lmb() 初始化 LMB,并创建一个 dummy lmb,同时在内存中分配内存;

  • bootstage_mark_name() 记录当前阶段的相关信息;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 执行初始化阶段 BOOTM_STATE_STARTret = do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START, images, 1);
-------------------------------------------------------------------------------   // common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{...if (states & BOOTM_STATE_START)ret = bootm_start(cmdtp, flag, argc, argv);
-------------------------------------------------------------------------------// common/bootm.c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[])
{// 清空 images 结构体memset((void *)&images, 0, sizeof(images));// 检查环境变量 verify,确定是否需要对镜像文件进行校验images.verify = getenv_yesno("verify"); // 初始化 LMBboot_start_lmb(&images);|--> lmb_init(&images->lmb); // 将 LMB 的地址、大小等全部置 0,计数值初始化为 1|--> lmb_add(&images->lmb, (phys_addr_t)mem_start, mem_size); // 分配一块 lmb|--> arch_lmb_reserve(&images->lmb);|--> sp = get_sp(); // 获取当前的 SP 指针|--> sp -= 4096; // 4K 字节对齐|--> lmb_reserve(lmb, sp, ...); // 分配栈空间 |--> board_lmb_reserve(&images->lmb); // 未实现该方法// 记录当前阶段的相关信息bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");images.state = BOOTM_STATE_START;return 0;
}

2.2.2.设置加载地址

用户可以在命令行中指定 OS 的加载地址,若未指定则使用 load_addr 中保存的默认地址:

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 设置 OS 在内存中的起始地址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.2.3.检查 zImage 是否有效 - bootz_setup()

  • 使用 map_sysmem() 将镜像地址映射为可访问的指针 (获取镜像文件的头部信息);

  • 检查魔数是否为 Linux 镜像文件;

  • 打印镜像文件的起始地址和结束地址;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 检查镜像文件是否有效ret = bootz_setup(images->ep, &zi_start, &zi_end);
--------------------------------------------------------------// arch/arm/lib/bootm.c
int bootz_setup(ulong image, ulong *start, ulong *end)
{struct zimage_header *zi;// 检查镜像文件是否有效zi = (struct zimage_header *)map_sysmem(image, 0); // 获取镜像文件的头部信息if (zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC) { // 检查是否为 Linux 镜像文件puts("Bad Linux ARM zImage magic!\n");return 1;}// 打印镜像文件在内存中的起始地址和结束地址*start = zi->zi_start;*end = zi->zi_end;printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]\n", image, *start, *end);return 0;
}

2.2.4.在 LMB 中为镜像文件预留空间 - lmb_reserve()

调用 lmb_reserve() 为镜像文件预留内存空间 (分配 LMB):

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 在内存中为镜像文件预留空间lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
----------------------------------------------------------------// lib/lmb.c
long lmb_reserve(struct lmb *lmb, phys_addr_t base, phys_size_t size)
{struct lmb_region *_rgn = &(lmb->reserved);return lmb_add_region(_rgn, base, size); // 分配一个 LMB
}

2.2.5.查找 RAMDisk 和设备树 - bootm_find_images()

  • boot_get_ramdisk() 查找 RAMDisk (IMX 未使用);

  • boot_get_fdt() 查找设备树,并设置设备树文件的起始地址和长度;

// cmd/bootm.c
static int bootz_start(cmd_tbl_t *cmdtp, int flag, int argc,char * const argv[], bootm_headers_t *images)
{...// 查找 RAMDisk、设备树等其他模块if (bootm_find_images(flag, argc, argv))return 1;
-----------------------------------------------------------// common/bootm.c
int bootm_find_images(int flag, int argc, char * const argv[])
{int ret;/* find ramdisk */ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,&images.rd_start, &images.rd_end);if (ret) {puts("Ramdisk image is corrupt or invalid\n");return 1;}#if defined(CONFIG_OF_LIBFDT)/* find flattened device tree */// 在镜像文件中查找设备树ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT, &images,&images.ft_addr, &images.ft_len);|--> ... // 在镜像文件中查找设备树|--> *of_flat_tree = fdt_blob; // 将设备树的地址保存至 images.ft_addr|--> *of_size = fdt_totalsize(fdt_blob); // 将设备树的长度保存至 images.ft_len...// 为设备树分配内存,并将设备树在内存中的起始地址,保存至环境变量 fdtaddr 中set_working_fdt_addr((ulong)images.ft_addr);|--> buf = map_sysmem(addr, 0);|--> setenv_hex("fdtaddr", addr);
#endif...return 0;
}

2.3.禁用中断 - bootm_disable_interrupts()

内核加载过程中需要禁用中断,同时禁用以太网和 USB:

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 内核启动过程中禁用中断bootm_disable_interrupts(); 
------------------------------------------------------------------------// common/bootm.c
ulong bootm_disable_interrupts(void)
{ulong iflag;/** We have reached the point of no return: we are going to* overwrite all exception vector code, so we cannot easily* recover from any failures any more...*/iflag = disable_interrupts();... // 禁用以太网和 USBreturn iflag;
}
------------------------------------------------------------------------// arch/arm/lib/interrupts.c
int disable_interrupts (void)
{unsigned long old,temp;__asm__ __volatile__("mrs %0, cpsr\n""orr %1, %0, #0xc0\n""msr cpsr_c, %1": "=r" (old), "=r" (temp):: "memory");return (old & 0x80) == 0;
}

2.4.准备运行环境并启动内核

分别调用 BOOTM_STATE_OS_PREPBOOTM_STATE_OS_FAKE_GOBOOTM_STATE_OS_GO 阶段对应的函数:

// cmd/bootm.c
int do_bootz(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{...// 标记需要启动的系统类型为 Linuximages.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);

2.4.1.查找 OS 对应的启动函数 - bootm_os_get_boot_func()

不同操作系统所使用的启动函数不同,启动函数定义在 boot_os_fn 类型的结构体数组 boot_os 中,Linux 系统对应的启动函数为 do_bootm_linux():

// common/bootm_os.c
static boot_os_fn *boot_os[] = {[IH_OS_U_BOOT] = do_bootm_standalone,
#ifdef CONFIG_BOOTM_LINUX[IH_OS_LINUX] = do_bootm_linux, // Linux 系统对应的启动函数
#endif
#ifdef CONFIG_BOOTM_NETBSD[IH_OS_NETBSD] = do_bootm_netbsd,
#endif
...
#ifdef CONFIG_BOOTM_OPENRTOS[IH_OS_OPENRTOS] = do_bootm_openrtos,
#endif
};

do_bootm_states() 启动内核时,会调用 bootm_os_get_boot_func() 查找系统对应的启动函数:

// common/bootm.c
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],int states, bootm_headers_t *images, int boot_progress)
{.../* From now on, we need the OS boot function */...boot_fn = bootm_os_get_boot_func(images->os.os); // 查找系统对应的启动函数|--> return boot_os[os]; // boot_fn = do_bootm_linux()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);.../* Call various other states that are not generally used */...// 执行 do_bootm_linux()if (!ret && (states & BOOTM_STATE_OS_PREP))ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

do_bootz() 启动 Linux 内核时,设置了 BOOTM_STATE_OS_PREP 标志,表示需要启动的系统类型为 Linux,因此会执行 do_bootm_linux()

2.4.2.Linux 启动函数 - do_bootm_linux()

检查启动阶段标志,并执行对应的系统环境准备函数 boot_prep_linux() 或运行函数 boot_jump_linux():

// arch/arm/lib/bootm.c
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;}// 运行 Linux 内核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;
}

2.4.3.Linux 运行环境准备 - boot_prep_linux()

处理环境变量 bootargs,其中保存了传递给 Linux 内核的启动参数:

// arch/arm/lib/bootm.c
static void boot_prep_linux(bootm_headers_t *images)
{// 从环境变量 bootargs 中读取 Linux 的启动命令char *commandline = getenv("bootargs");if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) { // 检查是否使用设备树debug("using: FDT\n");if (image_setup_linux(images)) { // 准备启动环境printf("FDT creation failed! hanging...");hang();}} else if (BOOTM_ENABLE_TAGS) {...} else {printf("FDT and ATAGS support not compiled in - hanging\n");hang();}
}

若配置使用设备树,则调用 image_setup_linux() 准备启动环境,包括调整设备树、分配存储 cmdline 的内存、处理 Ramdisk 以及设置启动参数:

  • 将 LMB 中的内存区域添加至设备树;

  • 在内核中为 cmdline 分配内存;

  • 重定位 RAMDisk;

  • 重定位设备树;

  • 设置设备树中内存相关的节点;

// common/image.c
int image_setup_linux(bootm_headers_t *images)
{ulong of_size = images->ft_len; // 获取设备树的长度char **of_flat_tree = &images->ft_addr; // 获取设备树在镜像文件中的起始地址ulong *initrd_start = &images->initrd_start; // RAMDisk 的起始地址ulong *initrd_end = &images->initrd_end; // RAMDisk 的结束地址struct lmb *lmb = &images->lmb; // LMBulong rd_len;int ret;// 将 LMB 中预留的内存区域,添加到设备树的 /memory 节点和 /reserved-memory 节点,// 使内核可以知道需要预留这些区域if (IMAGE_ENABLE_OF_LIBFDT)boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);// 在内核中分配内存,用于存储 cmdlineif (IMAGE_BOOT_GET_CMDLINE) {ret = boot_get_cmdline(lmb, &images->cmdline_start, &images->cmdline_end);if (ret) {puts("ERROR with allocation of cmdline\n");return ret;}}// 将 RAMDisk 重定位至高地址的内存中,避免和内核文件冲突if (IMAGE_ENABLE_RAMDISK_HIGH) {rd_len = images->rd_end - images->rd_start;ret = boot_ramdisk_high(lmb, images->rd_start, rd_len,initrd_start, initrd_end);if (ret)return ret;}// 如果当前设备树的位置不合适,例如,与内核加载区域冲突,// 则使用 LMB 分配新的内存区域并复制设备树if (IMAGE_ENABLE_OF_LIBFDT) {ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);if (ret)return ret;}// 设置设备树中的 bootargs 节点,更新 /memory 节点的内存范围,添加预留内存的相关描述if (IMAGE_ENABLE_OF_LIBFDT && of_size) {ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb);if (ret)return ret;}return 0;
}

2.4.4.跳转至 Linux 内核运行 - boot_jump_linux()

查找 Linux 内核的运行函数 kernel_entry (Linux 系统的第一个函数,类似于 _start) 并执行

内核运行前,会调用 announce_and_cleanup() 打印启动信息并清除之前的缓存 (I-Cache 和 D-Cache):

// arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64...
#elseunsigned long machid = gd->bd->bi_arch_number; // 开发板的硬件 ID (使用设备树时无效)char *s;void (*kernel_entry)(int zero, int arch, uint params);unsigned long r2;int fake = (flag & BOOTM_STATE_OS_FAKE_GO);// 运行 Linux 内核的入口kernel_entry = (void (*)(int, int, uint))images->ep;...// 输出调试信息,表示即将开始 Linux 内核的运行阶段debug("## Transferring control to Linux (at address %08lx)" \"...\n", (ulong) kernel_entry);// 记录当前的运行阶段bootstage_mark(BOOTSTAGE_ID_RUN_OS);// 打印启动信息:starting kernel ... 并初始化缓存 (清理掉原来的缓存)announce_and_cleanup(fake);|--> cleanup_before_linux(); // 清除 I-Cache 和 D-Cache// 获取设备树的地址if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)r2 = (unsigned long)images->ft_addr;elser2 = gd->bd->bi_boot_params;...// 运行内核kernel_entry(0, machid, r2);...
}

因为后续将要运行 Linux 内核,之前 uboot 所使用的数据缓存与指令缓存不再需要,调用 announce_and_cleanup() 将其清除,最后执行 kernel_entry() 运行 Linux 内核,其第二个参数 r2 为设备树的地址或者启动命令 bootargs

kernel_entry() 由操作系统定义,是其运行的第一个函数,类似 uboot 的 _start() 函数

3.启动过程概览

综上,uboot 启动 Linux 内核的过程大致如下图所示:

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

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

相关文章

TCP 三次握手协商 MSS 前,如何确定 MSS 值(结合 Linux 内核源码分析)

文章目录 一、SYN总结影响 SYN MSS 的因素 二、SYNACK总结影响 SYNACK MSS 的因素 结合 Linux 内核源码 一、SYN 总结影响 SYN MSS 的因素 套接字选项 TCP_MAXSEG路由选项 advmss出口 MTU 减去 40(TCP 和 IP 的固定首部大小)IPV4_MAX_PMTU - 40(同上) 二、SYNACK 总结影响 SY…

面试150 矩阵置0

思路 我们使用两个标记集合,分别记录当矩阵的元素为0的时候的横、纵坐标。然后在对矩阵元素进行遍历,如果所在行或者所在列的索引在集合中,对应的矩阵元素修改为0即可 class Solution:def setZeroes(self, matrix: List[List[int]]) -> N…

Element UI 完整使用实战示例

以下是 Element UI 的完整使用实战示例,涵盖从环境搭建、基础组件使用到项目实战的全流程,结合多个实际场景和代码示例: 一、环境搭建与基础配置 1. 安装 Element UI 通过 npm 或 yarn 安装: npm install element-ui --save # …

C# 线程同步(一)同步概念介绍

目录 1.阻塞(Blocking) 2.阻塞 VS 轮询 3.线程状态 到目前为止,我们已经阐述了如何在线程上启动任务、配置线程以及实现双向数据传递。同时,我们也说明了局部变量是线程私有的,而引用可以通过共享字段在线程间传递以…

解决leetcode第3588题.找到最大三角形面积

3588.找到最大三角形面积难度:中等问题描述:给你一个二维数组coords,大小为nx2,表示一个无限笛卡尔平面上n个点的坐标。找出一个最大三角形的两倍面积,其中三角形的三个顶点来自coords中的任意三个点,并且该…

WIFI 安全测试记录

之前为实训课特意买的无线网卡没用上,但是我怎么可能让他荒废。所以用了几个下午,浅学了WiFi,当然没找到什么好教材,自己摸索着学的很基础,主要是当练习了,特此把我此前学习…WiFi密码实践过程写上来。 省流…

android14设置--网络--Internet副标题修改

收银机订制项目 插SIM卡,设备使用数据流量时,设置–网络–Internet副标题显示对应SIM卡运营商名称,客户要求修改这时的名称(注意图标也要同步修改) packages\apps\Settings\src\com\android\settings\network\InternetPreferenceController.j…

Web3区块链有哪些岗位?

Web3区块链领域的岗位丰富多样,涵盖技术开发、产品管理、运营、商务等多个方面,以下是具体介绍: - 技术开发类: - 智能合约开发工程师:负责编写、审计和优化智能合约,常见于DeFi开发,包括抵押…

解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题

场景重现在使用 MyBatis/Mybatis-Plus 框架对 MySQL 操作时习惯了字段名小驼峰映射,然而在操作 Elasticsearch 时发现字段名没有小驼峰映射。解决方法1. 使用 ObjectMapper 手动转换: 这是最直接也最常用的方法。 在 Spring Boot 应用中使用 ObjectMappe…

Error:Cannot find module ‘chokidar‘

错误复现 在vue开发中,出现报错:Error:Cannot find module ‘chokidar’ 原因 缺包导致 解决方案 直接安装依赖包 npm install chokidar依旧无效,删除node_modules重新安装 rm -rf node_modules npm i

Spring AI 向量数据库详解与 RAG 简单实战项目

一、什么是向量数据库? 向量数据库用于存储、检索稠密语义向量(Embedding),是构建 RAG(检索增强生成)系统的核心组件。它支持近似最近邻搜索(ANN),可根据语义相似度找出…

【RK3568+PG2L50H开发板实验例程】Linux部分/FPGA FSPI 通信案例

本原创文章由深圳市小眼睛科技有限公司创作,版权归本公司所有,如需转载,需授权并注明出处(www.meyesemi.com) 1. 简介 本案例旨在 ARM端运行 Linux系统,基通过 FSPI测试。 2. ARM端和 FPGA端通信流程 (1)ARM端实现SP…

github如何创建一个自己的仓库保姆级教程

文章目录 准备阶段(github官网)添加ssh公钥添加token创建仓库 本地设置本地代理创建仓库添加文件到仓库进行提交 准备阶段(github官网) 添加ssh公钥 创建SSH KEY。先看一下你C盘用户目录下有没有.ssh目录,有的话看下里面有没有id_rsa和id_rsa.pub这两个文件&#…

LabVIEW 网络流通信功能

LabVIEW 的网络流技术实现主机 VI(Host VI)与客户端 VI(ClientVI)间的双向数据交互,包含命令发送与波形数据传输,支持跨设备、跨进程的实时通信,满足分布式系统中数据交互与控制需求。 主机 VI逻…

Prompt 精通之路(一)- AI 时代的新语言:到底什么是 Prompt?为什么它如此重要?

AI 时代的新语言:到底什么是 Prompt?为什么它如此重要? 标签: #Prompt新手指南 #提示词入门 #AI指令 #人工智能 #ChatGPT 🚀 Prompt 精通之路:系列文章导航 第一篇:AI 时代的新语言&#xff1a…

uniapp 滚动tab

uniapp woui unibest <route lang"json5">{style: {navigationBarTitleText: 知识产权,navigationBarBackgroundColor: #C80F06,navigationBarTextStyle: white,backgroundColorTop: #C80F06,},} </route> <template><view class"bgc-b …

日事清驾驶舱模式上线:实时数据更新+项目管理+数据可视化,提升决策效率​

大家好&#xff01;我们在日事清最新更新中推出了一个令人激动的新功能——驾驶舱模式。这一全新界面将为企业管理者和团队提供一个全面、实时的数据展示平台。下面&#xff0c;让我们详细了解这个功能如何帮助您更好地把握企业动态和提升决策效率。 快速入口&#xff1a;一键激…

【Maven】Maven深度避坑指南:依赖冲突全维度解决方案与工业级实战(超万字解析)

注&#xff1a;本文基于50大型企业级项目经验&#xff0c;结合Maven底层源码机制&#xff0c;系统化解决依赖冲突问题。包含20个实战场景、10类特殊案例及5大防御体系构建方案。 Maven深度避坑指南&#xff1a;依赖冲突全维度解决方案与工业级实战&#xff08;超万字解析&#…

Rust Web 全栈开发(二):构建 HTTP Server

Rust Web 全栈开发&#xff08;二&#xff09;&#xff1a;构建 HTTP Server Rust Web 全栈开发&#xff08;二&#xff09;&#xff1a;构建 HTTP Server创建成员包/库&#xff1a;httpserver、http解析 HTTP 请求HTTP 请求的构成构建 HttpRequest 构建 HTTP 响应HTTP 响应的构…

小架构step系列01:小架构初衷

1 概述 小公司做业务服务&#xff0c;需要聚焦到实际的业务上&#xff0c;尽快通过业务服务客户&#xff0c;给客户创建价值&#xff0c;公司才能生存下去。在技术上采用的Web应用架构具备以下特点&#xff1a; 主要由开源组件组装而成。这样既可以节省成本&#xff0c;也可以把…