1 images 全局变量#
不管是bootz
还是bootm
命令,启动kernel都会用到images
全局变量。images 定义在文件 cmd/bootm.c
:include/image.h
中的定义了bootm_headers_t
结构:该结构描述的是bootm
启动时的头部信息。该结构又包含了系统镜像头部和系统镜像。
1.1 bootm头部结构#
1 | 304 typedef struct bootm_headers { |
第 352~362 行这 11 个宏定义表示 BOOT 的不同阶段。
1.1.1 系统镜像头部结构#
先来看下image_header_t
结构,也就是系统镜像头部信息:
1 | typedef struct image_header { |
1.1.2 系统镜像结构#
再来看下image_info_t
结构,也就是系统镜像信息结构:
1 | typedef struct image_info { |
2 do_bootz 函数#
do_bootz
函数定义在cmd/bootm.c
:
先执行bootz_start
。先执行BOOTM_STATE_START
阶段。
第 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
。
2.1 bootz_start#
调用函数
do_bootm_states
,执行BOOTM_STATE_START
阶段。593 行,设置
images 的 ep
,也就是系统镜像的入口点,使用bootz
命令启动系统的时候就会设置系统在 DRAM 中的存储位置,这个存储位置就是系统镜像的入口点,因此images->ep=0X80800000
。镜像加载地址定义在include/configs/mx6ullevk.h
调用
bootz_setup
函数,此函数会判断当前的系统镜像文件是否为 Linux 的镜像文件,并且会打印出镜像相关信息,bootz_setup
函数稍后会讲解。调用
bootm_find_images
查找ramdisk
和设备树(dtb)
文件,但是我们没有用到ramdisk
,因此此函数在这里仅仅用于查找设备树(dtb)
文件,此函数稍后也会讲解。
2.1.1 bootm_start#
执行BOOTM_STATE_START
阶段时,执行bootm_start
:
初始化verfify
成员, 设置images
状态为 BOOTM_STATE_START
。
2.1.2 bootz_setup#
定义在文件 arch/arm/lib/bootm.c
:
- 宏
LINUX_ARM_ZIMAGE_MAGIC
就是 ARM Linux 系统魔术数
。 - 从传递进来的参数
image(也就是系统镜像首地址)
中获取zimage
头。 - 判断
image
是否为 ARM 的 Linux 系统镜像,如果不是的话就直接返回,并且打印出“Bad Linux ARM zImage magic!”
,比如我们输入一个错误的启动命令:bootz 80000000 – 900000000
因为我们并没有在0X80000000
处存放Linux 镜像文件(zImage)
,因此上面的命令肯定会执行出错如下: - 初始化函数
bootz_setup
的参数start 和 end
。 - 打印启动信息,如果 Linux 系统镜像正常的话打印如下:
2.1.3 bootm_find_images#
定义在文件 common/bootm.c
:
- 查找
ramdisk
,但是我们没有用到ramdisk
,因此这部分代码不用管。 - 查找
设备树(dtb)
文件,找到以后就将设备树的起始地址和长度分别写到images
的ft_addr
和ft_len
成员变量中。我们使用bootz
启动 Linux 的时候已经指明了设备树在DRAM 中的存储地址,因此images.ft_addr=0X83000000
,长度根据具体的设备树文件而定,比如我现在使用的设备树文件长度为0X8C81
,因此images.ft_len=0X8C81
。
2.2 do_bootm_states#
前面将state
先处理了BOOTM_STATE_START
阶段,接下来处里下面三个状态:
1 | BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO |
2.2.1 bootm_os_get_boot_func#
进入第658行,通过bootm_os_get_boot_func
来查找系统启动函数。由于前面提到images->os.os
就是系统类型设置 为 IH_OS_LINUX
,根据这个os
类型来选择对应的启动函数名为do_bootm_linux
:
2.2.2 boot_pre_linux#
第 676-677 行,处理BOOTM_STATE_OS_PREP
状态,调用函数 do_bootm_linux,do_bootm_linux
调用boot_prep_linux
来完成具体的处理过程。boot_prep_linux
主要用于处理环境变量bootargs,bootargs
保存着传递给Linux kernel
的参数:
设备树的chosen节点下存放了子节点bootargs,bootargs子节点存放bootargs环境变量
2.2.3 boot_jump_linux#
第699行,调用函数boot_selected_os
启动 Linux 内核,此函数第 4 个参数为 Linux 系统镜像头,第 5 个参数就是 Linux 系统启动函数 do_bootm_linux
。boot_selected_os
函数定义在文件common/bootm_os.c
如下:
最终调用 boot_selected_os->boot_fn(即do_bootm_linux)->boot_jump_linux
来启动 Linux 内核:boot_jump_linux
:
我们的板子IMX6ULL是armv7 32位架构,因此从else开始,第293 行,变量
machid
保存机器 ID,如果不使用设备树的话这个机器 ID 会被传递给 Linux内核,Linux 内核会在自己的机器 ID 列表里面查找是否存在与uboot
传递进来的machid
匹配的项目,如果存在就说明 Linux 内核支持这个机器,那么 Linux 就会启动!如果使用设备树的话这个machid
就无效了,设备树存有一个“兼容性”这个属性,Linux 内核会比较“兼容性”属性的值(字符串)来查看是否支持这个机器。第 295 行,函数
kernel_entry
,看名字“内核_进入”,说明此函数是进入 Linux 内核的,也就是最终的大boss!此函数有三个参数: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
来打印一些信息并做一些清理工作:
因此每次启动 Linux 之前输出“Starting kernel ...”
信息如下:继续回到函数
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
的值,最后调用调用
kernel_entry
函数进入 Linux 内核,至此Uboot
的整个运行流程结束,uboot
的使命也就完成了。
3 总结bootz启动过程#
4 uboot 启动 Linux 测试#
4.1 EMMC 启动 Linux#
编译出来的 Linux 镜像文件 zImage
和设备树文件保存在 EMMC中,uboot
从 EMMC 中读取这两个文件并启动,这个是我们产品最终的启动方式。但是我们目前还没有讲解如何移植linux 和设备树文件,以及如何将 zImage
和设备树文件保存到 EMMC中。不过大家拿到手的 I.MX6U-ALPHA 开发板(EMMC 版本)已经将 zImage
文件和设备树文件烧写到了 EMMC 中,所以我们可以直接读取来测试。先检查一下 EMMC 的分区 1 中有没有zImage
文件和设备树文件,输入命令ls mmc 1:1
:
EMMC 分区 1 中存在zimage
和 imx6ull-alientek-emmc.dtb
这两个文件,所以我们可以测试新移植的 uboot能不能启动 linux 内核。设置 bootargs
和 bootcmd
这两个环境变量:setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'mmc dev 1; fatload mmc 1:1 80800000 zImage; fatload mmc 1:1 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000;'
saveenv
直接输入 boot
,或者run bootcmd
即可启动 Linux 内核,如果 Linux 内核启动成功的话就会输出如下:
4.2 网络启动 Linux#
调试过程中由于我们不断更改kernel, 那么如果每次都烧录进emmc,从emmc启动就很繁琐,直接从网络启动。
先将zImage
和dtb
文件放在tftp共享目录下,通过nfs 或者 tftp
从 Ubuntu 中下载zImage
和设备树文件:
setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv
5 附录:比特与十六进制转换快查表#
1 | /* |
6 mkimage工具#
用来制作不压缩或者压缩的多种可启动映象文件。比如内核启动镜像(给zImage镜像添加64字节的头信息成uImage),再比如rootfs.img
。
1 | Usage: ./mkimage -l image |
选项 | 作用 |
---|---|
-A | 指定CPU的架构,比如:arm或者x86 |
-O | 指定操作系统的类型,比如:linux |
-T | 指定镜像的类型,比如:standalone、kernel、ramdisk、multi、firmware、script、filesystem |
-C | 指定压缩的方式,比如:none 不压缩、gzip 用gzip的压缩方式、bzip2 用bzip2的压缩方式 |
-a | 指定镜像在内存中的加载地址 |
-e | 指定镜像运行的入口点地址 |
-n | 指定镜像的名字 |
-d | 指定制作镜像的源文件 |
-x | 镜像是否可以片上执行 |
mkimage
命令举例如下:
1 | #mkimage -n "My Kernel" -A arm -O linux -T kernel -C gzip –a 0x8000 –e 0x8000 -d kernel.gz kernel.img |
以上命令将压缩了的内核二进制文件kernel.gz
转换成u-boot能够辨认的二进制文件kernel.img
,并指定kernel.img
的名字为“My Kernel”
,处理器体系架构为arm,操作系统类型为linux,程序类型为操作系统内核,程序由gzip压缩,程序的链接起始地址为0x8000,程序的入口地址为0x8000,注意这两个地址一定要是物理地址而不是对应的虚拟地址。
1 | #mkimage -n "My Rootfs" -A arm -O linux -T ramdisk -C gzip -d ramdisk.gz ramdisk.img |
以上命令将压缩了的ramdisk根文件系统二进制文件ramdisk.gz
转换成u-boot能够辨认的二进制文件ramdisk.img
,并指定ramdisk.img
的名字为“My Rootfs”
,处理器体系架构为arm,操作系统类型为linux,程序类型为ramdisk,程序由gzip压缩,不需要指定ramdisk的链接起始地址和入口地址。
如果我们将kernel.img
和ramdisk.img
文件分别写到flash芯片的0xFF000000
和0xFF200000
位置,系统启动后进入u-boot命令行界面,执行以下u-boot命令:
1 |
那么u-boot将把0xFF000000
位置的Linux内核解压缩到RAM中的0x8000
位置,再将0xFF200000
位置的ramdisk文件系统映像文件刨掉u-boot头部后复制到RAM中的某个位置,然后跳转到内核的入口地址0x8000
位置启动内核,同时把板子信息、ramdisk在RAM中的起始地址和结束地址、命令行字符串传给内核,这样Linux开始启动运行。
如果没有可以片上执行的norflash:
- mkimage
mkimage -n 'linux-2.6.14' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008040 -d zImage zImage.img
- tftp下载到ddr
ftp 0x30008000 zImage.img
- 启动内核
bootm 0x30008000