uboot启动内核

1、操作系统运行起来后分为两内核层和应用层。两层的权限不同,内核可以直接访问硬件,而应用层对硬件的访问遭到了限制。

(uboot的镜像是u-boot.bin,Linux的镜像是zImage)

2、SD卡中的特定分区

在没有上电时,bootloader(引导区)、kernel(内核)、rootfs(文件系统)等必要的软件都以镜像的形式存储在SD中(存储介质),运行时都是在DDR中,与其他外存介质无关。以上两种状态可以认为稳定状态。而从静止状态到运行状态的过程(启动过程),可认为是动态过程。启动过程就是从SD卡中把代码搬移到DDR内存中运行,这些代码就是进行相关硬件的初始化和软件架构的建立,最终达到运行时的稳定状态。未上电时,uboot.bin、ZImage、rootfs必须在SD中的特定位置存放,也就是说需要对SD卡进行分区,然后将各种镜像存放在各自的分区中。uboot在启动内核过程中传参给内核时,这些分区参数不能传错。

3、运行时必须先加载到DDR的连接地址处

uboot在第一阶段中进行重定位时将第二阶段(整个uboot的镜像)加载到DDR的0xC3E00000地址处,这个地址就是uboot的连接地址。一样的,uboot在启动内核时将内核镜像从SD卡读取存到DDR中,此时也必须存放到内核的链接地址处,否则启动不起来。(内核链接地址0x30008000)

4、内核启动需要必要的参数

uboot是无条件启动的,而内核不能,内核需要uboot来帮助启动。uboot要帮助内核实现重定位、给内核提供启动参数。

5、启动内核的第一步:加载内核到DDR中

Uboot如何启动内核? 1)先将内核镜像从启动介质(SD卡)加载到DDR中。 2)到DDR中启动内核镜像(uboot的第二阶段就是在DDR中运行的)。 内核的代码不需要考虑重定位,因为这些uboot已经帮做好了,内核只需要从链接地址处开始运行就可以了。

6、如何获取内核镜像?

1)在SD卡/iNand/Nand/NorFlash等存储介质。

如SD卡:uboot只需到SD卡kernel分区中读取内核镜像到DDR即可。读取需要用uboot的命令来读取。(x210的iNand版本是movi命令,Nand版本就是Nand命令)

movi read kernel 30008000

kernel 是SD卡的内核分区,30008000 是内核的链接地址。

2)tftp、nfs等网络下载方式从服务器远端获取镜像。

uboot还支持远程启动,也就是内核镜像不烧录到开发板的SD卡中等其他介质,而是放在主机的服务器中,然后启动时uboot通过网络从服务器中下载镜像到开发板的DDR中。

tftp 30008000 zImage

7、如何知道内核的链接地址?

链接地址在内核源代码的连接脚本或者Makefile中查找。

8、zImage和uImage的区别

bootm命令对应do_bootm函数,命令前加do_即可构成这个命令对应的函数,因此执行boot命令时,uboot实际执行的函数叫做do_bootm函数,在cmd_bootm.c文件内。

#if defined(CONFIG_SECURE_BOOT) 这个宏主要用来编译一些签名认证的代码。

#ifdef CONFIG_ZIMAGE_BOOT  这个条件用来支持zImage格式内核启动的。

9、vmlinuz、zImage和uImage

1)uboot经过编译生成的elf格式可执行文件为u-boot,类似于windows的exe格式,在操作系统下是可以直接运行的。但不能用来烧录下载,烧录时用u-boot.bin(也叫镜像文件,用来烧录到iNand中执行的)。U-boot.bin的来源:是由u-boot使用arm-linux-objcopy进行加工得到。

2)linux内核经过编译后也会生成elf格式的可执行程序,叫vmlinux或vmlinuz。这个是原始未加工的原版内核elf格式文件。vmlinuz和zImage所在的目录:  ~/x210v3_bsp/kernel/arch/arm/boot  

在实际烧录时不是vmlinuz/vmlinux,而是要用objcopy工具去制作成烧录镜像格式,Image格式(内核没有.bin后缀)。Vmlinuz大概有78M而Image只有7.5M。但还是觉得Image太大了,所以实际使用时要对其压缩,所以就有了zImage压缩镜像文件。

uImage和zImage是什么关系?uImage是由uboot对zImage加工得到的,加工的过程就是在zImage前面加上64k字节的头信息。

uImage不关linux内核的事,linux内核只管生成zImage即可,然后uboot中的mkimage工具再去由zImage加工生成uImage来给uboot启动。uboot中也可以支持zImage,是否支持就看x210_sd.h中是否定义了LINUX_ZIMAGE_MAGIC这个宏。

10、编译内核得到uImage然后启动

如果直接在kernel底下去make uImage会出现mkimage command not found。解决方案:去uboot/tools下cp mkimage /usr/local/bin/,复制mkimage工具到系统目录下。再去make uImage即可。

#if defined(CONFIG_ZIMAGE_BOOT)

after_header_check:

396-397行后,才是真正的zImage启动,之前都是进行镜像的头部信息校验(判断是哪种格式等)。

11、LINUX_ZIMAGE_MAGIC  宏定义

#ifdef CONFIG_ZIMAGE_BOOT

#define LINUX_ZIMAGE_MAGIC  0x016f2818

LINUX_ZIMAGE_MAGIC在这里称作一个魔数,这个数等于0x016f2818表示这个镜像是一个zImage。也就是说zImage格式的镜像中在头部的一个固定位置存放了这个数作为格式标记。

do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])函数。当执行bootm 0x30008000时,就是执行zImage启动内核,此时do_bootm的argc=2,argv[0]=bootm  argv[1]=0x30008000。也可以不带参进行默认执行,默认执行的连接地址为CFG_LOAD_ADDR,此宏定义在x210_sd.h。如何判断是zImage文件?zImage头部开始的第37-40字节处存放着zImage标志魔数,从这个位置取出然后对比LINUX_ZIMAGE_MAGIC。

12、image_header_t 结构体

image_header_t *hdr;

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;

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

hdr->ih_ep = ntohl(addr);这两句就是在进行改造。

static bootm_headers_t images; /*pointers to os/initrd/fdt images */

images全局变量是do_bootm函数中使用,用来完成启动过程的。zImage的校验过程其实就是先确认是不是zImage,确认后再修改zImage的头信息到合适,修改后用头信息去初始化images这个全局变量,然后就完成了校验。

13、uImage启动

case IMAGE_FORMAT_LEGACY:

type = image_get_type (os_hdr);

comp = image_get_comp (os_hdr);

os = image_get_os (os_hdr);

image_end = image_get_image_end (os_hdr);

load_start = image_get_load (os_hdr);

break;

以上代码就是用来启动uImage格式的。uImage方式是uboot本身发明的支持linux启动的镜像格式,但是后来这种方式被设备树方式所取代了(在do_bootm方式中叫FIT)。uImage的启动校验主要在boot_get_kernel函数中,主要任务就是校验uImage的头信息,并且得到真正的kernel的起始位置去启动。

uboot设计时只支持uImage启动,后来有了fdt方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式。第二阶段校验头信息结束,然后进入第三阶段,第三阶段主要任务是调用do_bootm_linux函数启动linux内核。

14、do_bootm_linux函数 和 镜像的入口处entrypoint

此函数在uboot/lib_arm/bootm.c目录下。ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相当于头是有一定的偏移量。一般执行一个镜像都是:第一步先读取头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类;第二步对镜像进行校验;第三步再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。

theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数指向就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。

#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) || \

    defined (CONFIG_MTDPARTITION)

setup_start_tag (bd);

给linux内核准备传递的参数处理。

15、传参详解

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传参。

16、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是否开启了相应宏以支持传参。

猜你喜欢

转载自blog.csdn.net/lushoumin/article/details/82903626