【linux kernel】启动加载程序的总结

刘博文
2023-12-01

一、背景

对于很多架构来说,都会使用【启动加载程序】将linux内核镜像加载到内存中。有的启动加载程序还会对内核镜像进行校验和检查,还有的会解压linux内核镜像并重新部署linux内核镜像。

对于linux内核来说,【启动加载程序】可以看做是引导加载程序linux内核镜像之间的粘合剂,启动加载程序负责提供合适的上下文环境让linux内核运行于其中。

本文将描述启动加载程序的组成结构和其一些重要功能。

(注)本文章内容基于linux内核版本:4.1.15

二、启动加载程序的结构

​ 本小节以ARM架构为例。

​ 在linux编译构建的最后阶段,启动加载程序会与vmlinux拼接成一个合成的、最终的linux内核镜像。在ARM架构下,启动加载程序放置于(/arch/arm/boot/compressed)目录下。其中承载启动加载核心功能的是:head.Shead-xxx.Smisc.cpiggy.xxx.S等文件。下文将逐一描述这些文件。

(2-1)piggy.xxx.S文件

​ 在/arch/arm/boot/compressed/目录中有piggy.gzip.S、piggy.lz4.S、piggy.lzma.S、piggy.lzo.S四个文件用于指定如何包含压缩后的linux内核镜像。本小节假设linux内核以lzo方式压缩,其piggy.lzo.S文件中的内容如下(/arch/arm/boot/compressed/piggly.lzo.S):

	.section .piggydata,#alloc
	.globl	input_data
input_data:
	.incbin	"arch/arm/boot/compressed/piggy.lzo"
	.globl	input_data_end
input_data_end:

​ 在编译构建过程中,汇编器将汇编这个文件并生成一个piggy.lzo镜像,该镜像包含一个名为.piggydata的段,piggly.lzo.S汇编文件的作用是将压缩后的二进制内核镜像(piggy.lzo)放到这个段中。压缩后内核镜像(piggy.lzo)是通过汇编器的预处理指令.incbin将piggy.lzo包含进来(.incbin类似于C语言中的#include文件指令)。input_datainput_data_end标签用于:在启动加载程序中确定包含的内核镜像的边界。

(2-2)head.o文件

​ 注意:这个文件与linux内核的入口(head.o)不同,虽然两者名称一样,但是其汇编代码和功能不一样。在启动加载程序中的head.o文件用于在底层用汇编语言实现处理器的初始化,具体的初始化操作有:支持处理器内部指令和数据缓存、禁止中断并建立C语言运行环境等。

(2-3)misc.c文件

misc.c文件用于解压和重新部署镜像。

三、启动加载程序的功能

(3-1)启动加载程序向linux内核的跳转

从启动加载程序向linux内核的跳转是由汇编语句:b __enter_kernel完成。

__enter_kernel是启动加载程序进入linux内核的入口点,汇编代码如下:

__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=内核的执行地址,上述代码使用mov pc, r4将pc设置为R4的值,从而实现启动加载程序向linux内核的跳转。

(3-2)解压linux内核镜像

首先解压linux内核镜像的入口是decompress_kernel()函数,该函数在(/arch/arm/compressed/head.S)文件中由汇编代码调用,调用点位于:

		mov	r0, r4
		mov	r1, sp			@ malloc space above stack
		add	r2, sp, #0x10000	@ 64k max
		mov	r3, r7
		bl	decompress_kernel
		bl	cache_clean_flush
		bl	cache_off
		mov	r1, r7			@ restore architecture number
		mov	r2, r8			@ restore atags pointer

#ifdef CONFIG_ARM_VIRT_EXT
		mrs	r0, spsr		@ Get saved CPU boot mode
		and	r0, r0, #MODE_MASK
		cmp	r0, #HYP_MODE		@ if not booted in HYP mode...
		bne	__enter_kernel		@ boot kernel directly

		adr	r12, .L__hyp_reentry_vectors_offset
		ldr	r0, [r12]
		add	r0, r0, r12

		bl	__hyp_set_vectors
		__HVC(0)			@ otherwise bounce to hyp mode

		b	.			@ should never be reached

		.align	2
.L__hyp_reentry_vectors_offset:	.long	__hyp_reentry_vectors - .
#else
		b	__enter_kernel
#endif

上述bl decompress_kernel代码调用decompress_kernel函数执行镜像解压。该函数定义如下:

extern int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x));

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");
}

在上述代码中,将调用do_decompress()函数,该函数定义在(/arch/arm/boot/compressed/decompress.c)文件中,其将调用__decompress()函数,该函数由配置宏定义管控并分别定义在/lib/decompress_unlzma.c、/lib/decompress_unlzo.c、/lib/decompress_unxz.c、/lib/decompress_unlz4.c文件中。

四、结尾

对于linux内核来说,启动加载程序并不是单独的运行程序,它是与vmlinux内核镜像拼接在一起,完成其使命的。

在linux源码构建过程的最后阶段,会打印出以下信息:

  LD      vmlinux
  SORTEX  vmlinux
  SYSMAP  System.map
  OBJCOPY arch/arm/boot/Image
  Building modules, stage 2.
  MODPOST 18 modules
  Kernel: arch/arm/boot/Image is ready
  LDS     arch/arm/boot/compressed/vmlinux.lds
  AS      arch/arm/boot/compressed/head.o
  LZO     arch/arm/boot/compressed/piggy.lzo
  CC      arch/arm/boot/compressed/misc.o
  CC      arch/arm/boot/compressed/decompress.o
  CC      arch/arm/boot/compressed/string.o
  AS      arch/arm/boot/compressed/piggy.lzo.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready

从以上信息分析可知,编译过程中先构建出ELF格式的内核主体vmlinux(包含符号、注释、调试信息和与架构相关的部分)。该内核主体由/arch/arm/kernel/vmlinux.lds.S链接脚本负责链接。

然后汇编出piggy.lzo.o(压缩后的内核镜像),最后会汇编出合成后的linux内核镜像。

本文分析了启动加载程序的大致结构,以及在汇编代码中的调用点和入口点等。
由于小生精力和知识有限,如遇文章存在有不妥之处,请多多批评或与我讨论,谢谢啦!


 类似资料: