linux bootup——uboot如何启动kernel

最近有被问到芯片从上电开始,到linux启动完成的流程,之前只有个大概的了解,借此机会再做一些了解。个人认为整个linux整个bootup过程大体上可以分为以下几个阶段:uboot的启动,uboot启动kernel,kernel启动直到用户可以操作。这次先来看一下uboot启动kernel的过程。

uboot

不过在此之前,我们对uboot的启动过程做一个简单的回顾。
在芯片上电之后,会启动固化在芯片中的bootrom,随后根据不同的启动设备,启动我们自己的bootloader。通常在嵌入式系统上,我们会使用uboot作为启动kernel的bootloader。

uboot-spl

通常来说,我们可以直接将uboot载入到芯片内部的ram中运行。但是对于某些soc来说,内部的ram比较小,小到没有办法装载一个完整的uboot镜像。这时候就需要spl,全程是secondary program loader。这里的第二是对于soc中的bootrom来说的。(补充一下:在某些soc上,比如zynq,会用自己的fsbl来代替uboot-spl来实现诸如加载bitstream的功能,其他跟uboot-spl类似)
那我们在uboot-spl中能完成那些事情呢?简单来说我们首先要初始化时钟及DDR,并且从指定的存储介质中搬运uboot第二阶段的代码到内存中运行。

uboot

uboot在初始化方面分为arch级和board级的初始化,arch级主要关闭MMU,TLB,初始化关键寄存器, board级则会初始化各种基础外设。

启动kernel

kernel镜像类型

考虑到空间占用和易用性,kernel通常不会以可执行文件的形式放在存储设备当中。那么kernel可以使用以下的镜像类型:
– 未压缩的elf:原始的kernel生成的elf可执行程序叫做vmlinux,一般来说我们并不直接烧录这个文件,因为他的大小太大了
– zImage:既然原始的kernel太大了,那我们自然就要对他进行压缩。压缩后的kernel在前端附加了一些解压缩的代码,这样的格式就是zImage了,在启动的时候通过头部的代码进行自解压。
– uImage:uImage其实就是在zImage前加了长度64字节的头,说明这个内核的版本,加载位置,生成时间,大小等信息
具体头部的内容如下:

typedef struct image_header {
    __be32      ih_magic;   /* Image Header Magic Number    */ 
    __be32      ih_hcrc;    /* Image Header CRC Checksum    */
    __be32      ih_time;    /* Image Creation Timestamp */
    __be32      ih_size;    /* Image Data Size      */
    __be32      ih_load;    /* Data  Load  Address      */
    __be32      ih_ep;      /* Entry Point Address      */
    __be32      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;
  • FIT image(flattened uImage Tree):直到目前,我们的image包含的依旧是内核本身,以及一些基本的信息,事实上我们启动一个kernel还需要dtb,ramdisk image等信息,于是乎最终产生了FIT image,这样我们在uboot中只要启动这一个image就可以了,而不需要通过复杂的环境变量来进行配置。

bootm命令

bootm的启动支持Legacy-uImage和FIT-uImage两种方式,我们知道Legacy-uImage依旧只包含Kernel本身,所以通常来说我们需要先将kernel,ramdisk,dtb加载到内存当中,然后使用

bootm <kernel-addr>
bootm <kernel-addr> <ramdisk-addr>
bootm <kernel-addr> - <dtb-addr>
bootm <kernel-addr> <ramdisk-addr> <dbt-addr>

来进行加载
而对于FIT-uImage就比较简单了,将uImage载入到内存中之后,直接调用bootm <kernel-addr>
通常来说Kernel加载的位置需要是RAM偏移0x8000的位置,前面需要预留32k的RAM,前面的32K总16k作为boot params另外16k作为临时页表。

启动kernel的基本要求及参数传递

根据head.s中ENTRY(stext)的注释:

This is normally called from the decompressor code. The requirements
are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
r1 = machine nr, r2 = atags or dtb pointer.

在kernel的入口函数具有如下要求:
– MMU关闭,kernel刚启动的时候页表尚未创建,所以在创建页表之前MMU都需要处于关闭状态
– D-cache关闭,这里指的是数据cache,不太确定为什么需要关闭,通常来说cache导致的都是数据一致性的问题。

同时我们可以看到,参数传递是通过寄存器来进行的:
– r1是machine id
– r2是atags或者dtb的地址

在uboot启动的时候需要将这两个参数放在r1r2中传递给kernel入口。

总结时间

这次没啥好总结的==有空把uboot启动代码具体分析一下。

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注