1)实验平台:正点原子Linux开发板
2)摘自《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号公众号,获取更多资料:正点原子
32.3 bootz启动Linux内核过程
32.3.1 images全局变量
不管是bootz还是bootm命令,在启动Linux内核的时候都会用到一个重要的全局变量:images,images在文件cmd/bootm.c中有如下定义:
示例代码32.3.1.1 images全局变量
43bootm_headers_t images;/* pointers to os/initrd/fdt images */
images是bootm_headers_t类型的全局变量,bootm_headers_t是个boot头结构体,在文件include/image.h中的定义如下(删除了一些条件编译代码):
示例代码32.3.1.2 bootm_headers_t结构体
304typedefstruct bootm_headers {
305/*
306 * Legacy os image header, if it is a multi component image
307 * then boot_get_ramdisk() and get_fdt() will attempt to get
308 * data from second and third component accordingly.
309 */
310 image_header_t *legacy_hdr_os; /* image header pointer */
311 image_header_t legacy_hdr_os_copy;/* header copy */
312 ulong legacy_hdr_valid;
313
......
333
334 #ifndef USE_HOSTCC
335 image_info_t os; /* OS镜像信息 */
336 ulong ep; /* OS入口点 */
337
338 ulong rd_start, rd_end; /* ramdisk开始和结束位置 */
339
340char*ft_addr; /* 设备树地址 */
341 ulong ft_len; /* 设备树长度 */
342
343 ulong initrd_start; /* initrd开始位置 */
344 ulong initrd_end; /* initrd结束位置 */
345 ulong cmdline_start; /* cmdline开始位置 */
346 ulong cmdline_end; /* cmdline结束位置 */
347 bd_t *kbd;
348 #endif
349
350int verify;/* getenv("verify")[0] != 'n' */
351
352 #define BOOTM_STATE_START (0x00000001)
353 #define BOOTM_STATE_FINDOS (0x00000002)
354 #define BOOTM_STATE_FINDOTHER (0x00000004)
355 #define BOOTM_STATE_LOADOS (0x00000008)
356 #define BOOTM_STATE_RAMDISK (0x00000010)
357 #define BOOTM_STATE_FDT (0x00000020)
358 #define BOOTM_STATE_OS_CMDLINE (0x00000040)
359 #define BOOTM_STATE_OS_BD_T (0x00000080)
360 #define BOOTM_STATE_OS_PREP (0x00000100)
361 #define BOOTM_STATE_OS_FAKE_GO (0x00000200)/*'Almost' run the OS*/
362 #define BOOTM_STATE_OS_GO (0x00000400)
363int state;
364
365 #ifdef CONFIG_LMB
366struct lmb lmb;/* 内存管理相关,不深入研究 */
367 #endif
368} bootm_headers_t;
第335行的os成员变量是image_info_t类型的,为系统镜像信息。
第352~362行这11个宏定义表示BOOT的不同阶段。
接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h中的定义如下:
示例代码32.3.1.3 image_info_t结构体
292typedefstruct image_info {
293 ulong start, end; /* blob开始和结束位置*/
294 ulong image_start, image_len; /* 镜像起始地址(包括blob)和长度 */
295 ulong load; /* 系统镜像加载地址*/
296uint8_t comp, type, os; /* 镜像压缩、类型,OS类型 */
297uint8_t arch; /* CPU架构 */
298} image_info_t;
全局变量images会在bootz命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。
32.3.2 do_bootz函数
bootz命令的执行函数为do_bootz,在文件cmd/bootm.c中有如下定义:
示例代码32.3.2.1 do_bootz函数
622int do_bootz(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[])
623{
624int ret;
625
626/* Consume 'bootz' */
627 argc--; argv++;
628
629if(bootz_start(cmdtp, flag, argc, argv,&images))
630return1;
631
632/*
633 * We are doing the BOOTM_STATE_LOADOS state ourselves, so must
634 * disable interrupts ourselves
635 */
636 bootm_disable_interrupts();
637
638 images.os.os = IH_OS_LINUX;
639 ret = do_bootm_states(cmdtp, flag, argc, argv,
640 BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |
641 BOOTM_STATE_OS_GO,
642&images,1);
643
644return ret;
645}
第629行,调用bootz_start函数,bootz_start函数执行过程参考32.3.3小节。
第636行,调用函数bootm_disable_interrupts关闭中断。
第638行,设置images.os.os为IH_OS_LINUX,也就是设置系统镜像为Linux,表示我们要启动的是Linux系统!后面会用到images.os.os来挑选具体的启动函数。
第639行,调用函数do_bootm_states来执行不同的BOOT阶段,这里要执行的BOOT阶段有:BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO。
32.3.3 bootz_start函数
bootz_srart函数也定义在文件cmd/bootm.c中,函数内容如下:
示例代码32.3.3.1 bootz_start函数
578staticint bootz_start(cmd_tbl_t *cmdtp,int flag,int argc,
579char*const argv[], bootm_headers_t *images)
580{
581int ret;
582 ulong zi_start, zi_end;
583
584 ret = do_bootm_states(cmdtp, flag, argc, argv,
585 BOOTM_STATE_START, images,1);
586
587/* Setup Linux kernel zImage entry point */
588if(!argc){
589 images->ep = load_addr;
590 debug("* kernel: default image load address = 0x%08lx",
591 load_addr);
592}else{
593 images->ep = simple_strtoul(argv[0],NULL,16);
594 debug("* kernel: cmdline image address = 0x%08lx",
595 images->ep);
596}
597
598 ret = bootz_setup(images->ep,&zi_start,&zi_end);
599if(ret !=0)
600return1;
601
602 lmb_reserve(&images->lmb, images->ep, zi_end - zi_start);
603
604/*
605 * Handle the BOOTM_STATE_FINDOTHER state ourselves as we do not
606 * have a header that provide this informaiton.
607 */
608if(bootm_find_images(flag, argc, argv))
609return1;
610
......
619return0;
620}
第584行,调用函数do_bootm_states,执行BOOTM_STATE_START阶段。
第593行,设置images的ep成员变量,也就是系统镜像的入口点,使用bootz命令启动系统的时候就会设置系统在DRAM中的存储位置,这个存储位置就是系统镜像的入口点,因此images->ep=0X80800000。
第598行,调用bootz_setup函数,此函数会判断当前的系统镜像文件是否为Linux的镜像文件,并且会打印出镜像相关信息,bootz_setup函数稍后会讲解。
第608行,调用函数bootm_find_images查找ramdisk和设备树(dtb)文件,但是我们没有用到ramdisk,因此此函数在这里仅仅用于查找设备树(dtb)文件,此函数稍后也会讲解。
先来看一下bootz_setup函数,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下:
示例代码32.3.3.2 bootz_setup函数
370 #define LINUX_ARM_ZIMAGE_MAGIC 0x016f2818
371
372int bootz_setup(ulong image, ulong *start, ulong *end)
373{
374struct zimage_header *zi;
375
376 zi =(struct zimage_header *)map_sysmem(image,0);
377if(zi->zi_magic != LINUX_ARM_ZIMAGE_MAGIC){
378 puts("Bad Linux ARM zImage magic!");
379return1;
380}
381
382*start = zi->zi_start;
383*end = zi->zi_end;
384
385 printf("Kernel image @ %#08lx [ %#08lx - %#08lx ]", image,
386 *start,*end);
387
388return0;
389}
第370行,宏LINUX_ARM_ZIMAGE_MAGIC就是ARM Linux系统魔术数。
第376行,从传递进来的参数image(也就是系统镜像首地址)中获取zimage头。zImage头结构体为zimage_header。
第377~380行,判断image是否为ARM的Linux系统镜像,如果不是的话就直接返回,并且打印出“Bad Linux ARM zImage magic!”,比如我们输入一个错误的启动命令:
bootz 80000000 – 900000000
因为我们并没有在0X80000000处存放Linux镜像文件(zImage),因此上面的命令肯定会执行出错的,结果如图32.3.3.1所示:
第382、383行初始化函数bootz_setup的参数start和end。
第385行,打印启动信息,如果Linux系统镜像正常的话就会输出图32.3.3.2所示的信息:
接下来看一下函数bootm_find_images,此函数定义在文件common/bootm.c中,函数内容如下:
示例代码32.3.3.3 bootm_find_images函数
225int bootm_find_images(int flag,int argc,char*const argv[])
226{
227int ret;
228
229/* find ramdisk */
230 ret = boot_get_ramdisk(argc, argv,&images, IH_INITRD_ARCH,
231&images.rd_start,&images.rd_end);
232if(ret){
233 puts("Ramdisk image is corrupt or invalid");
234return1;
235}
236
237 #if defined(CONFIG_OF_LIBFDT)
238/* find flattened device tree */
239 ret = boot_get_fdt(flag, argc, argv, IH_ARCH_DEFAULT,&images,
240&images.ft_addr,&images.ft_len);
241if(ret){
242 puts("Could not find a valid device tree");
243return1;
244}
245 set_working_fdt_addr((ulong)images.ft_addr);
246 #endif
......
258return0;
259}
第230~235行是跟查找ramdisk,但是我们没有用到ramdisk,因此这部分代码不用管。
第237~244行是查找设备树(dtb)文件,找到以后就将设备树的起始地址和长度分别写到images的ft_addr和ft_len成员变量中。我们使用bootz启动Linux的时候已经指明了设备树在DRAM中的存储地址,因此images.ft_addr=0X83000000,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为0X8C81,因此images.ft_len=0X8C81。
bootz_start函数就讲解到这里,bootz_start主要用于初始化images的相关成员变量。
32.3.4 do_bootm_states函数
do_bootz最后调用的就是函数do_bootm_states,而且在bootz_start中也调用了do_bootm_states函数,看来do_bootm_states函数还是个香饽饽。此函数定义在文件common/bootm.c中,函数代码如下:
示例代码32.3.4.1 do_bootm_states函数
591int do_bootm_states(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[],
592int states, bootm_headers_t *images,int boot_progress)
593{
594 boot_os_fn *boot_fn;
595 ulong iflag =0;
596int ret =0, need_boot_fn;
597
598 images->state |= states;
599
600/*
601 * Work through the states and see how far we get. We stop on
602 * any error.
603 */
604if(states & BOOTM_STATE_START)
605 ret = bootm_start(cmdtp, flag, argc, argv);
606
607if(!ret &&(states & BOOTM_STATE_FINDOS))
608 ret = bootm_find_os(cmdtp, flag, argc, argv);
609
610if(!ret &&(states & BOOTM_STATE_FINDOTHER)){
611 ret = bootm_find_other(cmdtp, flag, argc, argv);
612 argc =0;/* consume the args */
613}
614
615/* Load the OS */
616if(!ret &&(states & BOOTM_STATE_LOADOS)){
617 ulong load_end;
618
619 iflag = bootm_disable_interrupts();
620 ret = bootm_load_os(images,&load_end,0);
621if(ret ==0)
622 lmb_reserve(&images->lmb, images->os.load,
623(load_end - images->os.load));
624elseif(ret && ret != BOOTM_ERR_OVERLAP)
625goto err;
626elseif(ret == BOOTM_ERR_OVERLAP)
627 ret =0;
628 #if defined(CONFIG_SILENT_CONSOLE)&&!defined(CONFIG_SILENT_U_BOOT_ONLY)
629if(images->os.os == IH_OS_LINUX)
630 fixup_silent_linux();
631 #endif
632}
633
634/* Relocate the ramdisk */
635 #ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH
636if(!ret &&(states & BOOTM_STATE_RAMDISK)){
637 ulong rd_len = images->rd_end - images->rd_start;
638
639 ret = boot_ramdisk_high(&images->lmb, images->rd_start,
640 rd_len,&images->initrd_start,&images->initrd_end);
641if(!ret){
642 setenv_hex("initrd_start", images->initrd_start);
643 setenv_hex("initrd_end", images->initrd_end);
644}
645}
646 #endif
647 #if defined(CONFIG_OF_LIBFDT)&& defined(CONFIG_LMB)
648if(!ret &&(states & BOOTM_STATE_FDT)){
649 boot_fdt_add_mem_rsv_regions(&images->lmb, images->ft_addr);
650 ret = boot_relocate_fdt(&images->lmb,&images->ft_addr,
651&images->ft_len);
652}
653 #endif
654
655/* From now on, we need the OS boot function */
656if(ret)
657return ret;
658 boot_fn = bootm_os_get_boot_func(images->os.os);
659 need_boot_fn = states &(BOOTM_STATE_OS_CMDLINE |
660 BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
661 BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
662if(boot_fn ==NULL&& need_boot_fn){
663if(iflag)
664 enable_interrupts();
665 printf("ERROR: booting os '%s' (%d) is not supported",
666 genimg_get_os_name(images->os.os), images->os.os);
667 bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
668return1;
669}
670
671/* Call various other states that are not generally used */
672if(!ret &&(states & BOOTM_STATE_OS_CMDLINE))
673 ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);
674if(!ret &&(states & BOOTM_STATE_OS_BD_T))
675 ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
676if(!ret &&(states & BOOTM_STATE_OS_PREP))
677 ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
678
679 #ifdef CONFIG_TRACE
680/* Pretend to run the OS, then run a user command */
681if(!ret &&(states & BOOTM_STATE_OS_FAKE_GO)){
682char*cmd_list = getenv("fakegocmd");
683
684 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
685 images, boot_fn);
686if(!ret && cmd_list)
687 ret = run_command_list(cmd_list,-1, flag);
688}
689 #endif
690
691/* Check for unsupported subcommand. */
692if(ret){
693 puts("subcommand not supported");
694return ret;
695}
696
697/* Now run the OS! We hope this doesn't return */
698if(!ret &&(states & BOOTM_STATE_OS_GO))
699 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
700 images, boot_fn);
......
712return ret;
713}
函数do_bootm_states根据不同的BOOT状态执行不同的代码段,通过如下代码来判断BOOT状态:
states & BOOTM_STATE_XXX
在do_bootz函数中会用到BOOTM_STATE_OS_PREP 、BOOTM_STATE_OS_FAKE_GO和BOOTM_STATE_OS_GO这三个BOOT状态,bootz_start函数中会用到BOOTM_STATE_START这个BOOT状态。为了精简代码,方便分析,因此我们将示例代码32.3.4.1中的函数do_bootm_states进行精简,只留下下面这4个BOOT状态对应的处理代码:
BOOTM_STATE_OS_PREP
BOOTM_STATE_OS_FAKE_GO
BOOTM_STATE_OS_GO
BOOTM_STATE_START
精简以后的do_bootm_states函数如下所示:
示例代码32.3.4.2 精简后的do_bootm_states函数
591int do_bootm_states(cmd_tbl_t *cmdtp,int flag,int argc,char*const argv[],
592int states, bootm_headers_t *images,int boot_progress)
593{
594 boot_os_fn *boot_fn;
595 ulong iflag =0;
596int ret =0, need_boot_fn;
597
598 images->state |= states;
599
600/*
601 * Work through the states and see how far we get. We stop on
602 * any error.
603 */
604if(states & BOOTM_STATE_START)
605 ret = bootm_start(cmdtp, flag, argc, argv);
......
654
655/* From now on, we need the OS boot function */
656if(ret)
657return ret;
658 boot_fn = bootm_os_get_boot_func(images->os.os);
659 need_boot_fn = states &(BOOTM_STATE_OS_CMDLINE |
660 BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |
661 BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);
662if(boot_fn ==NULL&& need_boot_fn){
663if(iflag)
664 enable_interrupts();
665 printf("ERROR: booting os '%s' (%d) is not supported",
666 genimg_get_os_name(images->os.os), images->os.os);
667 bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);
668return1;
669}
670
......
676if(!ret &&(states & BOOTM_STATE_OS_PREP))
677 ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);
678
679 #ifdef CONFIG_TRACE
680/* Pretend to run the OS, then run a user command */
681if(!ret &&(states & BOOTM_STATE_OS_FAKE_GO)){
682char*cmd_list = getenv("fakegocmd");
683
684 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,
685 images, boot_fn);
686if(!ret && cmd_list)
687 ret = run_command_list(cmd_list,-1, flag);
688}
689 #endif
690
691/* Check for unsupported subcommand. */
692if(ret){
693 puts("subcommand not supported");
694return ret;
695}
696
697/* Now run the OS! We hope this doesn't return */
698if(!ret &&(states & BOOTM_STATE_OS_GO))
699 ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
700 images, boot_fn);
......
712return ret;
713}
第604、605行,处理BOOTM_STATE_START阶段,bootz_start会执行这一段代码,这里调用函数bootm_start,此函数定义在文件common/bootm.c中,函数内容如下:
示例代码32.3.4.2 bootm_start函数
69staticint bootm_start(cmd_tbl_t *cmdtp,int flag,int argc,
70char*const argv[])
71{
72 memset((void*)&images,0,sizeof(images));/* 清空images */
73 images.verify = getenv_yesno("verify");/* 初始化verfify成员 */
74
75 boot_start_lmb(&images);
76
77 bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START,"bootm_start");
78 images.state = BOOTM_STATE_START;/* 设置状态为BOOTM_STATE_START */
79
80 return0;
81}
接着回到示例代码32.3.4.2中,继续分析函数do_bootm_states。第658行非常重要!通过函数bootm_os_get_boot_func来查找系统启动函数,参数images->os.os就是系统类型,根据这个系统类型来选择对应的启动函数,在do_bootz中设置images.os.os= IH_OS_LINUX。函数返回值就是找到的系统启动函数,这里找到的Linux系统启动函数为do_bootm_linux,关于此函数查找系统启动函数的过程请参考32.3.5小节。因此boot_fn=do_bootm_linux,后面执行boot_fn函数的地方实际上是执行的do_bootm_linux函数。
第676行,处理BOOTM_STATE_OS_PREP状态,调用函数do_bootm_linux,do_bootm_linux也是调用boot_prep_linux来完成具体的处理过程。boot_prep_linux主要用于处理环境变量bootargs,bootargs保存着传递给Linuxkernel的参数。
第679~689行是处理BOOTM_STATE_OS_FAKE_GO状态的,但是要我们没用使能TRACE功能,因此宏CONFIG_TRACE也就没有定义,所以这段程序不会编译。
第699行,调用函数boot_selected_os启动Linux内核,此函数第4个参数为Linux系统镜像头,第5个参数就是Linux系统启动函数do_bootm_linux。boot_selected_os函数定义在文件common/bootm_os.c中,函数内容如下:
示例代码32.3.4.3 boot_selected_os函数
476int boot_selected_os(int argc,char*const argv[],int state,
477 bootm_headers_t *images, boot_os_fn *boot_fn)
478{
479 arch_preboot_os();
480 boot_fn(state, argc, argv, images);
......
490return BOOTM_ERR_RESET;
491}
第480行调用boot_fn函数,也就是do_bootm_linux函数来启动Linux内核。
32.3.5 bootm_os_get_boot_func函数
do_bootm_states会调用bootm_os_get_boot_func来查找对应系统的启动函数,此函数定义在文件common/bootm_os.c中,函数内容如下:
示例代码32.3.5.1 bootm_os_get_boot_func函数
493 boot_os_fn *bootm_os_get_boot_func(int os)
494{
495 #ifdef CONFIG_NEEDS_MANUAL_RELOC
496staticbool relocated;
497
498if(!relocated){
499int i;
500
501/* relocate boot function table */
502for(i =0; i < ARRAY_SIZE(boot_os); i++)
503if(boot_os[i]!=NULL)
504 boot_os[i]+= gd->reloc_off;
505
506 relocated = true;
507}
508 #endif
509return boot_os[os];
510}
第495~508行是条件编译,在本uboot中没有用到,因此这段代码无效,只有509行有效。在509行中boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件common/bootm_os.c中,如下所示:
示例代码32.3.5.2 boot_os数组
435static boot_os_fn *boot_os[]={
436[IH_OS_U_BOOT]= do_bootm_standalone,
437 #ifdef CONFIG_BOOTM_LINUX
438[IH_OS_LINUX]= do_bootm_linux,
439 #endif
......
465 #ifdef CONFIG_BOOTM_OPENRTOS
466[IH_OS_OPENRTOS]= do_bootm_openrtos,
467 #endif
468};
第438行就是Linux系统对应的启动函数:do_bootm_linux。
32.3.6 do_bootm_linux函数
经过前面的分析,我们知道了do_bootm_linux就是最终启动Linux内核的函数,此函数定义在文件arch/arm/lib/bootm.c,函数内容如下:
示例代码32.3.6.1 do_bootm_linux函数
339int do_bootm_linux(int flag,int argc,char*const argv[],
340 bootm_headers_t *images)
341{
342/* No need for those on ARM */
343if(flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
344return-1;
345
346if(flag & BOOTM_STATE_OS_PREP){
347 boot_prep_linux(images);
348return0;
349}
350
351if(flag &(BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)){
352 boot_jump_linux(images, flag);
353return0;
354}
355
356 boot_prep_linux(images);
357 boot_jump_linux(images, flag);
358return0;
359}
第351行,如果参数flag等于BOOTM_STATE_OS_GO或者BOOTM_STATE_OS_FAKE_GO的话就执行boot_jump_linux函数。boot_selected_os函数在调用do_bootm_linux的时候会将flag设置为BOOTM_STATE_OS_GO。
第352行,执行函数boot_jump_linux(又来了一个函数,绕啊绕啊!心累!),此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下:
示例代码32.3.6.2 boot_jump_linux函数
272staticvoid boot_jump_linux(bootm_headers_t *images,int flag)
273{
274 #ifdef CONFIG_ARM64
......
292 #else
293unsignedlong machid = gd->bd->bi_arch_number;
294char*s;
295void(*kernel_entry)(int zero,int arch, uint params);
296unsignedlong r2;
297int fake =(flag & BOOTM_STATE_OS_FAKE_GO);
298
299 kernel_entry =(void(*)(int,int, uint))images->ep;
300
301 s = getenv("machid");
302if(s){
303if(strict_strtoul(s,16,&machid)<0){
304 debug("strict_strtoul failed!");
305return;
306}
307 printf("Using machid 0x%lx from environment", machid);
308}
309
310 debug("## Transferring control to Linux (at address %08lx)"
311"...",(ulong) kernel_entry);
312 bootstage_mark(BOOTSTAGE_ID_RUN_OS);
313 announce_and_cleanup(fake);
314
315if(IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
316 r2 =(unsignedlong)images->ft_addr;
317else
318 r2 = gd->bd->bi_boot_params;
319
......
328 kernel_entry(0, machid, r2);
329}
330 #endif
331}
第274~292行是64位ARM芯片对应的代码,Cortex-A7是32位芯片,因此用不到。
第293行,变量machid保存机器ID,如果不使用设备树的话这个机器ID会被传递给Linux内核,Linux内核会在自己的机器ID列表里面查找是否存在与uboot传递进来的machid匹配的项目,如果存在就说Linux内核支持这个机器,那么Linux就会启动!如果使用设备树的话这个machid就无效了,设备树存有一个“兼容性”这个属性,Linux内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。
第295行,函数kernel_entry,看名字“内核_进入”,说明此函数是进入Linux内核的,也就是最终的大boos!!此函数有三个参数:zero,arch,params,第一个参数zero同样为0;第二个参数为机器ID;第三个参数ATAGS或者设备树(DTB)首地址,ATAGS是传统的方法,用于传递一些命令行信息啥的,如果使用设备树的话就要传递设备树(DTB)。
第299行,获取kernel_entry函数,函数kernel_entry并不是uboot定义的,而是Linux内核定义的,Linux内核镜像文件的第一行代码就是函数kernel_entry,而images->ep保存着Linux内核镜像的起始地址,而起始地址保存的不正是Linux内核第一行代码!
第313行,调用函数announce_and_cleanup来打印一些信息并做一些清理工作,此函数定义在文件arch/arm/lib/bootm.c中,函数内容如下:
示例代码32.3.6.3 announce_and_cleanup函数
72staticvoid announce_and_cleanup(int fake)
73{
74 printf("Starting kernel ...%s", fake ?
75 "(fake run for tracing)":"");
76 bootstage_mark_name(BOOTSTAGE_ID_BOOTM_HANDOFF,"start_kernel");
......
87 cleanup_before_linux();
88}
第74行,在启动Linux之前输出“Starting kernel ...”信息,如图32.3.6.1所示:
图32.3.6.1 系统启动提示信息
第87行调用cleanup_before_linux函数做一些清理工作。
继续回到示例代码32.3.6.2的函数boot_jump_linux,第315~318行是设置寄存器r2的值?为什么要设置r2的值呢?Linux内核一开始是汇编代码,因此函数kernel_entry就是个汇编函数。向汇编函数传递参数要使用r0、r1和r2(参数数量不超过3个的时候),所以r2寄存器就是函数kernel_entry的第三个参数。
第316行,如果使用设备树的话,r2应该是设备树的起始地址,而设备树地址保存在images的ftd_addr成员变量中。
第317行,如果不使用设备树的话,r2应该是uboot传递给Linux的参数起始地址,也就是环境变量bootargs的值,
第328行,调用kernel_entry函数进入Linux内核,此行将一去不复返,uboot的使命也就完成了,它可以安息了!
总结一下bootz命令的执行过程,如图32.3.6.2所示:
到这里uboot的启动流程我们就讲解完成了,加上uboot顶层Makefile的分析,洋洋洒洒100多页,还是不少的!这也仅仅是uboot启动流程分析,当缕清了uboot的启动流程以后,后面移植uboot就会轻松很多。其实在工作中我们基本不需要这么详细的去了解uboot,半导体厂商提供给我们的uboot一般是可以直接用的,只要能跑起来,可以使用就可以了。但是作为学习,我们是必须去详细的了解一下uboot的启动流程,否则如果在工作中遇到问题我们连解决的方法都没有,都不知道该从哪里看起。但是呢,如果第一次就想弄懂uboot的整个启动流程还是有点困难的,所以如果没有看懂的话,不要紧!不要气馁,大多数人第一次看uboot启动流程基本都有各种各样的问题。
题外话:
相信大家看完本章以后基本都有一个感觉:长、复杂、绕!没错,当我第一次学习uboot的时候看到uboot启动流程的时候也是这个感觉,当时我也一脸懵逼,怎么这么复杂,这么长呢?尤其前面的汇编代码部分,还要涉及到ARM处理器架构的内容,当时也怀疑过自己是不是搞这一块的料。不过好在自己坚持下来了,uboot的启动流程我至少分析过7,8遍,各种版本的,零几年很古老的;12年、14年比较新的等等很多个版本的uboot。就I.MX6ULL使用的这个2016.03版本uboot我至少详细的分析了2遍,直至写完本章,大概花了1个月的时间。这期间查阅了各种资料,看了不知道多少篇博客,在这里感谢那些无私奉献的网友们。
相信很多朋友看完本章可能会想:我什么时候也能这么厉害,能够这么详细的分析uboot启动流程。甚至可能会有挫败感,还是那句话:不要气馁!千里之行始于足下,所有你羡慕的人都曾经痛苦过,挫败过。脚踏实地,一步一个脚印,一点一滴的积累,最终你也会成为你所羡慕的人。在嵌入式Linux这条道路上,有众多的学习者陪着你,大家相互搀扶,终能踏出一条康庄大道,祝所有的同学终有所获!