uboot分析:uboot启动内核

本文详细解析了U-Boot启动内核的过程,包括内核的搬移、格式校验、参数准备及跳转执行等关键步骤。阐述了bootcmd环境变量的作用,内核格式zImage与uImage的区别,以及U-Boot如何通过标记链表向内核传递启动参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(注:本文参考资料:朱有鹏嵌入式课程。本文为个人学习记录,如有错误,欢迎指正。)

  1. U-Boot启动内核概述
    U-Boot启动完成后,最终进入到main_loop()循环中。若在bootdelay倒计时为0之前,U-Boot控制台有输入,则进入命令解析-执行的循环;若控制台无输入,U-Boot将启动内核。
    U-Boot启动内核可归结为以下四个步骤:
    1)将内核搬移至DDR中;

                        2)校验内核格式、CRC;
    
                        3)准备传参;
    
                        4)跳转执行内核。
    
  2. U-Boot启动内核过程分析
    2.1 将内核搬移至DDR中
    在/uboot/lib_arm/board.c->start_armboot()函数调用/uboot/common/main.c->main_loop()函数,main_loop()函数中包含了内核的启动代码。
    复制代码
    s = getenv (“bootcmd”); //获取bootcmd环境变量的值
    debug ("### main_loop: bootcmd="%s"\n", s ? s : “”);

if (bootdelay >= 0 && s && !abortboot (bootdelay))
{

        #ifndef CFG_HUSH_PARSER

run_command (s, 0); //执行bootcmd环境变量中的命令

}
复制代码
环境变量bootcmd的值在开发板配置文件(/uboot/include/configs/x210_sd.h)中定义。
复制代码
#if defined(CFG_FASTBOOT_NANDBSP)
#define CONFIG_BOOTCOMMAND “nand read C0008000 600000 400000; nand read 30A00000 B00000 180000;
bootm C0008000 30A00000”
#elif defined(CFG_FASTBOOT_SDMMCBSP)
#define CONFIG_BOOTCOMMAND “movi read kernel C0008000; movi read rootfs 30A00000 180000;
bootm C0008000 30A00000”
#endif
复制代码
环境变量bootcmd包含如下命令:
bootcmd=nand read C0008000 600000 400000; /将kernel(大小0x00400000字节)从nand中的0x00600000地址处拷贝到DDR中的 0xc0008000地址处/
nand read 30A00000 B00000 180000; /将rootfs(大小0x00180000字节)从nand中的0x00B00000地址处拷贝到DDR中的 0x30A08000地址处/
bootm C0008000 30A00000 //启动kernel、rootfs
U-Boot能准确识别kernel、rootfs在NandFlash中的位置,是因为U-Boot中已对NandFlash进行分区。烧录时,严格按照分区要求将uboot、kernel、rootfs烧录进NandFlash中即可。
在U-Boot下执行mtd命令,即可查看各个分区的情况。
2.2 校验内核格式、CRC
内核格式有两类:zImage和uImage。
并不是所有U-Boot都支持zImage,是否支持就看其配置文件(x210_sd.h)中是否定义CONFIG_ZIMAGE_BOOT这个宏。所以有些uboot是支持zImage启动的,有些则不支持。但是所有的uboot肯定都支持uImage启动。
zImage
Linux内核经过编译后生成一个ELF格式的可执行文件,vmlinux。再通过arm-linux-objcopy工具进行加工,最后进行压缩,得到zImage格式的内核镜像,可以烧录进启动介质中。

uImage

uImage是由zImage加工得到的。uboot中的mkimage工具将zImage加工生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。

ulmage格式的内核头部信息在/uboot/include/Image.h中定义。
ih_load是加载地址,即内核在DDR中的地址(运行地址);ih_ep是内核入口地址。
复制代码
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;
复制代码
执行环境变量bootcmd中的命令"bootm C0008000 30A00000",实质是执行do_bootm()函数(/uboot/common/Cmd_bootm.c->do_bootm())。
do_bootm()函数在标号after_header_check之前,都是在校验内核的头部信息。根据头部信息,判断内核格式和进行CRC校验。
实际上,从NandFlash中读取的uImage可以放置在DDR中的任意位置,只要不破坏U-Boot占有的内存空间即可。因为do_bootm()函数内部从头部获取内核的加载地址,当发现该uImage当前所处的地址与加载地址不同时,会将内核搬移至加载地址中。一般情况下,都会将内核搬移至加载地址中,便不用使用do_bootm()函数来搬移内核,提高效率。
do_bootm()函数将根据系统类型,启动内核。此处将调用/uboot/lib_arm/Bootm.c/->do_bootm_linux ()函数启动内核。
复制代码
after_header_check:
os = hdr->ih_os;
#endif

switch (os) {
default: /* handled by (original) Linux case */

    case IH_OS_LINUX:

#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv, &images);
break;

case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
break;

复制代码

2.3 准备传参
U-Boot中以标记链表(tagged list)形式来传递启动参数。
标记是一种数据结构,标记链表就是挨着存放的多个标记。
标记有多种类型,在/uboot/include/asm-arm/Setup.h中定义。标记链表以ATAG_CORE开始,以标记ATAG_NONE结束,中间包含其他标记。
复制代码
#define ATAG_CORE 0x54410001 //起始标记
#define ATAG_NONE 0x00000000 //结束标记
#define ATAG_MEM 0x54410002
#define ATAG_VIDEOTEXT 0x54410003
#define ATAG_RAMDISK 0x54410004
#define ATAG_INITRD 0x54410005
#define ATAG_INITRD2 0x54420005
#define ATAG_SERIAL 0x54410006
#define ATAG_REVISION 0x54410007
#define ATAG_VIDEOLFB 0x54410008
#define ATAG_CMDLINE 0x54410009
#define ATAG_ACORN 0x41000101
#define ATAG_MEMCLK 0x41000402
#define ATAG_MTDPART 0x41001099
复制代码
标记的数据结构定义在/uboot/include/asm-arm/Setup.h中
复制代码
struct tag_header
{
  u32 size; //表示标记的类型
  u32 tag; //表示标记的结构
};

struct tag {
struct tag_header hdr;
//不同的标记类型使用不同的联合,每一个标记对应一个数据结构体
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;

            /*
            * Acorn specific
            */
            struct tag_acorn        acorn;
            /*
             * DC21285 specific
             */
            struct tag_memclk       memclk;
            struct tag_mtdpart      mtdpart_info;
    } u;

};
复制代码
标记列表举例:

复制代码
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;

params->hdr.tag = ATAG_CORE; //起始标记
params->hdr.size = tag_size (tag_core);

params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;

params = tag_next (params);
}

#ifdef CONFIG_SETUP_MEMORY_TAGS
static void setup_memory_tags (bd_t *bd)
{
int i;

for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);

params->u.mem.start = bd->bi_dram[i].start;
params->u.mem.size = bd->bi_dram[i].size;

params = tag_next (params);
}
}

#endif

…//中间还有多个标记

static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE; //结束标记
params->hdr.size = 0;
}
复制代码
do_bootm_linux ()函数启动内核前,先将U-Boot中的启动参数传给内核。
参数分析:
1)0 : 相当于mov r0 #0。

2)machid : U-Boot中的机器码,从全局变量bd中获取。内核机器码和U-Boot机器码必须一致才能启动内核。

3)bd->bi_boot_params : 启动参数地址。

复制代码
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], bootm_headers_t *images)
{

//标记链表初始化

setup_start_tag (bd);
setup_serial_tag (&params);
setup_revision_tag (&params);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_initrd_tag (bd, initrd_start, initrd_end);
setup_videolfb_tag ((gd_t *) gd);
setup_mtdpartition_tag();
setup_end_tag (bd);
.......................................
void    (*theKernel)(int zero, int arch, uint params);       //定义函数指针theKernel
 ......................................
theKernel = (void (*)(int, int, uint))ep;                             //将入口地址赋值给theKernel
......................................
theKernel (0, machid, bd->bi_boot_params);                //传参,调用theKernel
......................................

}
复制代码
2.4 跳转执行内核
theKernel()函数执行成功后,内核将读取启动参数,并开始启动。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值