U-BOOT启动kernel的过程

2024-08-28 11:58
文章标签 启动 过程 boot kernel

本文主要是介绍U-BOOT启动kernel的过程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

u-boot是一种bootloader,它其实就是一段单机程序,在系统上电时自动执行,初始化硬件设备,准备好软件环境,就是为了达到其终极目的——启动内核。
本文记录了以u-boot启动运行在ARM上的Linux为例,拷贝内核镜像文件到SDRAM后,调用do_bootm的过程。话不多说,先上软件流程图:
这里写图片描述

一、内核镜像文件的检查

内核镜像文件拷贝到SDRAM上之后,需要对镜像文件进行检查,包括Image Magic Number,镜像文件头CRC,内核内容CRC,是否支持当前的CPU,是否需要解压,将内核内容拷贝到内核启动地址。这一些列的操作都是通过common/cmd_bootm.c中的do_bootm()函数来实现的。
@cmd_tbl_t *cmdtp: do_bootm的命令结构体指针
@argc: 参数个数,以”bootm 0x30007FC0”为例,argc = 2
@argv:存放参数,argv[0] = “bootm”, argv[1] = “0x30007FC0”

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{ulong   iflag;ulong   addr;ulong   data, len, checksum;ulong  *len_ptr;uint    unc_len = CFG_BOOTM_LEN;int i, verify;char    *name, *s;int (*appl)(int, char *[]);image_header_t *hdr = &header;// 检索环境变量"verify",若检索成功则verify = 0,否则为1s = getenv ("verify");verify = (s && (*s == 'n')) ? 0 : 1;// 若参数个数小于2,表明没有传入内核镜像加载地址,则使用默认的加载地址load_addrif (argc < 2) {addr = load_addr;} else {addr = simple_strtoul(argv[1], NULL, 16);}SHOW_BOOT_PROGRESS (1);printf ("## Booting image at %08lx ...\n", addr);// 拷贝镜像文件的文件头到header。memmove (&header, (char *)addr, sizeof(image_header_t));// check the Image Magic Numberif (ntohl(hdr->ih_magic) != IH_MAGIC) {{puts ("Bad Magic Number\n");SHOW_BOOT_PROGRESS (-1);return 1;}}SHOW_BOOT_PROGRESS (2);// 将header的地址值赋给data,长度赋给lendata = (ulong)&header;len  = sizeof(image_header_t);// 读取镜像文件头hcrc的值checksum = ntohl(hdr->ih_hcrc);hdr->ih_hcrc = 0;// 对镜像文件头做CRC校验。if (crc32 (0, (uchar *)data, len) != checksum) {puts ("Bad Header Checksum\n");SHOW_BOOT_PROGRESS (-2);return 1;}SHOW_BOOT_PROGRESS (3);/* for multi-file images we need the data part, too */// 显示镜像文件头信息。print_image_hdr ((image_header_t *)addr);// 计算内核的入口地址值,赋给data,内核的大小赋给lendata = addr + sizeof(image_header_t);len  = ntohl(hdr->ih_size);// 如果需要,校验内核内容的CRC。if (verify) {puts ("   Verifying Checksum ... ");if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {printf ("Bad Data CRC\n");SHOW_BOOT_PROGRESS (-3);return 1;}puts ("OK\n");}SHOW_BOOT_PROGRESS (4);// len_ptr指向内核的入口地址。len_ptr = (ulong *)data;if (hdr->ih_arch != IH_CPU_ARM){printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);SHOW_BOOT_PROGRESS (-4);return 1;}SHOW_BOOT_PROGRESS (5);// 获取镜像文件的类型,若是内核则 name = "Kernel Image";switch (hdr->ih_type) {case IH_TYPE_STANDALONE:name = "Standalone Application";/* A second argument overwrites the load address */if (argc > 2) {hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));}break;case IH_TYPE_KERNEL:name = "Kernel Image";break;case IH_TYPE_MULTI:name = "Multi-File Image";len  = ntohl(len_ptr[0]);/* OS kernel is always the first image */data += 8; /* kernel_len + terminator */for (i=1; len_ptr[i]; ++i)data += 4;break;default: printf ("Wrong Image Type for %s command\n", cmdtp->name);SHOW_BOOT_PROGRESS (-5);return 1;}SHOW_BOOT_PROGRESS (6);/** 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();// 判断镜像文件是否为压缩文件,若为压缩文件,解压缩。switch (hdr->ih_comp) {case IH_COMP_NONE:if(ntohl(hdr->ih_load) == data) {printf ("   XIP %s ... ", name);} else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)size_t l = len;void *to = (void *)ntohl(hdr->ih_load);void *from = (void *)data;printf ("   Loading %s ... ", name);while (l > 0) {size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;WATCHDOG_RESET();memmove (to, from, tail);to += tail;from += tail;l -= tail;}
#else   /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif  /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */}break;case IH_COMP_GZIP:printf ("   Uncompressing %s ... ", name);if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,(uchar *)data, &len) != 0) {puts ("GUNZIP ERROR - must RESET board to recover\n");SHOW_BOOT_PROGRESS (-6);do_reset (cmdtp, flag, argc, argv);}break;
#ifdef CONFIG_BZIP2case IH_COMP_BZIP2:printf ("   Uncompressing %s ... ", name);/** If we've got less than 4 MB of malloc() space,* use slower decompression algorithm which requires* at most 2300 KB of memory.*/i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),&unc_len, (char *)data, len,CFG_MALLOC_LEN < (4096 * 1024), 0);if (i != BZ_OK) {printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);SHOW_BOOT_PROGRESS (-6);udelay(100000);do_reset (cmdtp, flag, argc, argv);}break;
#endif /* CONFIG_BZIP2 */default:if (iflag)enable_interrupts();printf ("Unimplemented compression type %d\n", hdr->ih_comp);SHOW_BOOT_PROGRESS (-7);return 1;}puts ("OK\n");SHOW_BOOT_PROGRESS (7);// 对于内核镜像文件,在这里直接跳到下一步操作。switch (hdr->ih_type) {case IH_TYPE_STANDALONE:if (iflag)enable_interrupts();/* load (and uncompress), but don't start if "autostart"* is set to "no"*/if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {char buf[32];sprintf(buf, "%lX", len);setenv("filesize", buf);return 0;}appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);(*appl)(argc-1, &argv[1]);return 0;case IH_TYPE_KERNEL:case IH_TYPE_MULTI:/* handled below */break;default:if (iflag)enable_interrupts();printf ("Can't boot image type %d\n", hdr->ih_type);SHOW_BOOT_PROGRESS (-8);return 1;}SHOW_BOOT_PROGRESS (8);// 到了这里说明镜像文件是内核文件,判断是什么类型的内核,然后调用相应的启动函数,这里是Linux OS,所以调用do_bootm_linuxswitch (hdr->ih_os) {default:            /* handled by (original) Linux case */case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLEfixup_silent_linux();
#endif// 若bootm传入的命令参数为"bootm 0x30007FC0",则// cmdtp = store the address where the cmd_bootm struct table.// flag = 0// argc = 2// argv[0] = "bootm"// argv[1] = "0x30007FC0"// addr = 0x30007FC0// len_ptr = 0x30008000// verify = 1do_bootm_linux  (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;case IH_OS_NETBSD:do_bootm_netbsd (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;#ifdef CONFIG_LYNXKDIcase IH_OS_LYNXOS:do_bootm_lynxkdi (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;
#endifcase IH_OS_RTEMS:do_bootm_rtems (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;#if (CONFIG_COMMANDS & CFG_CMD_ELF)case IH_OS_VXWORKS:do_bootm_vxworks (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;case IH_OS_QNX:do_bootm_qnxelf (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;
#endif /* CFG_CMD_ELF */
#ifdef CONFIG_ARTOScase IH_OS_ARTOS:do_bootm_artos  (cmdtp, flag, argc, argv,addr, len_ptr, verify);break;
#endif}SHOW_BOOT_PROGRESS (-9);
#ifdef DEBUGputs ("\n## Control returned to monitor - resetting...\n");do_reset (cmdtp, flag, argc, argv);
#endifreturn 1;
}

二、达成终极目标——启动内核

在确认镜像文件无误,拷贝内核到调用入口地址处(如果需要的话)后,调用do_bootm_linux(),设置u-boot传给内核的参数并为启动内核做一些初始化,包括关闭中断,关闭MMU,关闭数据cache,设置CPU为SVC模式,设置R0~R2寄存器的值,最终跳到内核入口地址调用内核。
由于u-boot和内核的交互是单向的,传递参数的办法只有一个:u-boot将参数放在某个约定的地方之后,再启动内核,内核启动后从这个地方获得参数。


// 若bootm传入的命令参数为"bootm 0x30007FC0",则
// cmdtp = store the address where the cmd_bootm struct table.
// flag = 0
// argc = 2
// argv[0] = "bootm"
// argv[1] = "0x30007FC0"
// addr = 0x30007FC0
// len_ptr = 0x30008000
// verify = 1
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify)
{ulong len = 0, checksum;ulong initrd_start, initrd_end;ulong data;void (*theKernel)(int zero, int arch, uint params);image_header_t *hdr = &header;bd_t *bd = gd->bd;#ifdef CONFIG_CMDLINE_TAG// 获取OS的启动参数: 若 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0// 则commandline = "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"char *commandline = getenv ("bootargs");
#endif// theKernel指向内核的入口地址theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);/** no initrd image*/ SHOW_BOOT_PROGRESS (14);len = data = 0;#ifdef  DEBUGif (!data) {printf ("No initrd\n");}
#endifif (data) {initrd_start = data;initrd_end = initrd_start + len;} else {initrd_start = 0;initrd_end = 0;} SHOW_BOOT_PROGRESS (15);debug ("## Transferring control to Linux (at address %08lx) ...\n",(ulong) theKernel);// 设置传递给内核的参数 TLV格式
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \defined (CONFIG_CMDLINE_TAG) || \defined (CONFIG_INITRD_TAG) || \defined (CONFIG_SERIAL_TAG) || \defined (CONFIG_REVISION_TAG) || \defined (CONFIG_LCD) || \defined (CONFIG_VFD)setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAGsetup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAGsetup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGSsetup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAGsetup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAGif (initrd_start && initrd_end)setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)setup_videolfb_tag ((gd_t *) gd);
#endifsetup_end_tag (bd);
#endif/* we assume that the kernel is in place */printf ("\nStarting kernel ...\n\n");#ifdef CONFIG_USB_DEVICE{extern void udc_disconnect (void);//udc_disconnect (); // cancled by www.100ask.net}
#endif// 在调用内核之前,做一些必要的初始化。cleanup_before_linux ();// 通过入参,设置CPU寄存器// R0 = 0// R1 = 机器类型ID// R2 = 启动参数标记列表在RAM中起始基地址theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
}

// 设置起始TAG参数,参数列表的其实地址为 bd->bi_boot_params
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);
}

// 关中断,关流水线,清cache
int cleanup_before_linux (void)
{/** this function is called just before we call linux* it prepares the processor for linux** we turn off caches etc ...*/unsigned long i;disable_interrupts ();/* turn off I/D-cache */asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));i &= ~(C1_DC | C1_IC);asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));/* flush I/D-cache */i = 0;asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));return (0);
}

这篇关于U-BOOT启动kernel的过程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1114702

相关文章

JAVA系统中Spring Boot应用程序的配置文件application.yml使用详解

《JAVA系统中SpringBoot应用程序的配置文件application.yml使用详解》:本文主要介绍JAVA系统中SpringBoot应用程序的配置文件application.yml的... 目录文件路径文件内容解释1. Server 配置2. Spring 配置3. Logging 配置4. Ma

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为

mysql-8.0.30压缩包版安装和配置MySQL环境过程

《mysql-8.0.30压缩包版安装和配置MySQL环境过程》该文章介绍了如何在Windows系统中下载、安装和配置MySQL数据库,包括下载地址、解压文件、创建和配置my.ini文件、设置环境变量... 目录压缩包安装配置下载配置环境变量下载和初始化总结压缩包安装配置下载下载地址:https://d

SpringBoot项目启动后自动加载系统配置的多种实现方式

《SpringBoot项目启动后自动加载系统配置的多种实现方式》:本文主要介绍SpringBoot项目启动后自动加载系统配置的多种实现方式,并通过代码示例讲解的非常详细,对大家的学习或工作有一定的... 目录1. 使用 CommandLineRunner实现方式:2. 使用 ApplicationRunne

springboot整合gateway的详细过程

《springboot整合gateway的详细过程》本文介绍了如何配置和使用SpringCloudGateway构建一个API网关,通过实例代码介绍了springboot整合gateway的过程,需要... 目录1. 添加依赖2. 配置网关路由3. 启用Eureka客户端(可选)4. 创建主应用类5. 自定

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b

SpringBoot集成SOL链的详细过程

《SpringBoot集成SOL链的详细过程》Solanaj是一个用于与Solana区块链交互的Java库,它为Java开发者提供了一套功能丰富的API,使得在Java环境中可以轻松构建与Solana... 目录一、什么是solanaj?二、Pom依赖三、主要类3.1 RpcClient3.2 Public

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

Jenkins中自动化部署Spring Boot项目的全过程

《Jenkins中自动化部署SpringBoot项目的全过程》:本文主要介绍如何使用Jenkins从Git仓库拉取SpringBoot项目并进行自动化部署,通过配置Jenkins任务,实现项目的... 目录准备工作启动 Jenkins配置 Jenkins创建及配置任务源码管理构建触发器构建构建后操作构建任务