内核的解压缩过程详解
Posted 正在起飞的蜗牛
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核的解压缩过程详解相关的知识,希望对你有一定的参考价值。
1、各种内核镜像之间的区别
2、为什么内核需要解压缩
(1)需要解压缩是因为在制作内核镜像时进行了压缩,而压缩的镜像是不能直接执行的,内核镜像 = 未压缩的头部 + 压缩的内核;
(2)未压缩的头部主要功能就是将压缩的内核镜像解压缩后放到内存的合适地址处,然后调用解压缩的内核;
(3)将内核压缩的原因是未压缩的内核体积太大,压缩后可以节省存储空间。
(4)节省空间的代价就是内核启动过程更复杂,解压缩也会导致启动内核的时间更长。在现在复杂一些的嵌入式设备中,flash动辄就有几十个G,压缩内核节省的几兆空间其实影响不大,
所以个人觉得压缩内核对flash小的设备性价比高,对flash大的设备性价比不高,但是为了统一,都是采用了压缩的方式。
3、uboot启动内核的前期工作
(1)将内核镜像重定位到内存中,解析镜像的头部,找到内核镜像的入口地址;
(2)准备好机器码,tag参数地址或者dtb数据的地址;
(3)调用内核入口,将上一步准备好的数据传递给内核;
4、解压缩涉及的文件
(1)以arm架构的芯片为例:"arch/arm/boot/comperssed/"目录;
(2)分析镜像的程序入口和镜像组成结构,查看vmlinux.lds;
(3)整个镜像程序入口是在head.S文件中;
5、确认解压缩内核的存放地址
5.1、汇编代码
#ifdef CONFIG_AUTO_ZRELADDR
mov r4, pc //当前PC的值保存到r4寄存器
and r4, r4, #0xf8000000 //只保留高5位
/* Determine final kernel image address. */
add r4, r4, #TEXT_OFFSET //加上偏移量#TEXT_OFFSET
#else
ldr r4, =zreladdr //在配置文件中指定解压缩的内核地址
#endif
5.2、CONFIG_AUTO_ZRELADDR宏
(1)上面汇编代码的功能就是计算出解压缩内核的存放地址,然后保存到r4寄存器中;
(2)定义了CONFIG_AUTO_ZRELADDR宏表示根据当前PC的值计算出解压缩后内核存放的地址,然后存放到r4寄存器;
(3)如果没有定义CONFIG_AUTO_ZRELADDR宏,则把zreladdr变量的值赋值给r4寄存器;
5.3、TEXT_OFFSET宏
(1)相对于内存起址的内核代码存放的偏移,通常设为 32k (0x8000);
(2)TEXT_OFFSET宏分析"arch/arm/boot/comperssed/Makefile"和"arch/arm/Makefile";
5.4、zreladdr变量
//arch/arm/boot/comperssed/Makefile
ifneq ($(CONFIG_AUTO_ZRELADDR),y)
LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)
endif
//arch/arm/boot/Makefile
ZRELADDR := $(zreladdr-y)
//arch/arm/Mach-xxx/ Makefile.boot
在这里指定zreladdr-y的值
解压后内核存放的地址,也是最后解压后内核的运行起址,可以在配置文件中指定;
6、内核解压缩函数
6.1、汇编代码部分
* The C runtime environment should now be setup sufficiently.
* Set up some pointers, and start decompressing.
* r4 = kernel execution address
* r7 = architecture ID
* r8 = atags pointer
*/
mov r0, r4 //内核解压后存放的地址
mov r1, sp @ malloc space above stack
add r2, sp, #0x10000 @ 64k max
mov r3, r7 //architecture ID:机器码
bl decompress_kernel //执行解压zImage为elf格式的可执行kernel镜像
bl cache_clean_flush
bl cache_off
mov r1, r7 @ restore architecture number
mov r2, r8 @ restore atags pointer
//函数原型
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, \\
unsigned long free_mem_ptr_end_p,
int arch_id)
decompress_kernel函数传参 | 含义 |
---|---|
output_start | 内核解压缩后数据存放的起始内存地址 |
free_mem_ptr_p | 可用的内存起始地址 |
free_mem_ptr_end_p | 可用内存结束地址 |
arch_id | 机器码 |
(1)r4在这之前已经被存放了内核解压缩后存放的内存地址,这里将r4寄存器的值赋值给r0;
(2)sp是栈寄存器,r1寄存器的值是当前栈地址,r2寄存器的值是当前栈地址+64KB,效果就是在栈上面分配了64KB的内存空间传给decompress_kernel()函数;
(3)r3寄存器被赋值r7寄存器的值,是机器码;
(4)decompress_kernel()函数的4个传参分别对应r0-r3四个寄存器;
6.2、decompress_kernel()函数源码分析
//arch/arm/boot/compressed/piggy.S
.section .piggydata,#alloc
.globl input_data
input_data:
.incbin "arch/arm/boot/compressed/piggy_data"
.globl input_data_end
input_data_end:
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,
unsigned long free_mem_ptr_end_p, int arch_id)
int ret;
__stack_chk_guard_setup();
//对一些全局变量赋值
output_data = (unsigned char *)output_start; //解压缩后内核存放的内存地址
free_mem_ptr = free_mem_ptr_p; //可用内存的起始地址
free_mem_end_ptr = free_mem_ptr_end_p; //可用内存的结束地址
__machine_arch_type = arch_id; //机器码
//解压缩模块初始化
arch_decomp_setup();
putstr("Uncompressing Linux...");
//(压缩内核镜像所在内存地址, 压缩内核镜像大小, 解压缩镜像的存放地址, 出错处理函数指针)
ret = do_decompress(input_data, input_data_end - input_data,
output_data, error);
if (ret)
error("decompressor returned an error");
else
putstr(" done, booting the kernel.\\n");
(1)do_decompress()函数才是真正做解压缩的函数,只需要将压缩内核镜像的地址和长度、解压缩内核存放地址等信息传进去,函数内部会根据配置项调用对应解压缩方法的函数,原则就是用什么压缩方法就用对应的解压缩方法;
(2)压缩内核镜像所在的起始地址和结束地址保存在input_data、input_data_end变量中,这在piggy.S文件中指定,具体可以分析链接脚本;
(3)piggy_data就是压缩后的内核镜像,生成的大致过程:vmlinux->Image->piggy_data->zImage->zImage-dtb;
7、调用解压缩的内核
b __enter_kernel
__enter_kernel:
mov r0, #0 @ must be 0
ARM( mov pc, r4 ) @ call kernel 调用解压缩后的内核镜像
M_CLASS( add r4, r4, #1 ) @ enter in Thumb mode for M class
THUMB( bx r4 ) @ entry point is always ARM for A/R classes
在内核解压缩阶段,已经把解压缩的内核放到了r4寄存器执行的内存地址,这里将r4寄存器的值赋值给PC寄存器,下面就是执行r4寄存器指向的内存地址,也就是内核的第一句代码。
以上是关于内核的解压缩过程详解的主要内容,如果未能解决你的问题,请参考以下文章