之前分析过,在main_loop函数中,u-boot启动内核是两条语句:
s = getenv("bootcmd");
run_command(s, 0);
查看u-boot环境变量可知:
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
即run_command(s, 0) 要执行的命令为:
nand read.jffs2 0x30007FC0 kernel;
bootm 0x30007FC0
从Flash上的kernel分区将内核读到0x30007FC0,然后从0x30007FC0启动内核
先说一下分区的概念:
嵌入式Linux的分区与PC机windows分区不一样,嵌入式Flash上没有分区表,这些分区是在源码中写死的,我们要关注的是分区起始地址和分区大小,而不是分区名。例如韦老师改写的u-boot中,u-boot配置文件100ask24x0.h中,nandflash的分区有bootloader、环境参数params、kernel分区、文件系统分区root分别占大小256k、128k、2M,剩下的全是root分区
mtd命令查看下分区:
从上图可以看出kernel分区从0x00060000 开始,大小 0x00200000 = 2*16^5 = 2097152 = 2*1024*1204 = 2M
nand read.jffs2 0x30007FC0 kernel; 也可以写为
nand read.jffs2 0x30007FC0 0x00060000 0x00200000
所以分区重要的是分区起始地址和分区大小!
继续分析,
读出内核,do_nand()
nand read.jffs2 0x30007FC0 kernel这条指令怎么从Nandflash上读出内核,do_nand()这个函数读出内核
启动内核: do_bootm
Flash上存放的内核为uImage,uImage的结构为 头部+真正的内核,头部占64个字节
看下头部有哪些内容:image_header_t *hdr = &header;
主要关注两个参数:加载地址 uint32_t ih_load; /* Data Load Address */
入口地址 uint32_t ih_ep; /* Entry Point Address */
加载地址:在要启动内核之前,内核应该放存放的位置。对于JZ2440,加载地址是0x30008000
入口地址:启动内核时,需跳到入口地址运行。
bootm 0x30007FC0 这条命令,执行do_bootm(),uImage存放在0x30007FC0,
启动内核过程:先读出uImage头部,得到加载地址和入口地址,若当前内核并不位于加载地址,则将内核移动到加载地址,然后设置内核启动参数,最后跳到入口地址去执行启动内核。
看代码:bootm 0x30007FC0 命令行参数为2个,故addr 为argv[1], addr = 0x30007FC0 ,uImage头部大小为64字节
data = 0x30007FC0 + 0x40 = 0x30008000
所以从Nandflash内核分区将uImage读到0x30007FC0地址时,真正内核地址正好在JZ2440开发板的内核加载地址,无需再次去移动内核而浪费时间,直接启动内核即可。
下面是判断是否需要移动内核:
把内核移动到合适的地址后,执行do_bootm_linux(),查看函数do_bootm_linux():
void (*theKernel)(int zero, int arch, uint params);
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
theKernel 是一个函数指针,指向内核入口地址ih_ep,设置好启动参数后,执行theKernel (跳到入口地址)启动内核。
bd->bi_arch_number :这个参数很重要,叫做机器ID,根据机器ID来判断是否支持该单板。
bd->bi_boot_params: 内核启动参数
内核启动后,kernel就与u-boot没有联系了,那么在启动之前,u-boot如何给内核设置启动参数的呢?
事先约定好,在某个地址,以某种格式保存参数,内核启动时读取这些参数。
对于JZ2440开发板,该地址为0x30000100,保存参数的格式是TAG,如下设置启动参数:
tag结构体: