linux内核启动过程分析(一)

一、常见linux镜像文件
  启动到最后阶段后会将内核镜像加载到内存中,然后通过bootm,bootz等命令跳转到内核代码运行。内核镜像被加载到内存并获取到控制权限后,内核启动流程开始。嵌入式系统中内核镜像通常以压缩形式存储,目的是为了节省嵌入式系统的存储空间,并且这种压缩形式的镜像并不是一个可以执行的文件。所以内核阶段的首要工作是自解压内核镜像。
  内核源码进行编译后生成vmlinux文件,通常对其进行压缩后得到uImage、zImage或者bzImage。

  • vlinux:编译出来后未压缩最原始的内核文件,在PC的系统中一般用这个文件。
  • zImage:vmlinux经过gzip压缩后的文件。并且在其头部包含了一个自解压的程序。
  • uImage:它是uboot专用的映像文件,它是在zImage之前加上一个长度为64字节的“头”,说明这个内核的版本、加载位置、生成时间、大小等信息;其0x40之后与zImage没区别。

二、内核的自解压过程(kernel 4.1.15)
1.uboot的结束
uboot 2019.01版本中。通过下面调用关系跳转至内核。

do_bootm   (cmd\bootm.c)
	do_bootm_states (common\bootm.c)
		boot_fn = bootm_os_get_boot_func(images->os.os);      //boot_fn=do_bootm_linux
		boot_fn       //执行这个函数实际上是执行do_bootm_linux (arch\arm\lib\bootm.c)
				boot_prep_linux    (arch\arm\lib\bootm.c)
				boot_jump_linux     (arch\arm\lib\bootm.c)
					kernel_entry(0, machid, r2);	(kernel_entry = (void (*)(int, int, uint))images->ep;)将控制权交给内核, 启动内核

kernel_entry(0, machid, r2)形参r2如下:

	if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
		r2 = (unsigned long)images->ft_addr;
	else
		r2 = gd->bd->bi_boot_params;

由上边可以看出进入内核前通用寄存器r0=0、r1=machid、r2=形参r2(如果有dts那么这个就是dts的地址否则就是bootargs)。
2.自解压程序汇编部分
  内核的自解压代码位于内核源码的arch/arm/boot/compressed目录下,目录下的vmlinux.lds为自解压程序的链接文件,从中可以看到自解压程序的入口函数地址为“_start”

OUTPUT_ARCH(arm)
ENTRY(_start)					/*指定自解压程序的入口地址*/
SECTIONS
{
  /DISCARD/ : {
    *(.ARM.exidx*)
    *(.ARM.extab*)
    /*
     * Discard any r/w data - this produces a link error if we have any,
     * which is required for PIC decompression.  Local data generates
     * GOTOFF relocations, which prevents it being relocated independently
     * of the text/got segments.
     */
    *(.data)
  }

“_start”入口函数采用汇编代码实现,位于arch/arm/boot/compressed/head.S中。下面对改文件进行简要分析,了解大体流程即可

 AR_CLASS(	.arm	)
start:					@程序入口位置
		.type	start,#function
		.rept	7
		mov	r0, r0
		.endr
   ARM(		mov	r0, r0		)
   ARM(		b	1f		)
 THUMB(		adr	r12, BSYM(1f)	)
 THUMB(		bx	r12		)

		.word	_magic_sig	@ Magic numbers to help the loader
		.word	_magic_start	@ absolute load/run zImage address
		.word	_magic_end	@ zImage end address
		.word	0x04030201	@ endianness flag

 THUMB(		.thumb			)
1:
 ARM_BE8(	setend	be		)	@ go BE8 if compiled for BE8
 AR_CLASS(	mrs	r9, cpsr	)
#ifdef CONFIG_ARM_VIRT_EXT
		bl	__hyp_stub_install	@ get into SVC mode, reversibly
#endif
		/*通过uboot中kernel_entry的传参可以看出当前通用寄存器的r0=0,r1=machid,r2=启动参数的指针。
		*将r1(machid)存放到r7中,将r2(启动参数指针)存放到了r8中。*/
		mov	r7, r1			@ save architecture ID
		mov	r8, r2			@ save atags pointer
#ifndef CONFIG_CPU_V7M
		/*
		 * Booting from Angel - need to enter SVC mode and disable
		 * FIQs/IRQs (numeric definitions from angel arm.h source).
		 * We only do this if we were in user mode on entry.
		 */
		 /*进入到SVC模式关闭FIQs/IRQs,因为在解压过程中不需要中断处理,也不希望被中断打断。*/
		mrs	r2, cpsr		@ get current mode
		tst	r2, #3			@ not user?
		bne	not_angel
		mov	r0, #0x17		@ angel_SWIreason_EnterSVC
 ARM(		swi	0x123456	)	@ angel_SWI_ARM
 THUMB(		svc	0xab		)	@ angel_SWI_THUMB
not_angel:
		safe_svcmode_maskall r0
		msr	spsr_cxsf, r9		@ Save the CPU boot mode in
						@ SPSR
#endif
....../*部分内容省略*/
#ifdef CONFIG_AUTO_ZRELADDR
		/*
		 * Find the start of physical memory.  As we are executing
		 * without the MMU on, we are in the physical address space.
		 * We just need to get rid of any offset by aligning the
		 * address.
		 *
		 * This alignment is a balance between the requirements of
		 * different platforms - we have chosen 128MB to allow
		 * platforms which align the start of their physical memory
		 * to 128MB to use this feature, while allowing the zImage
		 * to be placed within the first 128MB of memory on other
		 * platforms.  Increasing the alignment means we place
		 * stricter alignment requirements on the start of physical
		 * memory, but relaxing it means that we break people who
		 * are already placing their zImage in (eg) the top 64MB
		 * of this range.
		 */
		/* 这里是计算真正的内核存放位置。计算过程是r4=pc,r4&=0xf8000000,r4+=TEXT_OFFSET。
		*压缩的内核映射会被解压到这个地址。
		*TEXT_OFFSET:内核便宜地址,即内核起始位置相对于内存起始位置的便宜,
		*对于物理内存还是虚拟内存都是一样的结果。在/arch/arm/makefile中设定。*/
		mov	r4, pc
		and	r4, r4, #0xf8000000
		/* Determine final kernel image address. */
		add	r4, r4, #TEXT_OFFSET
#else
		/*zreladdr内核启动在RAM中的物理地址。压缩的内核映射被解压到这个地址,然后执行。
		*这里是条件编译的,其作用和上面的类似。*/
		ldr	r4, =zreladdr
#endif
....../*部分内容省略*/
/*
 * 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
 */
 		/*下面跳转到decompress_kernel的C代码执行,实现解压过程,
 		*其中形参1是内核解压后的存放地址
 		*形参2是申请堆栈的首地址
 		*形参3是堆栈的结束地址
 		*形参4是machid。	*/
		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
		/*将R7和R8中保存的machid,启动参数的指针在回填到r1和r2中。
		*目的是为跳转到真正的内核代码传参。*/
		mov	r1, r7			@ restore architecture number
		mov	r2, r8			@ restore atags pointer
....../*这个中间省略掉一个条件编译,宏没被定义所以会跳过。*/
		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
....../*部分内容省略*/	 

到这里自解压的过程就已经完成,剩下的就是真正的内核被执行后,开始自己生命过程。
3.C语言实现的解压过程(decompress_kernel)
decompress_kernel调用关系如下:

decompress_kernel 
	do_decompress
		__decompress 

decompress_kernel (arch/arm/boot/compressed/misc.c)

/*由汇编代码可以看到下面传递参数的意义。
 		*output_start内核解压后的存放地址
 		*free_mem_ptr_p申请的堆栈地址(首地址)
 		*free_mem_ptr_end_p堆栈的结束地址
 		*arch_id是machid。	*/
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)

#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.c"
#endif

#ifdef CONFIG_KERNEL_LZO
#include "../../../../lib/decompress_unlzo.c"
#endif

#ifdef CONFIG_KERNEL_LZMA
#include "../../../../lib/decompress_unlzma.c"
#endif

#ifdef CONFIG_KERNEL_XZ
#define memmove memmove
#define memcpy memcpy
#include "../../../../lib/decompress_unxz.c"
#endif

#ifdef CONFIG_KERNEL_LZ4
#include "../../../../lib/decompress_unlz4.c"
#endif

int do_decompress(u8 *input, int len, u8 *output, void (*error)(char *x))
{
	return __decompress(input, len, NULL, NULL, output, 0, NULL, error);
}		

若配置CONFIG_KERNEL_LZ4则__decompress(lib/decompress_unlzo.c)实现过程如下:

STATIC int INIT __decompress(unsigned char *buf, long len,
			   long (*fill)(void*, unsigned long),
			   long (*flush)(void*, unsigned long),
			   unsigned char *out_buf, long olen,
			   long *pos,
			   void (*error)(char *x))
{
	/*unlzo 可在decompress_unlzo.c中找到。*/
	return unlzo(buf, len, fill, flush, out_buf, pos, error);
}

接下一篇:linux内核启动过程分析(二)

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飘忽不定的bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值