uboot如何启动内核

uboot和内核是什么

uboot就是一个裸机程序
  • uboot本质就是一个复杂的裸机程序
内核本质也是一个“裸机程序”
  1. 操作系统内核本质上与uboot和其他裸机程序没什么不同。
  2. 区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内存
    内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)
  • 直观来看:uboot的镜像是u-boot.bin,Linux系统的镜像是zImage,这两个东西都是其实都是裸机程序镜像。从系统的启动角度讲,内核其实就是一个大的复杂点的裸机程序。
部署在SD卡中特定分区内
  1. 一个完整的软件+硬件的嵌入式系统,静止时bootloader、kernel、rootfs等必须的软件都以镜像的形式存储在启动介质中(x210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储系统无关。上面两个状态都是稳定状态。第3个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。
  2. 动态启动过程其实就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件架构的建立,最终达到运行时稳定状态。
  3. 静止时u-boot.bin zImage rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此需要对SD卡进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot、内核就知道到哪里去找谁。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致)
内核启动需要必要的启动参数
  1. uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。
  2. 内核也有类似要求,uboot启动时内核将内存从SD卡读取到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址是0x30008000
内核启动需要必要的参数
  1. uboot是无条件启动的,从零开始启动的。
  2. 内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。
启动内核第一步:加载内核到DDR中链接地址处
  • uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码根本不考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)
静态内核镜像在哪里?
  1. SD卡/iNand/Nand/NorFlash等:raw分区
    • 常规启动时各种镜像都在SD卡中,因此uboot只需从SD卡的kernel分区去读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如x210的iNand版本是movi命令,x210的Nand版本是Nand命令)
  2. 这种启动方式来加载DDR,使用命令movi read kernel 30008000。 其中kernel指的是uboot中的kernel分区(uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel)
  3. tftp、nfs等网络下载方式从远端服务器获取镜像
    • uboot还支持远程启动。内核镜像烧录到主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。
  • 总结:最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出

镜像要放在DDR的什么地址?
  • 内核一定要放在链接地址处,链接地址去内核源代码的链接脚本或者Makefile中去查找。X210中是0x30008000。

zImage和uImage的区别联系

bootm命令对应do_bootm函数
  1. 命令名前加do_即可构成这个命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数交do_bootm函数,在cmd_bootm.c。
  2. do_bootm刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证),然后进行了细节部分的操作。然后到了CONFIG_ZIMAGE_BOOT,然后用这个宏来控制进行条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。
vmlinuz和zImage和uImage
  1. uboot经过编译直接生成的elf格式的可执行程序是u-boot,这个程序类似于windows下的exe格式,在操作系统下是可以直接执行的。但是这种格式不能用来烧录下载。我们用来烧录下载的是u-boot.bin,这个东西是由u-boot使用arm-linux-objcopy工具加工(去掉一些无用的)得到的。这个u-boot.bin就叫镜像(image),镜像就是用来烧录到iNand种执行的。
  2. Linux内核经过编译后也会生成一个elf格式的可执行文件,叫vmlinux或vmlinuz,这个就是原始的未经任何处理加工的原版内核elf文件;嵌入式系统部署时烧录的一般不是这个vmlinux或vmlinuz,而要用objcopy工具制作成烧录镜像格式(就是uboot.bin这种,但是内核没有.bin后缀),经过制作加工成烧录镜像的文件就叫Image(制作把78M大的精简成了7.5M,因此这个制作烧录镜像的主要目的就是缩减大小,节省磁盘)
  3. 原则上Image就可以直接被烧录到Flash上进行启动执行(类似于u-boot.bin),但是实际上并不是这么简单。实际上Linux的作者们觉得Image还是太大了所以对Image进行了压缩,并且在Image压缩后的文件的前端附加了一部分解压缩代码,构成了一个压缩格式的镜像就叫zImage。
  4. uboot为了启动Linux内核,还发明了一种内核格式叫uImage,uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生成uImage。注意:uImage不管在Image的事,Linux内核只管生成uImage来给uboot启动。这个加工过程其实就是在zImage前面加上64字节的uImage的头信息即可。
  5. 原则上uboot启动时应该给它uImage格式的内核镜像,但是实际上uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。所以:有的uboot支持zImage启动,有些不支持。但是所有都支持uImage。
编译内核得到uImage去启动
  • 去uboot/tools下拷贝mkimage到/usr/local/bin即可编译

zImage启动细节
  1. do_bootm函数中一直到397行的after_header_check都是在进行镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型。
LINUX_ZIMAGE_MAGIC
  1. 这是一个定义的魔数,等于0x016f2818,表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个固定位置存放了这个数作为格式标记。如果我们拿到了一个image,去他的那个位置去取4字节判断它是否等于LINUX_ZIMAGE_MAGIC,则可以知道这个镜像是不是一个zImage。
  2. 命令 bootm 0x30008000,所以do_bootm的argc=2,argv[0]=bootm,ragv[1]=0x30008000。实际上bootm还可以不带参数执行,则会从CFG_LOAD_ADDR地址去执行(定义在x210_sd.h中)
  3. zImage头部开始的第37-40字节处存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。可以用二进制阅读软件来打开zImage查看,就可以证明。能打开二进制的软件:winhex、UltraEditor
image_header_t
  1. 这个数据结构是uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。

     hdr->ih_os = IH_OS_LINUX;
     hdr->ih_ep = ntohl(addr);	这两句话就是在进行改造。
    
  2. images全局变量是do_bootm函数中使用的,用过来完成启动过程的。zImage的校验过程就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验。

uImage启动
  1. LEGACY(遗留的),在do_bootm函数中,这种方式指的就是uImage的方式
  2. uImage方式是uboot本身发明的支持Linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式(在do_bootm方式中叫FIT)
  3. uImage的启动校验主要在boot_get_kernel
设备树方式内核启动
  • 设备树启动方式暂时不讲,课程结束后补充。
总结:
  1. uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了FDT方式之后,就把uImage方式命名为LEGACY方式,FDT方式命名为FIT方式,于是多了些#if#endif添加的代码。后来移植的人为了省事添加了uImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是又有了一对#if#endif,于是乎整体代码看起来很恶心。
  2. 第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动Linux内核,调用do_bootm_linux函数来完成。

do_bootm_linux
  1. do_bootm_linux函数在uboot/lib_arm/bootm.c中
  2. SI找不到(是黑色的)不代表就没有。要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在SI工程中添加文件是,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等Makefile不识别的文件是没有被添加的。要搜索的关键字在Makefile中或脚本中,SI就搜不到。
镜像的entrypoint
  1. ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相当于头是由一定的偏移量的。这个偏移量记录在头信息中。
  2. 一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;第二步对镜像进行校验;第三步再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。
  3. theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。
机器码的再次确定
  • uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码时怎么确定的?第一顺序备选时环境变量machid,第二顺序备选是gd->bd->bi_arch_num(x210sd.h种硬编码配置的)
传参并启动概述
  1. 从110-144行就是uboot在给Linux内核准备传递的参数处理。
  2. Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明整个uboot是成功的,也成功加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句串口后没输出了,说明内核并没有被成功执行。原因一般是:传参(80%)、内核在DDR中的加载地址·····

传参详解

tag传参方式
  1. struct tag ,tag是一个数据结构,在uboot和Linux kernel 中都有tag数据机制,而且定义还是一样的。
  2. tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当做一个tag_xx来处理。
  3. tag_start和tag_end。kernel接受到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
  4. tag传参方式是由Linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以给kernel传参。
x210_sd.h中配置传参宏
CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存信息
CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs。
CONFIG_INITRD_TAG
CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
  • 起始tag是ATAG_CORE、结算tag是ATAG_NONE,其他的ATAG_xxx都是有效信息tag。
思考:内核如何拿到这些tag?
  • uboot最终是调用theKernel函数来执行了Linux内核的,uboot调用这个函数(其实就是Linux内核)时传递了3个参数。这3个参数就是uboot直接传递给Linux内核的3个参数,通过寄存器来实现传参的。(第1个参数就放在r0中,第二个参数放在r1中,第3个参数放在r2中)第一个参数固定为0,第二个参数是机器码,第三个参数传递的就是大片传参tag的首地址。
移植注意事项:
  1. uboot移植时一般只需要配置相应的宏即可。
  2. kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。

uboot启动内核的总结:

启动4步骤:
  1. 将内核搬移到DDR中
  2. 校验内核格式、CRC等
  3. 传参
  4. 跳转执行内核
涉及到的主要函数是:do_bootm和do_bootm_linux
uboot能启动的内核格式:zImage uImage fdt方式
跳转与函数指针的方式运行内核
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值