目录
images全局变量
do_bootz函数
bootz_start函数
do_bootm_states函数
bootm_os_get_boot_func函数
do_bootm_linux函数
images全局变量
不管是bootz还是bootm命令,在启动Linux内核的时候都会用到一个重要的全局变量:images, images在文件cmd/bootm.c中有如下定义:
images是bootm_headers_t类型的全局变量,bootm_headers_t是个boot头结构体,在文件include/image.h中的定义如下(删除了一些条件编译代码):
第335行的os成员变量是image info t类型的,为系统镜像信息。
第352-362行这11个宏定义表示BOOT的不同阶段。
接下来看一下结构体image_info_t,也就是系统镜像信息结构体,此结构体在文件include/image.h中的定义如下:
全局变量images会在bootz 命令的执行中频繁使用到,相当于Linux内核启动的“灵魂”。
do_bootz函数
bootz命令的执行函数为do_bootz,在文件cmd/bootm.c中有如下定义:
第629行,调用 bootz_start函数。
第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。
bootz_start函数
bootz_srart函数也定义在文件cmd/bootm.c 中,函数内容如下:
第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中,函数内容如下:
第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),因此上面的命令肯定会执行出错的,结果如图所示:
第382、383行初始化函数bootz_setup的参数start和end。
第385行,打印启动信息,如果Linux系统镜像正常的话就会输出图所示的信息:
接下来看一下函数bootm_find_images,此函数定义在文件common/bootm.c中,函数内容如下:
接下来看一下函数bootmfind_images,此函数定义在文件common/bootm.c中,函数内容如下:
第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的相关成员变量。
do_bootm_states函数
do-bootz最后调用的就是函数do_bootm_states,而且在bootz_start中也调用了do_bootm_states 函数,看来do_bootm_states函数还是个香饽饽。此函数定义在文件common/bootm.c中,函数代码如下:
函数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 状态对应的处理代码:
第604、605行,处理BOOTM_STATE_START阶段, bootz_start会执行这一段代码,这里调用函数bootm_start,此函数定义在文件common/bootm.c中,函数内容如下:
接着回到示例代码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,因此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保存着传递给Linux kernel的参数。
第699行,调用函数 boot_selected_os启动Linux内核,此函数第4个参数为Linux系统镜像头,第5个参数就是Linux系统启动函数do_bootm_linux。boot_selected_os 函数定义在文件common/bootm os.c中,函数内容如下:
第480行调用boot_fn函数,也就是do_bootm_linux函数来启动Linux内核。
bootm_os_get_boot_func函数
do_bootm_states会调用bootm_os_get_boot_func来查找对应系统的启动函数,此函数定义在文件common/bootm os.c中,函数内容如下:
第495-508行是条件编译,在本uboot中没有用到,因此这段代码无效,只有509行有效。在509行中boot_os是个数组,这个数组里面存放着不同的系统对应的启动函数。boot_os也定义在文件 common/bootm os.c中,如下所示:
第438行就是Linux系统对应的启动函数:do_bootm_linux。
do_bootm_linux函数
经过前面的分析,我们知道了do_bootm_linux就是最终启动Linux内核的函数,此函数定义在文件arch/arm/lib/bootm.c,函数内容如下:
第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中,函数内容如下:
第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中,函数内容如下:
第274-292行是64位ARM芯片对应的代码, Cortex-A7是32位芯片,因此用不到。
第293行,变量machid保存机器ID,如果不使用设备树的话这个机器ID会被传递给Linux内核,Linux内核会在自己的机器ID列表里面查找是否存在与uboot传递进来的machid匹配的项目,如果存在就说明Linux内核支持这个机器,那么Linux就会启动!如果使用设备树的话这个machid就无效了,设备树存有一个“兼容性”这个属性, Linux内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。
第295行,函数kenel_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中,函数内容如下:
第74行,在启动Linux之前输出"Starting kernel ..."信息,如图所示:
第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命令的执行过程,如图所示: