U-boot启动流程[三]

U-boot启动流程[三]


文章目录

  • U-boot启动流程[三]
    • 1 Linux启动基础镜像
      • 1.1 内核镜像
        • 1.1.1 vmlinux镜像
        • 1.1.2 Image和zImage镜像
      • 1.2 设备树
      • 1.3 根文件系统
    • 2 U-boot支持的镜像格式
      • 2.1 Legacy uimage格式
      • 2.2 Fit uimage格式
      • 2.3 Boot image格式
    • 3 bootm流程


本文基于以下软硬件假定:
架构:AARCH64
软件:U-boot 2022.10-rc1

1 Linux启动基础镜像

U-boot主要用于启动操作系统,以armv8架构下的linux为例,其启动时需要包含kernel、dtb和rootfs三部分。U-boot镜像都是以它们为基础制作的,因此在介绍U-boot镜像格式之前我们需要先了解一下它们的构成。

1.1 内核镜像

1.1.1 vmlinux镜像

linux内核编译完成后会在根目录生成原始的内核文件为vmlinux,使用readelf工具可看到其为elf文件格式:

hilbert@ubuntu:~/work/linux$ readelf -h vmlinux
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Shared object file)
  Machine:                           AArch64
  Version:                           0x1
  Entry point address:               0xffff800010000000
  Start of program headers:          64 (bytes into file)
  Start of section headers:          13681696 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         3
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

由于uboot引导的镜像不能包含elf头,因此该镜像不能直接被uboot使用。

1.1.2 Image和zImage镜像

Image镜像是vlinux经过objcopy去头后生成的纯二进制文件,对于armv8架构其编译的Makefile如下

OBJCOPYFLAGS_Image := -O binary -R .note -R .note.gnu.build-id -R .comment –S   (1)
targets := Image Image.bz2 Image.gz Image.lz4 Image.lzma Image.lzo
$(obj)/Image: vmlinux FORCE                                                     
        $(call if_changed,objcopy)2)

$(obj)/Image.bz2: $(obj)/Image FORCE
        $(call if_changed,bzip2)3)

$(obj)/Image.gz: $(obj)/Image FORCE
        $(call if_changed,gzip)4)

$(obj)/Image.lz4: $(obj)/Image FORCE
        $(call if_changed,lz4)5)

$(obj)/Image.lzma: $(obj)/Image FORCE
        $(call if_changed,lzma)6)

$(obj)/Image.lzo: $(obj)/Image FORCE
        $(call if_changed,lzo)7

(1)objcopy命令使用的flag定义
(2)以vmlinux为原始文件,通过objcopy命令制作Image镜像。其命令可扩展如下:

aarch64-linux-gnu-objcopy -O binary -R .note -R .note.gnu.build-id -R .comment -S vmlinux Image

该命令会执行以下操作:

 a –O binary:将输出二进制镜像,即会去掉elf头
 b –R .note:-R选项表示去掉镜像中指定的section,如这里会去掉.note、.note.gnu.build-id和.comment段
 c –S:去掉符号表和重定位信息,它与-R选项的功能类似,都是为了减小镜像的size

因此,执行该命令后生成的Image镜像是去掉elf头,去掉.note等无用的section,以及strip过的二进制镜像。它可以被uboot的booti命令直接启动。但若要使用bootm启动,则还需要将其进一步封装为后面介绍的uimage或bootimg镜像

(3 – 7)以Image为源文件,调用不同的压缩算法,对镜像进行压缩。若调用gzip命令,则可将其压缩为我们熟悉的zImage镜像。与Image一样,压缩后的镜像也是可以被booti直接启动,且经过封装以后可以被bootm启动的

1.2 设备树

设备树是设备树dts源文件经过编译后生成的,其目标文件为二进制格式的dtb文件。其示例编译命令如下:

    dtc -I dts -O dtb -o example.dtb example.dts
(1) –I:指定输入文件格式
(2)–O:指定输出文件格式
(3)–o:指定输出文件名

设备树还支持dtb overlay机制,即可以向设备提供一个基础dtb和多个dtbo镜像,并在启动前将它们merge为最终的dtb。下面用一个例子来说明:

(1)基础dts文件base.dts内容如下:

/dts-v1/;
                / {
                        foo: foonode {
                                foo-property;
                        };
                }; 

(2)dtbo源文件overlay.dts内容如下:

/dts-v1/;
/plugin/;
/ {
                        fragment@1 {
                                target = <&foo>;
                                __overlay__ {
                                        overlay-1-property;
                                        bar: barnode {
                                                bar-property;
                                        };
                                };
                        };
                        };

(3)分别使用以下命令编译dtb和dtbo

 dtc -@ -I dts -O dtb -o base.dtb base.dts
 dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts

(4)将dtbo merge到base dtb上

  a 通过fit镜像包含和定义一个overlay,这种情况uboot会自动解析fit参数并执行merge操作
  b 手动加载和apply overlay

(5)手动merge流程

a 设置base dtb和dtbo的地址

    setenv fdtaddr 0x87f00000
    setenv fdtovaddr 0x87fc0000
b 加载base dtb和dtbo     

    load ${devtype} ${bootpart} ${fdtaddr} ${bootdir}/base.dtb
    load ${devtype} ${bootpart} ${fdtovaddr} ${bootdir}/overlay.dtbo
c 将base dtb设置为工作dtb   

        fdtaddr $fdtaddr
d 增大dtb的size,以使其可以容纳所有overlay   
    
    fdt resize 8192
e apply dtb overlay   

    fdt apply $fdtovadd

1.3 根文件系统

linux可以支持多种形式的根文件系统,如initrd、initramfs、基于磁盘的根文件系统等。站在启动镜像的角度看其实它们都是制作好的文件系统镜像,内核可以从特定的位置获取并挂载它们。以下是它们在启动时的基本特性:

(1)initrd

它是一种内存文件系统,需要由bootloader预先加载到内存中,并将其内存地址传递给内核。如uboot将initrd加载到地址$initrd_addr处,则bootm参数如下:

    bootm  $kernel_addr  $initrd_addr  $fdt_addr

(2)initramfs

initramfs也是一种内存文件系统,但与initrd不同,它是与内核打包在一起的。因此不需要通过额外的参数

(3)磁盘rootfs

磁盘根文件系统会被刷写到flash、mmc或disk的分区中,在内核启动时可在bootargs添加下面格式的参数,以指定根文件系统的位置

root=/dev/xxx

因此,以上这些rootfs只有initrd是需要uboot独立加载的,故只有当rootfs为initrd时,uboot镜像打包流程才需要在镜像打包时为其单独考虑

2 U-boot支持的镜像格式

2.1 Legacy uimage格式

uboot最先支持legacy uimage格式的镜像,它是在内核镜像基础上添加一个64字节header生成的。该header信息用于指定镜像的一些属性,如内核的类型、压缩算法类型、加载地址、运行地址、crc完整性校验值等。其格式如下:

typedef struct image_header {
        uint32_t        ih_magic;       /* Image Header Magic Number    */
        uint32_t        ih_hcrc;        /* Image Header CRC Checksum    */
        uint32_t        ih_time;        /* Image Creation Timestamp     */
        uint32_t        ih_size;        /* Image Data Size              */
        uint32_t        ih_load;        /* Data  Load  Address          */
        uint32_t        ih_ep;          /* Entry Point Address          */
        uint32_t        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;

uboot的bootm命令会解析镜像头中的信息,并根据这些信息执行镜像校验、解压和启动等流程。以下是创建uImage的命令示例:

mkimage -A arm64 -O linux -C none -T kernel -a 0x80008000 -e 0x80008040 -n Linux_Image -d zImage uImage

但是它也有着一些缺点,如:

(1)加载流程比较繁琐,如需要分别加载内核、initrd和dtb
(2)启动参数较多,需要分别制定内核、initrd和dtb的地址
(3)在支持secure boot的系统中对secure boot的支持不足

为此,uboot又定义了一种新的镜像格式fit uimage,用于解决上述问

2.2 Fit uimage格式

Fit uimage是使用devicetree语法来定义uimage镜像描述信息以及启动时的各种属性,这些信息被写入一个后缀名为its的源文件中。以下是一个its文件的示例

/dts-v1/;

/ {
        description = "Various kernels, ramdisks and FDT blobs";
        #address-cells = <1>;

        images {
                kernel-1 {
                        description = "vanilla-2.6.23";              (1)
                        data = /incbin/("./vmlinux.bin.gz");         (2)
                        type = "kernel";                             (3)
                        arch = "ppc";                                (4)
                        os = "linux";                                (5)
                        compression = "gzip";                        (6)
                        load = <00000000>;                           (7)
                        entry = <00000000>;                          (8)
                        hash-1 {
                                algo = "md5";                        (9)
                        };
                        hash-2 {
                                algo = "sha1";
                        };
                };

                kernel-2 {
                        description = "2.6.23-denx";
                        data = /incbin/("./2.6.23-denx.bin.gz");
                        type = "kernel";
                        arch = "ppc";
                        os = "linux";
                        compression = "gzip";
                        load = <00000000>;
                        entry = <00000000>;
                        hash-1 {
                                algo = "sha1";
                        };
                };

                kernel-3 {
                        description = "2.4.25-denx";
                        data = /incbin/("./2.4.25-denx.bin.gz");
                        type = "kernel";
                        arch = "ppc";
                        os = "linux";
                        compression = "gzip";
                        load = <00000000>;
                        entry = <00000000>;
                        hash-1 {
                                algo = "md5";
                        };
                };

                ramdisk-1 {
                        description = "eldk-4.2-ramdisk";
                        data = /incbin/("./eldk-4.2-ramdisk");
                        type = "ramdisk";
                        arch = "ppc";
                        os = "linux";
                        compression = "gzip";
                        load = <00000000>;
                        entry = <00000000>;
                        hash-1 {
                                algo = "sha1";
                        };
                };

                ramdisk-2 {
                        description = "eldk-3.1-ramdisk";
                        data = /incbin/("./eldk-3.1-ramdisk");
                        type = "ramdisk";
                        arch = "ppc";
                        os = "linux";
                        compression = "gzip";
                        load = <00000000>;
                        entry = <00000000>;
                        hash-1 {
                                algo = "crc32";
                        };
                };

                fdt-1 {
                        description = "tqm5200-fdt";
                        data = /incbin/("./tqm5200.dtb");
                        type = "flat_dt";
                        arch = "ppc";
                        compression = "none";
                        hash-1 {
                                algo = "crc32";
                        };
                };

                fdt-2 {
                        description = "tqm5200s-fdt";
                        data = /incbin/("./tqm5200s.dtb");
                        type = "flat_dt";
                        arch = "ppc";
                        compression = "none";
                        load = <00700000>;
                        hash-1 {
                                algo = "sha1";
                        };
                };

        };

        configurations {
                default = "config-1";

                config-1 {
                        description = "tqm5200 vanilla-2.6.23 configuration";
                        kernel = "kernel-1";
                        ramdisk = "ramdisk-1";
                        fdt = "fdt-1";
                };

                config-2 {
                        description = "tqm5200s denx-2.6.23 configuration";
                        kernel = "kernel-2";
                        ramdisk = "ramdisk-1";
                        fdt = "fdt-2";
                };

                config-3 {
                        description = "tqm5200s denx-2.4.25 configuration";
                        kernel = "kernel-3";
                        ramdisk = "ramdisk-2";
                };
        };
};

它包含imagesconfigurations两个顶级节点,images指定该its文件会包含哪些镜像,以及这些镜像的属性信息。configurations用于定义一系列镜像组合信息,如在本例中包含了config-1config-2config-3三种镜像组合方式。Its使用default属性指定启动时默认采用的配置信息,若启动时不希望使用默认配置,则可通过在启动参数中动态指定配置序号。下面我们通过kernel-1节点看下image属性的含义:

1. 镜像的描述信息
2. 镜像文件的路径
3. 镜像类型,如kernel、ramdisk或fdt
4. 支持的架构
5. 支持的操作系统
6. 其使用的压缩算法
7. 加载地址
8. 运行地址
9. 完整性校验使用的hash算法

configurations的属性比较简单,就是指定某个配置下使用哪一个kerneldtbramdisk镜像。Fit image除了支持完整性校验外,还可支持hash算法 + 非对称算法的secure boot方案,如以下例子:

kernel {
                        data = /incbin/("test-kernel.bin");
                        type = "kernel_noload";
                        arch = "sandbox";
                        os = "linux";
                        compression = "none";
                        load = <0x4>;
                        entry = <0x8>;
                        kernel-version = <1>;
                        signature {
                                algo = "sha1,rsa2048";        (1)
                                key-name-hint = "dev";       (2)
                        };
                };
  • (1)指定sha1为secure boot签名使用的hash算法,rsa2048为其使用的签名算法
  • (2)可能使用的验签密钥名

与设备树类似,its文件可以通过mkimage和dtc编译生成itb文件。镜像生成方式如下:

mkimage -f xxx.its xxx.itb

xxx.itb文件可以直接传给uboot,并通过bootm命令执行,如xxx.itb被加载到0x80000000,则其命令如下:

bootm 0x80000000

若需要选择非默认的镜像配置,则可通过指定配置序号实现,例如:

bootm 0x80000000#config@2

2.3 Boot image格式

boot imageandroid定义的启动镜像格式,到目前为止一共定义了三个版本(v0 – v2),其中v0版本包含andr_img_hdrkernelramdisksecond stage,v1版本增加了recovery dtbo/acpio,v2版本又增加了dtb。在这些镜像中second stage是可选的,而recovery dtbo只有在使用recovery分区的非AB系统中才需要,且它们都需要page对齐(通常为2k)。以下是boot image镜像的基本格式:

U-boot启动流程[三]_第1张图片

andr_img_hdr镜像头用于描述这些镜像的信息,如其长度、加载地址等,其定义如下:

struct andr_img_hdr {
    /* Must be ANDR_BOOT_MAGIC. */
    char magic[ANDR_BOOT_MAGIC_SIZE];

    u32 kernel_size; /* size in bytes */
    u32 kernel_addr; /* physical load addr */

    u32 ramdisk_size; /* size in bytes */
    u32 ramdisk_addr; /* physical load addr */

    u32 second_size; /* size in bytes */
    u32 second_addr; /* physical load addr */

    u32 tags_addr; /* physical addr for kernel tags */
    u32 page_size; /* flash page size we assume */

    u32 header_version;
    u32 os_version;

    char name[ANDR_BOOT_NAME_SIZE]; /* asciiz product name */

    char cmdline[ANDR_BOOT_ARGS_SIZE];

    u32 id[8]; /* timestamp / checksum / sha1 / etc */

    char extra_cmdline[ANDR_BOOT_EXTRA_ARGS_SIZE];

    u32 recovery_dtbo_size;   /* size in bytes for recovery DTBO/ACPIO image */
    u64 recovery_dtbo_offset; /* offset to recovery dtbo/acpio in boot image */
    u32 header_size;

    u32 dtb_size; /* size in bytes for DTB image */
    u64 dtb_addr; /* physical load address for DTB image */
} __attribute__((packed));

可以使用mkbootimg.py脚本制作boot image镜像,该脚本参数比较简单,就是指定与镜像头中定义的相关参数。例如:

mkbootimg.py \
        --base aaa \
        --kernel kernel/arch/arm64/boot/Image.gz \
        --kernel_offset bbb \
        --second kernel/arch/arm64/boot/ccc.dtb \
        --second_offset ddd \
        --board test_board \
        -o boot.img

3 bootm流程

bootm是uboot用于启动操作系统的命令,它的主要流程包括根据镜像头获取镜像信息,解压镜像,以及启动操作系统。以下为其主要执行流程:

U-boot启动流程[三]_第2张图片

以上流程最终会调用特定os的启动函数,例如需要启动armv8架构的linux,则其调用的接口为arch/arm/lib/bootm.c中的do_bootm_linux。以下为其执行流程:

U-boot启动流程[三]_第3张图片

上面的流程都比较直观,有兴趣可以对照代码自行分析一下


你可能感兴趣的:(Firefly-RK3399,linux,服务器,驱动开发,arm开发,架构)