您的位置:首页 > 其它

uboot终极目标:启动内核

2014-09-01 20:25 239 查看
uboot总的启动linux内核的流程:启动文件start.S基本软硬件环境初始化好之后,调用arm_lib/board.c下的startarm_boot()函数,这个函数最终调用main_loop()函数,内核的调用肯定就在这里面完成了。
因此,首先分析common/main.c:的main_loop函数:

main_loop()
{
char *s;
int bootdelay;
embedsky_lcd_Init();
extern int mtdparts_init(void);
if (!getenv("mtdparts"))
{
run_command("mtdparts default", 0);
}
else
{
mtdparts_init();
}
s = getenv ("bootcmd");
if (bootdelay >= 0 && s && !abortboot (bootdelay))
{
if (bBootFrmNORFlash())  //如果从Nor启动,即进入u-boot菜单下载模式,可以去分析bBootFrmNORFlash()这个函数
{
run_command("menu", 0);
}
/*
* Main Loop for Monitor Command Processing
*/
else  //从nand启动:wince和linux的选择
{
#ifdef CONFIG_SURPORT_WINCE //未定义
if (!TOC_Read())//wince匹配
{
/* Launch wince */
char cmd_buf[16];
printf("Booting wince ...\n");
strcpy(cmd_buf, "wince");
run_command(cmd_buf, 0);
}
else//最终匹配加载Linux内核
#endif
{
printf("Booting Linux ...\n");
//boot_zImage(0x200000,0x200000);
//启动内核
run_command (s, 0);
}
}
}
//run_command("menu", 0);
/*进入menu模式后的:获取命令以及操作*/
for (;;) {
len = readline (CFG_PROMPT); //获取命令的长度

flag = 0;	/* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);//保存输入的命令数据
else if (len == 0)
flag |= CMD_FLAG_REPEAT; //如果输入数据0,那么重复上次执行的命令,即只敲入回车键的情况
return;		/* retry autoboot */

if (len == -1) //异常中断处理:遇到^C
puts ("<INTERRUPT>\n");
else
rc = run_command (lastcommand, flag);//执行命令并保存命令匹配情况

if (rc <= 0) {// 输入的命令不匹配时,清空记录
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
}
uboot启动内核主要从main_loop函数里面的两条语句(如下)展开分析:

第一条:

s = getenv ("bootcmd");   //获取启动环境变量(包含两条命令)

getenv()返回的是指针,该指针指向字符串"bootcmd"指向的内容

bootcmd定义:environment.c和env_common.c都有:(数据结构--结构体定义)

#ifdef CONFIG_BOOTCOMMAND

"bootcmd=" CONFIG_BOOTCOMMAND"\0"

而#define  CONFIG_BOOTCOMMAND   "nboot 0x32000000 kernel; bootm 0x32000000"在tq2440.h里面定义

bootcmd=nboot 0x32000000 kernel; bootm 0x32000000   这里面一共有两条命令,他们的作用:

(1)nboot:从nand的kernel分区读出内核放到内存地址0x32000000  (2)bootm:再从内存地址0x32000000启动内核

第二条:

run_command(s,0);  //运行上面得到的命令
1、从nand读出内核到SDRAM(uImage=头部+真正的内核)

--具体从哪里读?  --从kernel分区(nand里边地址0x002000000处开始读,读0x00200000大小2M)

--读到哪里去?        --内存的某个合适的位置,这个根据上面传入的地址参数

关于nand里边各个分区的定义在include\configs\tq2440.h头文件里面:

#define MTDPARTS_DEFAULT		"mtdparts=nandflash0:256k@0(bios)," \
"128k(params)," \
"128k(toc)," \
"512k(eboot)," \
"1024k(logo)," \
"2m(kernel)," \
"-(root)"
//@0:起始地址为:0,接着下面的是大小,MTD:内存技术设备
具体的nand读操作:

nboot命令:分析common/cmd_nand.c的do_nandboot()函数和nboot命令对应的结构体即可

2、bootm启动内核:该命令调用do_bootm()函数
1)先读内核的头部,从头部看要加载内核地址和(bootm+传入的内核地址)是否相同,如果不相同,要把真正的内核移到ih_load这个加载地址去,最后跳到ih_ep入口地址

内核头部结构体在image.h里面定义,主要关心:加载地址ih_load和入口地址ih_ep

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;
//一共64字节
这个结构体里面的每个变量到底从哪里得到值?由内核制作工具tools/mkimage.c文件里面给予值
image_header_t header;
image_header_t *hdr = &header;
/* Build new header */
hdr->ih_magic = htonl(IH_MAGIC);
hdr->ih_time  = htonl(sbuf.st_mtime);
hdr->ih_size  = htonl(sbuf.st_size - sizeof(image_header_t));
hdr->ih_load  = htonl(addr);
hdr->ih_ep    = htonl(ep);
hdr->ih_dcrc  = htonl(checksum);
hdr->ih_os    = opt_os;
hdr->ih_arch  = opt_arch;
hdr->ih_type  = opt_type;
hdr->ih_comp  = opt_comp;
根据这个内核的头部判断是否需要将内核移到合适位置,下面是个人的小结。具体的分析看下篇博文:do_bootm()函数分析。

        首先bootm命令调用do_bootm函数:判断bootm xxxx 这个加载地址xxxx是否与指定的加载地址是否相同
如果不同,从这个地址开始提取出这个64byte的头部并对其进行校验分析,然后把去掉头部的内核复制到-a指定的ih_load加载地址中去运行,此时,ih_ep=ih_load
如果相同,让其原封不同的放在那,此时,-e指定的入口地址应该推后64byte,以跳过这64byte的头部,此时,ih_ep != ih_load
2)真正启动内核:(两个任务)

主要分析lib_arm/armlinux.c的do_bootm_linux()函数

  (1)设置启动参数:armlinux.c里面定义参数的Tags函数,并在do_bootm_linux()函数调用完成

  (2)跳到入口地址真正启动内核:do_bootm()函数调用do_bootm_linux函数:
//定义theKernel是一个c函数的指针
void (*theKernel)(int zero, int arch, uint params);
//给theKernel赋值指向entry point
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
//内核入口函数的调用,脱离u-boot,进入Linux内核中运行
theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

到这里uboot启动内核的使命就完成了!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: