uboot学习之五-----uboot如何启动Linux内核
uboot和内核到底是什么?
uboot实质就是一个复杂的裸机程序;uboot可以被配置也可以做移植;
操作系统内核本身就是一个裸机程序,和我们学的uboot和其他裸机程序没有本质的区别;区别就是我们操作系统运行起来后可以分为应用层和内核层,分层后,两层的权限不同,内存访问和设备操作的管理上更加精细(内核可以随便方位各种硬件,而应用程序只能被限制的访问硬件和内存地址)
直观来看:uboot的镜像是u-boot.bin,Linux系统的镜像是zImage,这两个东西其实都是两个裸机程序镜像。从系统启动的角度来讲的。内核和uboot就是裸机程序;
部署在SD卡中特定分区内
(1)一个完整的软件+硬件的嵌入式系统,静止时(未上电)bootloader、kernel、rootfs等必须的软件都以镜像的形式存储在启动介质中(x210中是inand/SD卡);运行时都是在DDR内存中运行的,与存储介质无关。上面两个状态都是稳定状态的,第三个状态是动态过程,即从静止态到运行态的过程,也就是启动过程。也就是这整个过程。
(2)动态启动过程就是从SD卡逐步搬移到DDR内存,并且运行启动代码进行相关硬件初始化和软件架构的建立,最终达到运行时稳定状态。
(3)静止时u-boot.bin
zImage
rootfs都在SD卡中,他们不可能随意存在SD卡的任意位置,因此我们需要对SD卡进行一个分区,然后将各种镜像各自存在各自的分区中,这样在启动过程中,uboot、内核等就知道到哪里去找谁。(uboot和内核的分区表必须一致,同时和SD卡的实际使用的分区要一致)
运行时必须先加载到DDR中链接地址处
uboot在第一阶段中进行重定位时将第二阶段(整个uboot镜像)加载到DDR中的0xc3e00000地址处,这个地址就是uboot的连接地址。
(2)内核也有类似的要求,uboot启动内核时将内核从SD卡读取到DDR中(其实就是重定位过程),不能随意放,必须放在内核的链接地址处,否则启动不起来,譬如我们用的内核连接地址是0x300080000
内核启动需要必要的启动参数
(1)内核是不能开机自动完全从零开始启动的,内核启动需要别人帮忙。uboot要帮助内核实现重定位(从SD卡到DDR )uboot还要给内核提供启动参数。
启动内核第一步:加载内核到DDR中
uboot要启动内核,分为2个步骤:第一步是将内核镜像从启动介质中加载到DDR中,第二步是去DDR中启动内核镜像。(内核代码根本就没考虑重定位,因为内核知道有bootloader帮忙把自己加载到DDR中链接地址处,内核就直接从链接地址处运行的)
静态内核镜像在哪里?
(1)SD卡、inand、nand、norflash等:raw区
常规启动时各种镜像都在SD卡中,因此uboot只需要从启动介质SD卡的内核分区去读取内核镜像到DDR的链接地址处,读取要使用uboot命令来读取(例如inand版本是movi命令,x210的nand版本就是nand命令)
(2)这种启动方式来加载ddr,使用命令:movi
read kernel 30008000.
其中kernel指的是uboot中的kernel分区(就是uboot规定SD卡中的一个区域范围,这个区域范围被设计来存放kernel镜像,就是所谓的kernel分区,有时候也叫原始分区,操作系统启动后可以用文件系统来管理分区。)
(3)tftp、nfs等网络下载方式从远端服务器获取镜像
uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中,而是放在主机的服务器中,然后需要启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。
分析总结:最终结果是内核镜像到DDR中的特定地址即可。以上两种方式各有优劣,产品在出厂时会设置从SD卡启动,客户不会还有去搭建tftp服务器才能使用,tftp下载远程启动,适合开发。
镜像要放在DDR的什么地址?
内核一定要放在链接地址处,链接地址去内核源代码的连接脚本或者makefile中去查找。x210中是0x300080000。
zImage和uImage的区别联系
bootm命令对应do_boot函数
(1)命令名前加do_,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就叫镜像,镜像就是用来烧录的,烧录到inand中执行,或者是放在SD卡中执行,或者dnw下载执行,
(2)Linux
kernel经过编译后也会生成一个ELF格式的可执行程序,叫vmlinuz或者vmlinux,这个就是原始的未经任何处理加工的原版内核ELF文件,嵌入式部署烧录的一般不是这个vmlinuz,而是使用objcpy工具去制作成烧录镜像格式(就是u-boot.bin这种,但是内核没有.bin后缀)制作的这个烧录镜像是Image,制作镜像主要目的是缩减大小,节俭磁盘。
(3)原则上Image就可以直接被烧录到Flash,但实际上并不是这么简单,实际上Linux的作者们觉得Image太大了,对其进行了压缩,并且在压缩后的前一段部分,附加了一部分解压缩代码,构成了一个压缩可是的镜像就是zImage(因为当年Image大小刚刚比一张软盘大,(软盘有2中,1.2M和1.4M,Image比1.4M大一点),为了节省一张软盘的钱,于是乎,设计了这种压缩Image成zImage的技术)
(4)uboot为了启动Linux内核,还发明了一种内核格式叫uImage。uImage是由zImage加工得到的,uboot中有一个工具,可以将zImage加工生产uImage。注意:uImage不管Linux内核的事,Linux内核只管生成zImage即可,然后uboot中的mkimage工具再去有zImage加工生成uImage来给uboot启动。这个加工的过程是在zImage前面加上64字节的uImage的头信息即可。
(5)原则上uboot启动时应该给他uImage格式的内核镜像,但是实际上uboot也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAG_MAGIC这个宏。所以有些uboot支持zImage启动,有些则不支持。但是所有的uboot肯定都支持uImage启动。
编译内核得到uImage去启动
(1)第一步:如果直接在kernel底下去make uImage会提示mkimage没找到,解决方案是在 /uboot/tool下去复制到 /usr/local/bin.下面去,复制它到系统目录下,再去执行make uImage就可以了
zImage启动细节
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC 0x016f2818
/* find out kernel image address */
if (argc < 2) {
addr = load_addr;
debug ("* kernel: default image load address = 0x%08lx\n",
load_addr);
} else {
addr = simple_strtoul(argv[1], NULL, 16);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
}
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
printf("Boot with zImage\n");
addr = virt_to_phys(addr);
hdr = (image_header_t *)addr;
hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
/* save pointer to image header */
images.legacy_hdr_os = hdr;
images.legacy_hdr_valid = 1;
goto after_header_check;
}
#endif
do_bootm函数一直到397行之前都是进行zImage镜像的头部信息校验。校验时就要根据不同种类的image类型进行不同的校验。所以do_bootm函数的核心就是去分辨传进来的image到底是什么类型,然后按照这种类型的头信息格式去校验。校验通过则进入下一步准备启动内核;若果校验失败则认为镜像有问题,所以不能启动。
#define LINUX_ZIMAGE_MAGIC 0x016f2818
(1)这是一个魔数,0x016f2818表示这个镜像是zImage,也就是说zImage格式的镜像中,在头部的一个固定位置存放了这个数,作为格式标记,如果我们拿到了一个image,去他的那个位置去取4个字节,判断它是否等于这个魔数LINUX_ZIMAGE_MAGIC。则可以知道这个镜像是不是zImage
(2)命令bootm
0x30008000,所以do_bootm的argc=2,argv[0]=bootm argv[1]=0x30008000
但是实际bootm命令还可以不带参数执行,如果不带参数直接bootm则会从,CFG_LOAD_ADDR地址去执行(定义在x210_sd.h中)
(3)zImage从头部开始的第37到40个字节,存的是zImage的标志的魔数,从这个位置取出对比LINUX_ZIMAGE_MAGIC,我们可以用二进制查看工具来查看zImage的镜像,发现真的是存放的这个魔数。
image_header_t
(1)这个数据结构是我们uboot启动内核使用的一个标准启动数据结构,zImage头信息也是一个image_header_t,但是在实际启动之前需要进行一些改造,
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
printf("Boot with zImage\n");
addr = virt_to_phys(addr);
hdr = (image_header_t *)addr;
hdr->ih_os = IH_OS_LINUX;
hdr->ih_ep = ntohl(addr);
memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
/* save pointer to image header */
images.legacy_hdr_os = hdr;
images.legacy_hdr_valid = 1;
这里就是在进行改造。
(2)image全局变量是在bootm函数中使用,目的是用来指向
os/initrd/fdt
images,也就是用来完成启动的,zImage校验过程先确定是不是zImage,然后再修改zImage头信息,到合适,也就是上面的改造,最后再用这个头信息去初始化image,然后完成了校验。
zImage启动方式是后来添加的,而且用了goto的方式跳转了一部分代码,本身对uboot的结构上添加的。
uImage启动
(1)在uboot启动的do_boot中有一个legacy的方法,指的就是uImage这样的方式,为什么是legacy(遗留的),是因为uImage本身是uboot发明的一种启动的方式,后来这种方式是不好的,被废弃,于是被一种新的方式给替代了,新的方式就是设备树的方式,在这里被叫做fit,这个就是设备树的方式。
(2)uImage启动校验函数是在boot_get_kernel这个函数里,主要任务就是校验我们的uImage的头信息,并且得到真正的kernel的起始位置去启动。
总结:uboot本身也只支持uImage方式启动的,后来有了设备树之后,就把uImage方式命名为legacy方式,fdt方式就命名为fit方式,于是乎多了#if
#endif添加代码。后来移植的人又为了省事添加了zImage的方式,又为了省事,添加了#if #endif .
第二阶段校验头信息结束,进行第三阶段,启动Linux内核,调用do_bootm_linux这个函数
do_bootm_linux
(1)找到do_bootm_linux,这个函数在lib_arm/bootm.c中,sourceinsight找不到,要搜索查找,
(2)镜像的entrypoint
ep就是程序入口,一个镜像的起始部分不是在镜像的开头(镜像的开头有很多字节的头信息),真正开始执行的代码在中间的某个部分,相对于头有一定偏移量的,这个偏移量放在头信息中的。
一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定种类,第二步对镜像进行校验,第三步再次读取头信息,由头信息的特定地址知道这个镜像的各种信息,包括长度,种类,入口地址等等,第四步就是去entrypoint处开始执行镜像,
theKernel = (void (*)(int, int, uint))ep;将真正的入口地址赋值给thekernel,就是操作系统的第一句代码
机器码的再次确定
uboot在启动内核时,机器码要传给内核。uboot传给内核的机器码是怎么确定的?第一顺序备选的是环境变量machid,第二顺序备选是gd->bd->bi_arch_number(x210_sd.h配置的)
传参并启动
给内核传参(do_bootm_linux这里的110-144行)
Starting kernel ...这里打印是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_xxx来处理。
(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
(4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。
x210_sd.h中配置传参宏
(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.
(3)CONFIG_INITRD_TAG
(4)CONFIG_MTDPARTITION,传参内容是iNand/SD卡的分区表。
(5)起始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中)第1个参数固定为0,第2个参数是机器码,第3个参数传递的就是大片传参tag的首地址。
2.7.7.3、移植时注意事项
(1)uboot移植时一般只需要配置相应的宏即可
(2)kernel启动不成功,注意传参是否成功。传参不成功首先看uboot中bootargs设置是否正确,其次看uboot是否开启了相应宏以支持传参。
https://www.cnblogs.com/yr-linux/p/5495734.html
uboot学习之五-----uboot如何启动Linux内核的更多相关文章
- Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核
Qemu搭建ARM vexpress开发环境(二)----通过u-boot启动Linux内核 标签(空格分隔): Qemu ARM Linux 在上文<Qemu搭建ARM vexpress开发环 ...
- Linux内核分析第三周学习博客——跟踪分析Linux内核的启动过程
Linux内核分析第三周学习博客--跟踪分析Linux内核的启动过程 实验过程截图: 过程分析: 在Linux内核的启动过程中,一共经历了start_kernel,rest_init,kernel_t ...
- uefi是如何启动linux内核的?
答:uefi启动linux内核有两条路径: 1. uefi直接进入uefi shell来启动linux内核 2. uefi直接进入uefi shell启动grub启动器,然后进入grub shell启 ...
- uboot学习之uboot启动流程简述
一.uboot启动分为了三个阶段BL0.BL1.BL2:BL0表示上电后运行ROM中固化的一段程序,其中ROM中的程序是厂家写进去的,所以具体功能可能根据厂家芯片而有所不同.功能如下: 初始化系统时钟 ...
- 八、启动linux内核并修改开机logo
1. 编译并烧写linux内核 1)先准备好内核源码包urbetter-linux2.6.28-v1.0.tgz,输入命令:tar -zxvf urbetter-linux2.6.28-v1.0.tg ...
- uboot学习之uboot.bin的运行流程
上篇博客:http://www.cnblogs.com/yeqluofwupheng/p/7347925.html 讲到uboot-spl的工作流程,接下来简述一下uboot.bin的工作流程,这对应 ...
- 通过Bochs分析Lilo启动Linux内核的过程
1. Bochs调试 参考:http://www.cnblogs.com/long123king/p/3414884.html http://bochs.sourceforge.net/cgi-bin ...
- 基于TFTP方式加载启动Linux内核
一.软硬件平台 1.开发板:创龙AM3359核心板,网口采用RMII形式. 2.UBOOT版本:U-Boot-2016.05,采用FDT和DM. 3.交换芯片MARVELL的88E63 ...
- 【mongodb系统学习之五】mongodb启动最常用参数
五.mongodb启动时其他常用参数的使用(都是选用): 1).--logappend,指定日志的写入方式为追加,强烈建议使用: 2).--port,指定mongodb的端口号,当不使用这个参数的时候 ...
随机推荐
- es之java各种查询操作
matchAllQuery 匹配所有文档 queryStringQuery 基于Lucene的字段检索 wildcardQuery 通配符查询匹配多个字符,?匹配1个字符* termQuery 词条查 ...
- Redis之Java客户端Jedis
导读 Redis不仅使用命令客户端来操作,而且可以使用程序客户端操作. 现在基本上主流的语言都有客户端支持,比如Java.C.C#.C++.php.Node.js.Go等. 在官方网站里列一些Java ...
- java的基本数据类型有
整型数据根据它所占内容大小的不同可分为4种类型. 数据类型 内存 byte 8位 short 16位 int 32位 long 64位 浮点类型 数据类型 内存 float 32位 double 64 ...
- 开源认证组件汇总 Kerberos和CAS
一.Kerberos 1.Kerberos原理和工作机制 概述:Kerberos的工作围绕着票据展开,票据类似于人的驾驶证,驾驶证标识了人的信息,以及其可以驾驶的车辆等级. 1.1 客户机初始验证 ...
- Unity各版本差异
Unity各版本差异 version unity 5.x 4.x 2017 差异 特点 首先放出unity的下载地址,然后再慢慢分析各个版本.再者unity可以多个版本共存,只要不放在同一目录下. ...
- /etc/init.d# ./redis-server start
root@ubuntu:/etc/init.d# ll total drwxr-xr-x root root May : ./ drwxr-xr-x root root May : ../ -rwxr ...
- c# 用DotNetZip来解压/压缩文件
//https://archive.codeplex.com/?p=dotnetzip //最新在Nuget 下载DotNetZip using Ionic.Zip; private void but ...
- django小知识
def __str__: return self.name 在显示的时候,将原来显示的额object对象,显示成这个类的名字
- Powershell 邮件发送
目录 目录 前言 Send-MailMessage NETMail 使用OutLook发送邮件 前言 最近领导想在winServer2012上搞个自动发送邮件的计划任务,下面有几种发送邮件的方式. 如 ...
- 阶段1 语言基础+高级_1-2 -面向对象和封装_15练习使用private关键字定义
练习使用private关键字定义一个学生类.通过这个联系说明一种特殊情况 先定义了name个age分别再定义getter和setter的方法 boolean类型的getter方法不能叫做get开头的. ...