uboot启动内核原理
看完上一节,你应该已经学会如何启动内核了,但只会启动内核是不行的,我们的目的是令内核按照我们的想法去运行,这就不得不提一下内核传参机制。
3.3.1 还是bootm
内核启动不是无条件的,而是有一定的先决条件,这个条件由启动内核的bootloader(我们这里就是uboot)来构建保证。
Linux规定了一种“向我传参“机制,那么uboot要是还想启动Linux内核就只能乖乖的遵守,上节我们只是讲解了uboot通过bootm命令启动内核,关于bootm所做的实际工作并未讲解,其实bootm执行时,uboot实际执行的函数叫do_bootm函数,该函数位于cmd/bootm.c文件中(我们的uboot2017是在此目录下,早一些的uboot版本位于cmd/cmd_bootm.c文件)。
do_bootm函数主要做了以下工作:
读取内核镜像头信息,然后在头信息的特定地址找MAGIC_NUM,由此来确定镜像种类。
对镜像进行校验,校验通过则进入下一步准备启动内核;如果校验失败则认为镜像有问题,不能启动。
再次读取头信息,由特定地址知道这个镜像的各种信息(镜像长度、镜像种类、入口地址);第四步就去entrypoint处开始执行镜像。
uboot本身设计时只支持uImage启动,原来uboot的代码也是这样写的。后来有了fdt方式之后,就把uImage方式命令为LEGACY方式,fdt方式命令为FIT方式。
找到do_bootm_linux函数,ep就是entrypoint的缩写,就是程序入口。一个镜像文件的起始执行部分不是在镜像的开头(镜像开头有n个字节的头信息),真正的镜像文件执行时第一句代码在镜像的中部某个字节处,相当于头是有一定的偏移量的。这个偏移量记录在头信息中。
theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,则这个函数指向就指向了内存中加载的OS镜像的真正入口地址(就是操作系统的第一句执行的代码)。
3.3.2 内核传参
theKernel函数启动内核时,传递了三个变量,没错,这三个变量就是uboot向Linux内核传递的参数,从前往后分别放在了ARM的r0-r2寄存器上,内核启动之后会去这三个寄存器上读取uboot传递给它的参数并解析运行。
首先第一个参数,也是我目前认为最鸡肋的参数:向寄存器r0写入数值“0“,对!没错!就是一个0,现在我也不明白为什么linus大神为什么会定义这么一个参数,可能是作为一个预留吧!也许哪天我境界到了就能领会大师的深意了。
和第一个参数相比,第二个参数就很有意义了,这也是很多人最容易疏忽的一个传参:板卡机器码。该机器码一定要和kernel中的机器码一致,否则内核启动必然失败!内核在编译链接过程中,将各种处理器内核描述符组合成表,启动后从机器描述符表中查询有无r1寄存器指定的机器码,如果没有就将退出,所以这也说明了为什么在u-boot中机器码一定要和内核中的机器码一致,否则内核就无法启动。
在介绍第三个传参之前,我想先给大家介绍两种Linux内核启动的方式,因为这决定了第三个参数,第一种是比较传统的ATAGS传参方式启动内核,这也是我们选择的内核启动方式(另一种方式比较新,还没有时间去深入研究..),第二种就是设备树方式启动内核,设备树在Linux3.0以后的内核版本开始支持,关于Linux设备树这里不做过多讲解。
我们使用的是ATAGS传参,那么就我们就要在内核启动前将传递参数设置好放在一个内存地址处,然后将该地址赋值给r2寄存器,也即uboot的第三个传参(如果是设备树传参,我们就将设备树加载到DDR的地址赋值给r2寄存器)。
3.3.3 ATAGS传参
struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline;
/* * Acorn specific */ struct tag_acorn acorn;
/* * DC21285 specific */ struct tag_memclk memclk; } u; }; |
tag是一个结构体类型 ,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。
结构体tag_header和共用体tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理, kernel接收到的传参是若干个tag构成的,这些tag由tag_start起始,到tag_end结束。
(1)CONFIG_SETUP_MEMORY_TAGS,tag_mem,传参内容是内存配置信息。
(2)CONFIG_CMDLINE_TAG,tag_cmdline,传参内容是启动命令行参数,也就是uboot环境变量的bootargs.