目录
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的终极目的是启动内核,然后分为下面几个步骤。