bootz 启动 kernel

Posted Li-Yongjun

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了bootz 启动 kernel相关的知识,希望对你有一定的参考价值。


活动地址:CSDN21天学习挑战赛

思考

uboot 启动 kernel,这是一个众所周知的事实。但是,你有没有思考过,这两个独立的镜像实体,是如何做到 A 启动 B 的呢?如果是同一份代码中的 func_a() 调用 func_b(),还好理解,因为是在同一份程序中(或者说是同一份指令中),func_a 是知道 func_b() 的地址的,直接跳转过去就行了(设置 PC 指针指向 func_b 的地址)。
但是 uboot 和 kernel 不在同一份指令中,这是两个独立的程序,那么是怎样实现 程序 A “调用” 程序 B 的呢?

bootz

在 uboot 命令行下,可以使用 bootz 命令启动 kernel,我们就以此为切入点,探究 uboot 启动 kernel 的底层原理。

表象

使用如下命令,就能完成 uboot 启动 kernel。

tftp 80800000 zImage
tftp 83000000 imx6ull-14x14-evk.dtb
bootz 80800000 - 83000000

这里简单介绍下命令作用。
前两条是通过 tftp 命令,分别将 kernel 镜像 zImage 和设备树二进制文件下载到内存的 0x80800000 和 0x83000000 位置。
第三条是使用 bootz 命令启动 kernel。

bootz

接下来,就分析下 bootz 启动 kernel 的原理。
通过 uboot 命令行,向 bootz 传递了三个参数 “80800000”、“-”、“83000000”,它们分别是 “kernel 镜像在内存中的首地址”、“无效参数”、“设备树文件首地址”,
bootz 命令对应的执行函数为 do_bootz(),在该函数中,首先设置 kernel 首地址和设备树首地址(其实这就是 uboot 能够启动 kernel 的原因:有了地址就能跳转过去运行)。

images->ep = simple_strtoul(argv[0], NULL, 16);

变量 ep 用来存储 kernel 镜像在内存中的首地址,该参数由 argv[0] 赋值,argv[0] 就是刚刚在命令行传递给 bootz 的第一个参数 80800000

images.ft_addr = argv[2];

变量 ft_addr 用来存储设备树在内存中的首地址,该参数由 argv[2] 赋值,为 83000000

kernel_entry()

do_bootz() 最终会调用 kernel_entry(),kernel_entry 并不是在 uboot 中定义的函数,它是一个地址转换来的,如下:

kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1,
				void *res2))images->ep;

而 image->ep 就是 kernel 在内存中的首地址,这不就联系上了嘛🤞。

uboot 将设备树传递给 kernel

刚才分析出 images.ft_addr 存放设备树在内存中的首地址,那又是如何将这个地址传递给 kernel 呢?这里不像函数传参,当成实参就传递过去了。
一来 uboot 和 kernel 不是同一份程序,不共用同一个堆栈;二来 kernel 一开始的代码是汇编代码,C 函数也没办法直接将自己的实参传递给汇编函数。
那要怎么才能实现 uboot 中的一个变量传递给 kernel 呢?答案是使用寄存器。
uboot 生命周期的最后一个函数调用如下

r2 = (unsigned long)images->ft_addr;
kernel_entry(0, machid, r2);

使用 r2 寄存器存储设备树文件的首地址。

kernel 接收设备树

再来看 kernel 是怎么接收这个地址的呢?
kernel 的入口函数是 head.S 中的 stext

ENTRY(stext)
	...
	ldr	r13, =__mmap_switched		@ address to jump to after

__mmap_switched:
	...
	str	r2, [r6]			@ Save atags pointer
	cmp	r7, #0
	strne	r0, [r7]			@ Save control register values
	b	start_kernel
ENDPROC(__mmap_switched)


__mmap_switched_data:
	.long	__data_loc			@ r4
	.long	_sdata				@ r5
	.long	__bss_start			@ r6
	.long	_end				@ r7
	.long	processor_id			@ r4
	.long	__machine_arch_type		@ r5
	.long	__atags_pointer			@ r6
...

总体流程是:
stext 调用 __mmap_switched,在 __mmap_switched 中将 r2 的值赋给全局变量 __atags_pointer,然后调用 start_kernel()。

start_kernel() 调用 setup_arch(),在该函数中解析设备树,如下:

mdesc = setup_machine_fdt(__atags_pointer);

总结

uboot 启动 kernel:

uboot 拿到 kernel 在内存中的首地址,就能跳转到该地址运行。

uboot 将设备树传递给 kernel:

通过 r2 寄存器。

以上是关于bootz 启动 kernel的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统移植:bootz 启动 Linux 内核

Linux系统移植:bootz 启动 Linux 内核

bootz启动linux内核——uboot生命的终点——学习笔记

bootz启动内核流程

Spring Bootz之热部署

Ubuntu驱动程序开发2-Uboot命令使用