在uboot命令行print查看系统的启动命令结果是:bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0。这条命令的意思是,从nandflash上面的kernel分区把内核读到SDRAM的ox30007FC0地址,然后从这个地址启动。
在我们的PC上,每个硬盘前面会有一个分区表,但是在嵌入式Linux里面,Flash是没有分区表的,那我们的flash里面的boot env kernel 跟文件系统这些分区只能在源码中写死,所以我们不关心falsh里面这些分区的名字,而是这些分区的地址,那么看一下在哪里写死的。
这里面定义了mtd分区,这个分区位于nandflash上面,前面从0开始的256K是bootloader,接下来的128k存放的是环境变量参数,接下来的2m是kernel,剩下的东西是跟文件系统。对于这些分区,名字不重要,重要的是他们的起始地址和大小,这些东西是在代码里面写死的。
OpenJTAG> mtd
device nand0 <nandflash0>, # parts = 4
#: 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
active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000
defaults:
mtdids : nand0=nandflash0
mtdparts: mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)
bootm 0x300007FC0地址,这个地址放uImage文件。文件中包含头信息+内核文件。其中头文件格式:
/*
* all data in network byte order (aka natural aka bigendian)
*/
typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
uint32_t ih_time; /* Image Creation Timestamp */
uint32_t ih_size; /* Image Data Size */
uint32_t ih_load; /* Data Load Address */
uint32_t ih_ep; /* Entry Point Address */
uint32_t ih_dcrc; /* Image Data CRC Checksum */
uint8_t ih_os; /* Operating System */
uint8_t ih_arch; /* CPU architecture */
uint8_t ih_type; /* Image Type */
uint8_t ih_comp; /* Compression Type */
uint8_t ih_name[IH_NMLEN]; /* Image Name */
} image_header_t;
内核启动打印信息:
## Booting image at 30007fc0 ...
Image Name: Linux-2.6.22.6
Created: 2022-03-23 8:02:24 UTC
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1848720 Bytes = 1.8 MB
Load Address: 30008000
Entry Point: 30008000
Verifying Checksum ... OK
XIP Kernel Image ... OK
Starting kernel ...
Uncompressing Linux...................................................................................................................... done, booting the kernel.
Linux version 2.6.22.6 (book@book-virtual-machine) (gcc version 3.4.5) #1 Wed Mar 23 16:02:20 CST 2022
CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177
Machine: SMDK2440
Memory policy: ECC disabled, Data cache writeback
CPU S3C2440A (id 0x32440001)
S3C244X: core 400.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz
S3C24XX Clocks, (c) 2004 Simtec Electronics
CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on
CPU0: D VIVT write-back cache
CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
Built 1 zonelists. Total pages: 16256
Kernel command line: noinitrd root=/dev/mtdblock3 rw init=/linuxrc console=ttySAC0,115200 nfsroot=2.2.2.102:/work/nfs_root ip=2.2.2.112:2.2.2.102:2.2.2.1022:255.255.255.0::eth0:off
irq: clearing subpending status 00000002
PID hash table entries: 256 (order: 8, 1024 bytes)
timer tcon=00500000, tcnt a2c1, tcfg 00000200,00000000, usec 00001eb8
跳转地址是0x30008000,加载地址是0x30007FC0,中间相差64字节,这64字节就是头部的大小。
bootm的功能是:
- 根据头部,移动内核到合适的地方;
- 启动内核,这是通过do_bootm_linux函数完成的。
在启动kernel之前uboot需要告诉内核一些启动参数才能启动, 通过do_bootm_linux传递参数给内核, 但是当跳到内核运行时uboot已经不起作用了怎么传参数,
uboot是按照某种约定数据格式(称为TAG)把要传给kernel的参数写在某个约定地址, 当kernel启动后就去约定的地址读取参数.这个地址是0x30000100,这个格式称为TAG,设置TAG的代码在这里
一些关于uboot传参给kernel的概念:
uboot向kernel传参的实现就是uboot和kernel规定一种传参的数据格式(tag),
然后uboot将需要传递给kernel的参数存放到SDRAM的某段内存中,
然后将这段内存的起始地址传递给kernel,
然后kernel通过这个地址来读取传递的参数。
kernel如何知道tag的起始地址???
uboot启动内核的最后通过
theKernel (0, machid, bd->bi_boot_params);来启动内核,
这三个参数,第一个固定是0,;第二个是机器码;第三个就是tag的存放的起始地址,
也就是setup_start_tag。这三个参数分别存放在寄存器r0 r1 r2中。
我们看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 启动内核
kernel如何知道tag的结束地址???
在tag传参的过程中,有一个setup_start_tag(ATAG_CORE类型)和setup_end_tag(AATAG_NONE类型),
setup_start_tag表示tag开始传参,
setup_end_tag 表示传参的结束,
这两个tag之间的tag就是实际的向内核传递的tag。
设置参数到某个约定的地址, 这个约定的地址就是启动参数的起始地址,
在board/100ask24x0/100ask24x0.c文件的board_init函数中设置
int board_init (void)
{
...
/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100; // 这个就是TAG的起始地址, 也就是启动参数的起始地址
}
/***********************************************************************************/
以下是一些关于tag的结构:
struct tag_header {
u32 size; // tag参数个数
u32 tag; // tag地址
};
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002
struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};
#define ATAG_CMDLINE 0x54410009
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
#define ATAG_NONE 0x00000000
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
/***********************************************************************************/
下面分析do_bootm_linux函数时如何设置内核启动参数的:
setup_start_tag (bd);
-> params = (struct tag *) bd->bi_boot_params; // params = 0x30000100
-> params->hdr.tag = ATAG_CORE; // params->hdr.tag = 0x54410001
-> params->hdr.size = tag_size (tag_core); // params->hdr.size = (8 + 12) >> 2 = 5 //tag参数个数
-> params->u.core.flags = 0;
-> params->u.core.pagesize = 0;
-> params->u.core.rootdev = 0;
-> params = tag_next (params); // params指向下一个tag起始地址
...
#ifdef CONFIG_SETUP_MEMORY_TAGS
setup_memory_tags (bd);
-> params->hdr.tag = ATAG_MEM; // params->hdr.tag = 0x54410002
-> params->hdr.size = tag_size (tag_mem32); // params->hdr.size = (8 + 8) >> 2 = 4 //tag参数个数
-> params->u.mem.start = bd->bi_dram[i].start; // sdram起始地址, params->u.mem.start = 0x30000000;
-> params->u.mem.size = bd->bi_dram[i].size; // sdarm大小64M, params->u.mem.size = 64*1024*1024;
-> params = tag_next (params); // params指向下一个tag起始地址
#endif
#ifdef CONFIG_CMDLINE_TAG
setup_commandline_tag (bd, commandline); //
-> pparams->hdr.tag = ATAG_CMDLINE; // params->hdr.tag = 0x54410009
-> pparams->hdr.size = (sizeof (struct tag_header) + strlen (commandline) + 1 + 4) >> 2; // params->hdr.size = (8+X+1+4)/4
-> strlen (commandline)得到命令命令行的长度
-> strcpy (params->u.cmdline.cmdline, commandline); //将命令行拷贝进结构体存储
#endif -> params = tag_next (params); // params指向下一个tag起始地址
#ifdef CONFIG_INITRD_TAG
if (initrd_start && initrd_end)
setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
setup_videolfb_tag ((gd_t *) gd);
#endif
setup_end_tag (bd);
-> params->hdr.tag = ATAG_NONE; // params->hdr.tag = 0x00000000 tag结束标记
-> params->hdr.size = 0;
#endif
可以得到启动参数存储结构:
-------------------------
| 内核 |
| |
-------------------------0x300080000
| . |
| . |
| . |
| . |
-------------------------
hdr.tag | 0x00000000 |
-------------------------
hdr.size | 0 |
------------------------- params = tag_next (params); // end_tag
cmdline | 命令行数据 |
-------------------------
hdr.tag | 0x54410009 |
-------------------------
hdr.size | (8+strlen+1+4)/4 | 命令行长度
------------------------- params = tag_next (params); // commandline_tag
| 其他memory_tag |
-------------------------
u.mem.start | 0x30000000 |
-------------------------
u.mem.size | 64*1024*1024 |
-------------------------
hdr.tag | 0x54410002 |
-------------------------
hdr.size | 4 |
------------------------- params = tag_next (params); // memory_tags
| 0 |
-------------------------
| 0 |
-------------------------
| 0 |
-------------------------
hdr.tag | 0x54410001 | tag
-------------------------
hdr.size | 5 | tag参数个数
------------------------- 0x30000100 tag起始地址 start_tag
| . |
| . |
------------------------- 0x30000000 sdram起始地址
uboot: 最终目的启动内核
1. 从Flash读出内核
2. 启动
而启动之前需要设置参数给kernel然后跳到kernel入口地址执行