首页 > 其他分享 >uboot如何启动内核

uboot如何启动内核

时间:2022-08-21 12:46:50浏览次数:57  
标签:uboot 启动 tag 内核 zImage 镜像

uboot和内核是什么


uboot就是一个裸机程序
uboot本质就是一个复杂的裸机程序


内核本质也是一个“裸机程序”
操作系统内核本质上与uboot和其他裸机程序没什么不同。
区别就是操作系统运行起来后在软件上分为内核层和应用层,分层后两层的权限不同,内存
内存访问和设备操作的管理上更加精细(内核可以随便访问各种硬件,而应用程序只能被限制的访问硬件和内存地址)
直观来看:uboot的镜像是u-boot.bin,Linux系统的镜像是zImage,这两个东西都是其实都是裸机程序镜像。从系统的启动角度讲,内核其实就是一个大的复杂点的裸机程序。


部署在SD卡中特定分区内
一个完整的软件+硬件的嵌入式系统,静止时bootloader、kernel、rootfs等必须的软件都以镜像的形式存储在启动介质中(x210中是iNand/SD卡);运行时都是在DDR内存中运行的,与存储系统无关。上面两个状态都是稳定状态。第3个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。
动态启动过程其实就是一个从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关的硬件初始化和软件架构的建立,最终达到运行时稳定状态。
静止时u-boot.bin zImage rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此需要对SD卡进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中uboot、内核就知道到哪里去找谁。(uboot和kernel中的分区表必须一致,同时和SD卡的实际使用的分区要一致)


内核启动需要必要的启动参数
uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处,这个地址就是uboot的链接地址。
内核也有类似要求,uboot启动时内核将内存从SD卡读取到DDR中(其实就是个重定位的过程),不能随意放置,必须放在内核的链接地址处,否则启动不起来。譬如我们使用的内核链接地址是0x30008000


内核启动需要必要的参数
uboot是无条件启动的,从零开始启动的。
内核是不能开机自动完全从零开始启动的,内核启动要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR),uboot还要给内核提供启动参数。


启动内核第一步:加载内核到DDR中链接地址处
uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码根本不考虑重定位,因为内核知道会有uboot之类的把自己加载到DDR中链接地址处的,所以内核直接就是从链接地址处开始运行的)


静态内核镜像在哪里?
SD卡/iNand/Nand/NorFlash等:raw分区
常规启动时各种镜像都在SD卡中,因此uboot只需从SD卡的kernel分区去读取内核镜像到DDR中即可。读取要使用uboot的命令来读取(譬如x210的iNand版本是movi命令,x210的Nand版本是Nand命令)
这种启动方式来加载DDR,使用命令movi read kernel 30008000。 其中kernel指的是uboot中的kernel分区(uboot中规定的SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel)
tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动。内核镜像烧录到主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。
总结:最终结果要的是内核镜像到DDR中特定地址即可,不管内核镜像是怎么到DDR中的。以上2种方式各有优劣。产品出

 


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


zImage和uImage的区别联系
bootm命令对应do_bootm函数
命令名前加do_即可构成这个命令对应的函数,因此当我们bootm命令执行时,uboot实际执行的函数交do_bootm函数,在cmd_bootm.c。
do_bootm刚开始定义了一些变量,然后用宏来条件编译执行了secureboot的一些代码(主要进行签名认证),然后进行了细节部分的操作。然后到了CONFIG_ZIMAGE_BOOT,然后用这个宏来控制进行条件编译一段代码,这段代码是用来支持zImage格式的内核启动的。


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


编译内核得到uImage去启动
去uboot/tools下拷贝mkimage到/usr/local/bin即可编译


zImage启动细节
do_bootm函数中一直到397行的after_header_check都是在进行镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型。


LINUX_ZIMAGE_MAGIC
这是一个定义的魔数,等于0x016f2818,表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个固定位置存放了这个数作为格式标记。如果我们拿到了一个image,去他的那个位置去取4字节判断它是否等于LINUX_ZIMAGE_MAGIC,则可以知道这个镜像是不是一个zImage。
命令 bootm 0x30008000,所以do_bootm的argc=2,argv[0]=bootm,ragv[1]=0x30008000。实际上bootm还可以不带参数执行,则会从CFG_LOAD_ADDR地址去执行(定义在x210_sd.h中)
zImage头部开始的第37-40字节处存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。可以用二进制阅读软件来打开zImage查看,就可以证明。能打开二进制的软件:winhex、UltraEditor


image_header_t
这个数据结构是uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造。

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

 

uImage启动
LEGACY(遗留的),在do_bootm函数中,这种方式指的就是uImage的方式
uImage方式是uboot本身发明的支持Linux启动的镜像格式,但是后来这种方式被一种新的方式替代,这个新的方式就是设备树方式(在do_bootm方式中叫FIT)
uImage的启动校验主要在boot_get_kernel


设备树方式内核启动
设备树启动方式暂时不讲,课程结束后补充。


总结:
uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了FDT方式之后,就把uImage方式命名为LEGACY方式,FDT方式命名为FIT方式,于是多了些#if#endif添加的代码。后来移植的人为了省事添加了uImage启动的方式,又为了省事把zImage启动方式直接写在了uImage和fdt启动方式之前,于是又有了一对#if#endif,于是乎整体代码看起来很恶心。
第二阶段校验头信息结束,下面进入第三阶段,第三阶段主要任务是启动Linux内核,调用do_bootm_linux函数来完成。


do_bootm_linux
do_bootm_linux函数在uboot/lib_arm/bootm.c中
SI找不到(是黑色的)不代表就没有。要搜索一下才能确定;搜索不到也不能代表就没有,因为我们在SI工程中添加文件是,SI只会添加它能识别的文件格式的文件,有一些像Makefile、xx.conf等Makefile不识别的文件是没有被添加的。要搜索的关键字在Makefile中或脚本中,SI就搜不到。


镜像的entrypoint
ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相当于头是由一定的偏移量的。这个偏移量记录在头信息中。
一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;第二步对镜像进行校验;第三步再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。
theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。


机器码的再次确定
uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码时怎么确定的?第一顺序备选时环境变量machid,第二顺序备选是gd->bd->bi_arch_num(x210sd.h种硬编码配置的)


传参并启动概述
从110-144行就是uboot在给Linux内核准备传递的参数处理。
Starting kernel … 这个是uboot中最后一句打印出来的东西。这句如果能出现,说明整个uboot是成功的,也成功加载了内核镜像,也校验通过了,也找到入口地址了,也试图去执行了。如果这句串口后没输出了,说明内核并没有被成功执行。原因一般是:传参(80%)、内核在DDR中的加载地址·····

 


传参详解
tag传参方式
struct tag ,tag是一个数据结构,在uboot和Linux kernel 中都有tag数据机制,而且定义还是一样的。
tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当做一个tag_xx来处理。
tag_start和tag_end。kernel接受到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
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卡的分区表。
1
2
3
4


起始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的首地址。


移植注意事项:
uboot移植时一般只需要配置相应的宏即可。
kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。


uboot启动内核的总结:
启动4步骤:
将内核搬移到DDR中
校验内核格式、CRC等
传参
跳转执行内核


涉及到的主要函数是:do_bootm和do_bootm_linux
uboot能启动的内核格式:zImage uImage fdt方式
跳转与函数指针的方式运行内核
————————————————
版权声明:本文为CSDN博主「calm~down」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41719582/article/details/108403473

标签:uboot,启动,tag,内核,zImage,镜像
From: https://www.cnblogs.com/liwei-swjtu/p/16609794.html

相关文章

  • Linux 内核对交换芯片上送的协议报文的处理
          1.应用场景       对于数据报文,交换芯片完成硬件转发,即转发层面,无须cpu的参与。       对于协议报文,例如L2的EAPS环网检......
  • 一,Docker安装与启动
       Docker官方建议在Ubuntu中安装,因为Docker是基于Ubuntu发布的,而且一般Docker出现的问题Ubuntu是最先更新或者打补丁的。在很多版本的CentOS中是不支持更新最新的一......
  • 内核态和用户态
    内核态也叫内核空间,是内核进程/线程所在的区域。主要负责运行系统、硬件交互内核态运行的代码不受任何限制,CPU可以执行任何指令。 用户态也叫用户空间,是用户进程/线......
  • Linux驱动开发十四.使用内核自带的LED驱动
    回顾一下我们现在先后都做了几种LED的点亮试验:裸机点亮LED使用汇编语言读写寄存器点亮LED使用C语言读写寄存器点亮LED在系统下直接操作寄存器映射点亮LED在设备树下......
  • 使用二进制重排 & Clang插桩技术点来进行iOS冷启动进行优化
    1.冷启动1.1什么是冷启动?冷启动是指内存中不包含该应用程序相关的数据,必须要从磁盘载入到内存中的启动过程。注意:重新打开APP,不一定就是冷启动。当内存不足,APP被系......
  • 用户态读取内核中断的方式
    1.内核态中实现poll接口使用poll_wait添加到队列中,用户态使用poll获取状态2.fasync异步信号,在内核中中断回调函数中实现fasync接口,fasync中fasync_helper和kill_fa......
  • 使用docker简单编译k20pro内核
    简介本文将介绍一下如何使用docker编译红米k20pro的内核。作者当时尝试构建内核的原因是为了将3年前(好像是吧)购买的k20pro至尊版(已退役,12GB内存,512GB硬盘)制作成一个小的服......
  • 分析lvgl的代码启动过程,对比esp32,stm32,linux
    lvgl是gui层负责绘制gui并根据输入设备的事件来响应重绘,然后把绘制的缓冲区发送给显示驱动去实际显示。以下代码参考lvglarduino官方例程,guiguider模拟器例程,,零知stm3......
  • ufw开机不启动,ufw inactive after reboot
    废话:最近部署debian服务器,我用ufw把ssh端口打开了,重启服务器后,ssh又连接不上了。我心想ufw开机不自启动的吗?那我服务器每次重启后我都得把显示器接上手动打开22端口,这不是......
  • linux启动过程
    Linux系统启动过程科教 科教无界 2022-06-0807:16 发表于浙江收录于合集#科教152个#编程技能69个#Linux11个Linux启动时我们会看到许多启动信息。Linu......