这篇文章结合JZ2440v3开发板和uboot1.1.6代码讲述uboot如何启动内核(kernel版本为linux2.2.26,但实际没讲到和kernel代码有关的阶段)。
太长不看:可直接跳到第5节看uboot启动kernel过程
(1) 各存储器大小:NAND、NOR、SDRAM;
(2) 各文件大小:uboot、param、kernel、root;
(3) uboot启动kernel过程中涉及的关键函数及其之间的调用关系;
(1) 不讲uboot如何提前于kernel加载到SDRAM中的;
(2) 不讲kernel被uboot启动后如何开始它自己的启动流程的;
(3) 不讲使用nfs网络文件系统时可以没有root分区的情况;
(4) 不讲uboot存放在NOR下的启动过程;
一个正常能运行linux的开发板,其NAND flash中包含4部分数据:bootloader/uboot、params、kernel、root。
(1) bootloader/uboot:uboot1.1.6打补丁后编译的bin文件将近200k;
(2) params:不固定,上限设为128k;params即uboot要传给kernel的全部参数的链表;
(3) kernel:linux2.2.26打补丁后编译的bin文件将近2M;
(4) root:根文件系统,上限为NAND flash中剩余的200M+空间。一个最小的、仅包含busybox(暂且叫做“命令工具箱”吧)和必备文件夹的yaffs2文件系统,大小约9M;
提一下:这里说的(我所理解)“存储器”包括外部RAM和ROM类存储器:SDRAM、NAND、NOR。
但从S3C2440手册看到,SDRAM和NOR flash由于共用一样的总线所以统称为“存储器(Memory)”,NAND单独算作“NAND Flash”类(如图):
S3C2440给4个文件在NAND中分配的空间大小如下:
SDRAM = 0x30000000 + 64MB
NOR = 0x00000000 + 2MB
NAND = 0x00000000 + 256MB
我本来觉得:kernel上电“启动”、“加载”后,把多少字节从flash(NAND)拷贝到SDRAM中备用呢?SDRAM毕竟是用来做内存条的闪存颗粒,可不便宜。应该装不下整个kernel吧?
比较kernel文件和SDRAM空间大小发现:完整加载kernel只占用1/16的空间——SDRAM还剩余大部分空间可用~
而且后来发现SDRAM和NAND价钱差不多,加大SDRAM容量把kernel甚至和别的数据(如有必要)一起完整拷也没问题(反而是NOR的单价是SDRAM、NAND的30倍。所以只用了2MB的片子):
SDRAM = 4.2/32 = 0.063/MB
NOR = 4/2 = 2元/MB:
NAND = 17.5/256 = 0.07元/MB:
所以uboot和kernel都完整加载到SDRAM后,实际占用/剩余空间大小:
(1) (不重要)uboot除了存放在NAND,也可以存放在NOR中:从地址0x0开始,把NOR中uboot前面的一小段代码当做SDRAM执行(毕竟两者共用总线,而且NOR对比NAND的一大特点就是支持代码就地执行(XIP,execute in place)),然后跳转到SDARAM执行剩余的大部分代码——这正是由前面一小段代码从NOR搬运来SDRAM的。
(2) 上电启动前,文件都在NAND flash中,(用mtd命令可看到)分布(起始地址和大小):
值得注意的是:图中左边的NAND Controller Space表示JZ2440v3开发板上使用的NAND flash的总大小256MB及其分区划分;右边的Memory Controller Space表示SDRAM和NOR因共用总线而得到的命名空间分布(即它们各自地址的范围),并不是实际容量——因为SDRAM和NOR都是比NAND贵很多的存储器,所以即使有1G的命名空间,SDRAM只占用其中的64MB、NOR占用2MB。
#: name size offset mask_flags
0: bootloader 0x00040000 0x00000000 0
1: params 0x00020000 0x00040000 0
2: kernel 0x00200000 0x00060000 0
3: root 0x0fda0000 0x00260000 0
即:
NAND = 0x00000000 + 256MB
=> uboot = 256kB
=> params = 128kB
=> kernel = 2MB
=> root = 256M - 256k - 128k - 2M ~= 254MB
(3) 上电启动后,文件都在SDRAM中,分布(起始地址和大小):
这里正式开始讲uboot启动kernel过程涉及的函数及其之间的调用关系。
该过程从uboot代码执行设定好的bootcmd命令作为开始(在uboot命令行下输入print可以看到它的值):
bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0
bootm命令作为bootcmd命令的后半部分,对应uboot代码中的do_bootm()函数。
(1) uboot先提前于kernel加载到SDRAM中了,然后执行bootcmd命令的前半段:
把kernel从NAND中完整读取到SDRAM;
(2) 执行bootcmd命令的后半段:检查并移动SDRAM中的kernel到“正确”位置,然后启动它(以下提到的image可能指代SDRAM的kernel或代码的image全局结构体变量):
do_bootm ->
bootm_start ->
boot_get_kernel:
复制头部64B到image.image_header_t;
并提取其中的kernel地址、大小、类型,另外保存到image.os
根据检查到的image类型,填充image其他成员
boot_load_os ->
对比头部信息中的kernel“设置启动SDRAM地址”(iamge_addr)与当前已加载到的实际所在地址(load);
若不相等,则重新搬运(SDRAM->SDRAM)
boot_fn=boot_os ->
即运行do_bootm_linux():准备跳转启动kernel
(3) (这也属于bootm命令的内容;但属于另一阶段的动作,所以另起一小节)
do_bootm ->
do_bootm_linux ->
boot_prep_linux:
以链表形式保存要传递给kernel的参数到某个位置(128kB的param分区):
它以gd->bd->bi_boot_params为起始地址(gd="global data";bd="boot data");
这些参数包括:kernel信息、代码设置的变量参数、bootargs传入的(根文件系统路径)命令等
boot_jump_linux:
传3个参数到寄存器,并跳转到kernel,由kernel取出这3个参数、然后正式启动;
(R0=0x0,R1=机器号,R2=参数链表地址:指向更多启动参数)