u-boot分析__uboot启动内核

目录

1.整体结构

2.分区的概念

 3.读出内核

4 uImage格式

5.bootm命令启动内核

5.1 设置启动参数

 5.1.1 setup_start_tag

 5.1.2 setup_memory_tag

 5.1.3 setup_commandline_tag

 5.1.4 setup_end_tag

扫描二维码关注公众号,回复: 13730214 查看本文章

5.2 启动内核

6.回顾总结


1.整体结构

uboot启动内核时依赖于两个函数,

s = getenv("bootcmd")获取环境变量,然后去运行命令,其中s就是这里的

这条命令的意思是,从nandflash上面的kernel分区把内核读到SDRAM的ox30007FC0地址,然后从这个地址启动。

2.分区的概念

在我们的PC上,每个硬盘前面会有一个分区表,但是在嵌入式Linux里面,Flash是没有分区表的,那我们的flash里面的boot env kernel 跟文件系统这些分区只能在源码中写死,所以我们不关心falsh里面这些分区的名字,而是这些分区的地址,那么看一下在哪里写死的。

 这里面定义了mtd分区,这个分区位于nandflash上面,前面从0开始的256K是bootloader,接下来的128k存放的是环境变量参数,接下来的2m是kernel,剩下的东西是跟文件系统。对于这些分区,名字不重要,重要的是他们的起始地址和大小,这些东西是在代码里面写死的。

我们用mtd看一下

 我们可以看到kernel分区的起始地址和长度,那么读内核命令可以替换成下面的

 3.读出内核

 前面我们看到bootm命令在源码中对应的是do_bootm,那么这里应该是有一个约定俗成的,我们的nand命令在源码中应该会对应do_nand,于是我们去源码中找do_nand函数,

 这里可以的read.jffs2,jffs2是一种文件格式,但是这里跟文件格式没什么关系,之所以用read.jffs2是因为用这个命令的时候,后面的分区长度0x00200000不需要页对齐,可以随便写,用其他后缀的话,这个长度是需要块对齐或者页对齐的,最终会调用nand_read_opts这个函数,这个函数内部细节先不看。

4 uImage格式

falsh中保存的内核是uImage,这个uImage格式是  头部+真正的内核,头部结构如下

ih_load表示加载地址,就是在内核运行的时候,要先把内核放到SDRAM的哪个地方。

ih_ep表示入口地址,就是运行内核的时候直接跳到这个地址就可以了。

我们之前说过nand read.jffs2 0x3007FC0 kernel,从nandflash读取内核的时候放到哪里都可以,不一定是放到0x3007FC0,这个地址随便放,只要不破坏高地址的这些东西就可以了。

 为什么可以随便放,就是因为uImage有个头部,头部里面有加载地址和入口地址,我们用bootm的时候,我们把uImage放到某个地址xxxx,然后bootm xxxx,然后bootm会先读出头部,知道他的加载地址和入口地址,如果发现当前内核并不位于他的加载地址,也就是xxxx和加载地址不相等,那么就要把内核移到加载地址中去,然后跳到入口地址执行,我们看下代码中是不是这样的。

 这个函数就是把我们的内核data移动到load加载地址,那如果说我们的data就刚好处于ih_load,那就不用移动了,这就是为什么我们从nand读出来之后放到了0x3007FC0,而并没有真的随便放一个地址。

对于我们这个开发板,内核的加载地址是0x3000800,然后我们发现0x3000800-0x3007FC0=64个字节,因为我们的uImage的头部刚好就是64个字节,那么我们真正的内核就正好位于0x3000800了,这样就不用移动了,加快启动速度。

 现在bootm读取了内核头部信息,并把内核数据data放到了加载地址,接下来就是启动内核,启动内核是在do_boot_linux函数中做的。

5.bootm命令启动内核

按说前面已经把内核放到加载地址那里了,接下来直接调到入口地址执行即可,但是在此之前还有一些事情要做,类比我们的X86,我们的PC机启动时,我们的BIOS会去检测内存,检测flash,BIOS会去检测出电脑中有多大内存,然后告诉内核,我们的Linux中也有类似的东西,我们的uboot要告诉内核一些启动参数,也就是设置启动参数,然后才是调到入口地址启动内核,我们接下来看一下do_boot_linux函数。

5.1 设置启动参数

uboot启动内核后就要跳转到内核那里去,然后uboot就不存在了,那uboot和内核之间怎么交互数据,最简单的方法就是,在某个地址按照某种格式保存数据。这个地址是0x30000100,这个格式称为TAG,设置TAG的代码在这里

我们看一下这四个

 5.1.1 setup_start_tag

 tag是一个结构体,他有一个头部和一个联合体,其中头部包含size和tag。

我们看setup_start_tag函数里面,params = (struct tag*) bd->bi_boot_params,bi_boot_params我们再代码搜一下可以看到

bi_boot_params是0x30000100,那么这些参数就是放到0x30000100这里,然后首先存放的是size,

从上面的代码中可以看到,size保存的是tag_size(tag_core),而tag_size是一个宏,

那么这里size就是头部的长度加上tag_core的长度。 

 

然后保存的是ATAG_CORE,

然后存放的是分别是联合体中的flags,pagesize,rootdev这三个值,一共是存放了五个变量。

执行完setup_start_tag之后得到如下内容,其中size是5,但是单位是4个字节,也就是5*4=20个字节,

 5.1.2 setup_memory_tag

 一样头部也是size,

然后是tag,

 然后是size和start,表示内存,

 这里的size和start在最开始的开机启动时的初始化代码已经设置好了。

 执行完setup_memory_tag之后,

 5.1.3 setup_commandline_tag

setup_commandline_tag输入了另外一个额外的参数commandline,这个参数来源于环境变量。

 这个环境变量的意思是,root也就是跟文件系统位于第3(从0开始)个flash分区,init表示第一个应用程序是linuxc,console表示内核大打印信息从串口0打印出来。

然后看一下setup_commandline_tag函数

执行完 setup_commandline_tag之后,得到如下内容

 5.1.4 setup_end_tag

这个比较简单,size和tag都是零,

5.2 启动内核

我们看一下启动内核函数,theKernel是在这里赋值的, 直接把入口地址给它。

 然后直接启动就好了

我们可以看到这个函数有三个参数,其中第三个就是前面在0x30000100这里存放的启动参数,然后第二个参数是机器id,我们在board_init里面给它赋值了

 为什么要给内核传这个参数呢,是因为内核可能支持很多单板,但是内核能不能支持我们当前正在用的这个单板呢,就要根据机器ID来比较,uboot作为上电后运行的第一个程序,有责任确定下这个单板的机器ID是多少,然后告诉内核,然后内核启动的时候,内核就会去比对机器ID,确定一下能不能支持这个单板。

6.回顾总结

我们uboot的终极目的是启动内核,然后分为下面几个步骤。

猜你喜欢

转载自blog.csdn.net/u013171226/article/details/123186059